From 5c73eb05c326c519a3d6fcb51db6bcaebbe0a43d Mon Sep 17 00:00:00 2001
From: lieser <lieser@server.fake>
Date: Mon, 13 May 2013 20:13:10 +0200
Subject: [PATCH] 0.1 release

---
 LICENSE.txt                          |  21 +
 README.md                            |  23 +-
 THIRDPARTY_LICENSE.txt               | 117 ++++
 chrome.manifest                      |   5 +
 chrome/content/asn1hex-1.1.js        | 288 ++++++++
 chrome/content/base64.js             |  71 ++
 chrome/content/dkim.js               | 978 +++++++++++++++++++++++++++
 chrome/content/dns.js                | 553 +++++++++++++++
 chrome/content/jsbn.js               | 559 +++++++++++++++
 chrome/content/jsbn2.js              | 656 ++++++++++++++++++
 chrome/content/msgHdrViewOverlay.xul |  46 ++
 chrome/content/prng4.js              |  45 ++
 chrome/content/rng.js                |  68 ++
 chrome/content/rsa.js                | 112 +++
 chrome/content/rsasign-1.2.js        | 392 +++++++++++
 chrome/locale/en-US/dkim.js          |  76 +++
 chrome/locale/en-US/dns.js           |   7 +
 chrome/locale/en-US/xulstrings.dtd   |   1 +
 install.rdf                          |  40 ++
 19 files changed, 4054 insertions(+), 4 deletions(-)
 create mode 100644 LICENSE.txt
 create mode 100644 THIRDPARTY_LICENSE.txt
 create mode 100644 chrome.manifest
 create mode 100644 chrome/content/asn1hex-1.1.js
 create mode 100644 chrome/content/base64.js
 create mode 100644 chrome/content/dkim.js
 create mode 100644 chrome/content/dns.js
 create mode 100644 chrome/content/jsbn.js
 create mode 100644 chrome/content/jsbn2.js
 create mode 100644 chrome/content/msgHdrViewOverlay.xul
 create mode 100644 chrome/content/prng4.js
 create mode 100644 chrome/content/rng.js
 create mode 100644 chrome/content/rsa.js
 create mode 100644 chrome/content/rsasign-1.2.js
 create mode 100644 chrome/locale/en-US/dkim.js
 create mode 100644 chrome/locale/en-US/dns.js
 create mode 100644 chrome/locale/en-US/xulstrings.dtd
 create mode 100644 install.rdf

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..49bf2386
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Philippe Lieser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
index 7add9c2c..df1906fc 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,19 @@
-dkim_verifier
-=============
-
-DKIM Verifier Extension for Mozilla Thunderbird
+DKIM Verifier
+=============
+
+This is a Add-on for Mozilla Thunderbird, that verifies DKIM Signatures according to the RFC 6376 (http://tools.ietf.org/html/rfc6376)
+
+Included third-party Libraries
+------------------------------
+ - Tom Wu's jsbn library - BigInteger and RSA (http://www-cs-students.stanford.edu/~tjw/jsbn/)
+  - jsbn.js - basic BigInteger class
+  - jsbn2.js - BigInteger class extension
+  - rsa.js - RSAKey class for RSA public key encryption
+  - rng.js - Random number generator
+  - prng4.js - Random number generator
+  - base64.js - String encoder for Base64 and Hex
+ - Kenji Urushima's 'RSA-Sign JavaScript Library' (http://kjur.github.com/jsrsasign)
+  - asn1hex.js - simple ASN.1 parser to read hexadecimal encoded ASN.1 DER
+  - rsasign-1.2.js - RSAKey class extension for RSA signing and verification
+ - Joshua Tauberer's DNS Libary (part of Thunderbird Sender Verification Extension) (http://razor.occams.info/code/spf/)
+  - dns.js - DNS Library
\ No newline at end of file
diff --git a/THIRDPARTY_LICENSE.txt b/THIRDPARTY_LICENSE.txt
new file mode 100644
index 00000000..12d6ab1f
--- /dev/null
+++ b/THIRDPARTY_LICENSE.txt
@@ -0,0 +1,117 @@
+This document contains third party licensing information for third party
+software components included within DKIM Verifier.
+
+
+*******************************************************************************
+*******************************************************************************
+For Tom Wu's jsbn library - BigInteger and RSA
+
+jsbn.js - basic BigInteger class
+jsbn2.js - BigInteger class extension
+rsa.js - RSAKey class for RSA public key encryption
+rng.js - Random number generator
+prng4.js - Random number generator
+base64.js - String encoder for Base64 and Hex
+
+Site:
+http://www-cs-students.stanford.edu/~tjw/jsbn/
+License:
+http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE
+
+*******************************************************************************
+
+Licensing
+---------
+
+This software is covered under the following copyright:
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+Address all questions regarding this license to:
+
+  Tom Wu
+  tjw@cs.Stanford.EDU
+
+
+*******************************************************************************
+*******************************************************************************
+For Kenji Urushima's 'RSA-Sign JavaScript Library' (jsrsasig)
+
+asn1hex.js - simple ASN.1 parser to read hexadecimal encoded ASN.1 DER
+rsasign-1.2.js - RSAKey class extension for RSA signing and verification
+
+Site:
+http://kjur.github.com/jsrsasign
+License:
+http://kjur.github.com/jsrsasign/license/
+
+*******************************************************************************
+
+The 'jsrsasign'(RSA-Sign JavaScript Library) License
+
+Copyright (c) 2010-2013 Kenji Urushima
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+*******************************************************************************
+*******************************************************************************
+For Joshua Tauberer's DNS Libary
+(part of Thunderbird Sender Verification Extension)
+
+dns.js - DNS Library
+
+Site:
+http://razor.occams.info/code/spf/
+
+*******************************************************************************
+
+Copyright 2005 Joshua Tauberer <http://razor.occams.info>
+
+Feel free to use this file however you want, but
+credit would be nice.
diff --git a/chrome.manifest b/chrome.manifest
new file mode 100644
index 00000000..769f999c
--- /dev/null
+++ b/chrome.manifest
@@ -0,0 +1,5 @@
+content		dkim_verifier			chrome/content/
+locale		dkim_verifier en-US		chrome/locale/en-US/
+overlay		chrome://messenger/content/msgHdrViewOverlay.xul  chrome://dkim_verifier/content/msgHdrViewOverlay.xul
+# skin  mailhops  classic/1.0  jar:chrome/mailhops.jar!/skin/classic/
+# overlay		chrome://messenger/content/messenger.xul  chrome://dkim_verifier/content/myhelloworld.xul
diff --git a/chrome/content/asn1hex-1.1.js b/chrome/content/asn1hex-1.1.js
new file mode 100644
index 00000000..10917393
--- /dev/null
+++ b/chrome/content/asn1hex-1.1.js
@@ -0,0 +1,288 @@
+/*! asn1hex-1.1.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license
+ */
+//
+// asn1hex.js - Hexadecimal represented ASN.1 string library
+//
+// version: 1.1 (09-May-2012)
+//
+// Copyright (c) 2010-2012 Kenji Urushima (kenji.urushima@gmail.com)
+//
+// This software is licensed under the terms of the MIT License.
+// http://kjur.github.com/jsrsasign/license/
+//
+// The above copyright and license notice shall be 
+// included in all copies or substantial portions of the Software.
+//
+// Depends on:
+//
+
+// MEMO:
+//   f('3082025b02...', 2) ... 82025b ... 3bytes
+//   f('020100', 2) ... 01 ... 1byte
+//   f('0203001...', 2) ... 03 ... 1byte
+//   f('02818003...', 2) ... 8180 ... 2bytes
+//   f('3080....0000', 2) ... 80 ... -1
+//
+//   Requirements:
+//   - ASN.1 type octet length MUST be 1. 
+//     (i.e. ASN.1 primitives like SET, SEQUENCE, INTEGER, OCTETSTRING ...)
+//   - 
+
+/**
+ * @fileOverview
+ * @name asn1hex-1.1.js
+ * @author Kenji Urushima kenji.urushima@gmail.com
+ * @version 1.1
+ * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a>
+ */
+
+/**
+ * get byte length for ASN.1 L(length) bytes
+ * @name getByteLengthOfL_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return byte length for ASN.1 L(length) bytes
+ */
+function _asnhex_getByteLengthOfL_AtObj(s, pos) {
+  if (s.substring(pos + 2, pos + 3) != '8') return 1;
+  var i = parseInt(s.substring(pos + 3, pos + 4));
+  if (i == 0) return -1; 		// length octet '80' indefinite length
+  if (0 < i && i < 10) return i + 1;	// including '8?' octet;
+  return -2;				// malformed format
+}
+
+
+/**
+ * get hexadecimal string for ASN.1 L(length) bytes
+ * @name getHexOfL_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return {String} hexadecimal string for ASN.1 L(length) bytes
+ */
+function _asnhex_getHexOfL_AtObj(s, pos) {
+  var len = _asnhex_getByteLengthOfL_AtObj(s, pos);
+  if (len < 1) return '';
+  return s.substring(pos + 2, pos + 2 + len * 2);
+}
+
+//
+//   getting ASN.1 length value at the position 'idx' of
+//   hexa decimal string 's'.
+//
+//   f('3082025b02...', 0) ... 82025b ... ???
+//   f('020100', 0) ... 01 ... 1
+//   f('0203001...', 0) ... 03 ... 3
+//   f('02818003...', 0) ... 8180 ... 128
+/**
+ * get integer value of ASN.1 length for ASN.1 data
+ * @name getIntOfL_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return ASN.1 L(length) integer value
+ */
+function _asnhex_getIntOfL_AtObj(s, pos) {
+  var hLength = _asnhex_getHexOfL_AtObj(s, pos);
+  if (hLength == '') return -1;
+  var bi;
+  if (parseInt(hLength.substring(0, 1)) < 8) {
+     bi = parseBigInt(hLength, 16);
+  } else {
+     bi = parseBigInt(hLength.substring(2), 16);
+  }
+  return bi.intValue();
+}
+
+/**
+ * get ASN.1 value starting string position for ASN.1 object refered by index 'idx'.
+ * @name getStartPosOfV_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ */
+function _asnhex_getStartPosOfV_AtObj(s, pos) {
+  var l_len = _asnhex_getByteLengthOfL_AtObj(s, pos);
+  if (l_len < 0) return l_len;
+  return pos + (l_len + 1) * 2;
+}
+
+/**
+ * get hexadecimal string of ASN.1 V(value)
+ * @name getHexOfV_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return {String} hexadecimal string of ASN.1 value.
+ */
+function _asnhex_getHexOfV_AtObj(s, pos) {
+  var pos1 = _asnhex_getStartPosOfV_AtObj(s, pos);
+  var len = _asnhex_getIntOfL_AtObj(s, pos);
+  return s.substring(pos1, pos1 + len * 2);
+}
+
+/**
+ * get hexadecimal string of ASN.1 TLV at
+ * @name getHexOfTLV_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return {String} hexadecimal string of ASN.1 TLV.
+ * @since 1.1
+ */
+function _asnhex_getHexOfTLV_AtObj(s, pos) {
+  var hT = s.substr(pos, 2);
+  var hL = _asnhex_getHexOfL_AtObj(s, pos);
+  var hV = _asnhex_getHexOfV_AtObj(s, pos);
+  return hT + hL + hV;
+}
+
+/**
+ * get next sibling starting index for ASN.1 object string
+ * @name getPosOfNextSibling_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} pos string index
+ * @return next sibling starting index for ASN.1 object string
+ */
+function _asnhex_getPosOfNextSibling_AtObj(s, pos) {
+  var pos1 = _asnhex_getStartPosOfV_AtObj(s, pos);
+  var len = _asnhex_getIntOfL_AtObj(s, pos);
+  return pos1 + len * 2;
+}
+
+/**
+ * get array of indexes of child ASN.1 objects
+ * @name getPosArrayOfChildren_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} s hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} start string index of ASN.1 object
+ * @return {Array of Number} array of indexes for childen of ASN.1 objects
+ */
+function _asnhex_getPosArrayOfChildren_AtObj(h, pos) {
+  var a = new Array();
+  var p0 = _asnhex_getStartPosOfV_AtObj(h, pos);
+  a.push(p0);
+
+  var len = _asnhex_getIntOfL_AtObj(h, pos);
+  var p = p0;
+  var k = 0;
+  while (1) {
+    var pNext = _asnhex_getPosOfNextSibling_AtObj(h, p);
+    if (pNext == null || (pNext - p0  >= (len * 2))) break;
+    if (k >= 200) break;
+
+    a.push(pNext);
+    p = pNext;
+
+    k++;
+  }
+
+  return a;
+}
+
+/**
+ * get string index of nth child object of ASN.1 object refered by h, idx
+ * @name getNthChildIndex_AtObj
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} h hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} idx start string index of ASN.1 object
+ * @param {Number} nth for child
+ * @return {Number} string index of nth child.
+ * @since 1.1
+ */
+function _asnhex_getNthChildIndex_AtObj(h, idx, nth) {
+  var a = _asnhex_getPosArrayOfChildren_AtObj(h, idx);
+  return a[nth];
+}
+
+// ========== decendant methods ==============================
+
+/**
+ * get string index of nth child object of ASN.1 object refered by h, idx
+ * @name getDecendantIndexByNthList
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} h hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} currentIndex start string index of ASN.1 object
+ * @param {Array of Number} nthList array list of nth
+ * @return {Number} string index refered by nthList
+ * @since 1.1
+ */
+function _asnhex_getDecendantIndexByNthList(h, currentIndex, nthList) {
+  if (nthList.length == 0) {
+    return currentIndex;
+  }
+  var firstNth = nthList.shift();
+  var a = _asnhex_getPosArrayOfChildren_AtObj(h, currentIndex);
+  return _asnhex_getDecendantIndexByNthList(h, a[firstNth], nthList);
+}
+
+/**
+ * get hexadecimal string of ASN.1 TLV refered by current index and nth index list.
+ * @name getDecendantHexTLVByNthList
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} h hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} currentIndex start string index of ASN.1 object
+ * @param {Array of Number} nthList array list of nth
+ * @return {Number} hexadecimal string of ASN.1 TLV refered by nthList
+ * @since 1.1
+ */
+function _asnhex_getDecendantHexTLVByNthList(h, currentIndex, nthList) {
+  var idx = _asnhex_getDecendantIndexByNthList(h, currentIndex, nthList);
+  return _asnhex_getHexOfTLV_AtObj(h, idx);
+}
+
+/**
+ * get hexadecimal string of ASN.1 V refered by current index and nth index list.
+ * @name getDecendantHexVByNthList
+ * @memberOf ASN1HEX
+ * @function
+ * @param {String} h hexadecimal string of ASN.1 DER encoded data
+ * @param {Number} currentIndex start string index of ASN.1 object
+ * @param {Array of Number} nthList array list of nth
+ * @return {Number} hexadecimal string of ASN.1 V refered by nthList
+ * @since 1.1
+ */
+function _asnhex_getDecendantHexVByNthList(h, currentIndex, nthList) {
+  var idx = _asnhex_getDecendantIndexByNthList(h, currentIndex, nthList);
+  return _asnhex_getHexOfV_AtObj(h, idx);
+}
+
+// ========== class definition ==============================
+
+/**
+ * ASN.1 DER encoded hexadecimal string utility class
+ * @class ASN.1 DER encoded hexadecimal string utility class
+ * @author Kenji Urushima
+ * @version 1.1 (09 May 2012)
+ * @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a>
+ * @since 1.1
+ */
+function ASN1HEX() {
+  return ASN1HEX;
+}
+
+ASN1HEX.getByteLengthOfL_AtObj = _asnhex_getByteLengthOfL_AtObj;
+ASN1HEX.getHexOfL_AtObj = _asnhex_getHexOfL_AtObj;
+ASN1HEX.getIntOfL_AtObj = _asnhex_getIntOfL_AtObj;
+ASN1HEX.getStartPosOfV_AtObj = _asnhex_getStartPosOfV_AtObj;
+ASN1HEX.getHexOfV_AtObj = _asnhex_getHexOfV_AtObj;
+ASN1HEX.getHexOfTLV_AtObj = _asnhex_getHexOfTLV_AtObj;
+ASN1HEX.getPosOfNextSibling_AtObj = _asnhex_getPosOfNextSibling_AtObj;
+ASN1HEX.getPosArrayOfChildren_AtObj = _asnhex_getPosArrayOfChildren_AtObj;
+ASN1HEX.getNthChildIndex_AtObj = _asnhex_getNthChildIndex_AtObj;
+ASN1HEX.getDecendantIndexByNthList = _asnhex_getDecendantIndexByNthList;
+ASN1HEX.getDecendantHexVByNthList = _asnhex_getDecendantHexVByNthList;
+ASN1HEX.getDecendantHexTLVByNthList = _asnhex_getDecendantHexTLVByNthList;
diff --git a/chrome/content/base64.js b/chrome/content/base64.js
new file mode 100644
index 00000000..f5b168c4
--- /dev/null
+++ b/chrome/content/base64.js
@@ -0,0 +1,71 @@
+var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+var b64pad="=";
+
+function hex2b64(h) {
+  var i;
+  var c;
+  var ret = "";
+  for(i = 0; i+3 <= h.length; i+=3) {
+    c = parseInt(h.substring(i,i+3),16);
+    ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63);
+  }
+  if(i+1 == h.length) {
+    c = parseInt(h.substring(i,i+1),16);
+    ret += b64map.charAt(c << 2);
+  }
+  else if(i+2 == h.length) {
+    c = parseInt(h.substring(i,i+2),16);
+    ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
+  }
+  while((ret.length & 3) > 0) ret += b64pad;
+  return ret;
+}
+
+// convert a base64 string to hex
+function b64tohex(s) {
+  var ret = ""
+  var i;
+  var k = 0; // b64 state, 0-3
+  var slop;
+  for(i = 0; i < s.length; ++i) {
+    if(s.charAt(i) == b64pad) break;
+    v = b64map.indexOf(s.charAt(i));
+    if(v < 0) continue;
+    if(k == 0) {
+      ret += int2char(v >> 2);
+      slop = v & 3;
+      k = 1;
+    }
+    else if(k == 1) {
+      ret += int2char((slop << 2) | (v >> 4));
+      slop = v & 0xf;
+      k = 2;
+    }
+    else if(k == 2) {
+      ret += int2char(slop);
+      ret += int2char(v >> 2);
+      slop = v & 3;
+      k = 3;
+    }
+    else {
+      ret += int2char((slop << 2) | (v >> 4));
+      ret += int2char(v & 0xf);
+      k = 0;
+    }
+  }
+  if(k == 1)
+    ret += int2char(slop << 2);
+  return ret;
+}
+
+// convert a base64 string to a byte/number array
+function b64toBA(s) {
+  //piggyback on b64tohex for now, optimize later
+  var h = b64tohex(s);
+  var i;
+  var a = new Array();
+  for(i = 0; 2*i < h.length; ++i) {
+    a[i] = parseInt(h.substring(2*i,2*i+2),16);
+  }
+  return a;
+}
diff --git a/chrome/content/dkim.js b/chrome/content/dkim.js
new file mode 100644
index 00000000..440d7df1
--- /dev/null
+++ b/chrome/content/dkim.js
@@ -0,0 +1,978 @@
+/*
+ * dkim.js - DKIM Verifier Extension for Mozilla Thunderbird
+ *
+ * Verifies the DKIM-Signatures as specified in RFC 6376
+ * http://tools.ietf.org/html/rfc6376
+ *
+ * version: 0.1 (13 May 2013)
+ *
+ * Copyright (c) 2010-2013 Philippe Lieser
+ *
+ * This software is licensed under the terms of the MIT License.
+ *
+ * The above copyright and license notice shall be 
+ * included in all copies or substantial portions of the Software.
+ */
+
+/*
+ * Depends (directly) on:
+ *  - dns.js
+ *  - rsa.js
+ *  - rsasign-1.2.js
+ *  - base64.js
+ *  - asn1hex-1.1.js
+ */
+ 
+/*
+ * Violations against RFC 6376:
+ * ============================
+ *  - validate tag list as specified in Section 3.2 of RFC 6376
+ *    - Tags with duplicate names MUST NOT occur within a single tag-list;
+ *      if a tag name does occur more than once, the entire tag-list is invalid
+ *  - at the moment, only a subset of valid Local-part in the i-Tag is recognised
+ *  - no test for multiple key records in an DNS RRset (Section 3.6.2.2)
+ *  - key record flags are ignored (Section 3.6.1)
+ *  - no check that from header is signed
+ *  - Multiple Instances of a header Field are not supported (Section 5.4.2)
+ *  - message with bad signature is treated differently from a message with no signature
+ *    (result is shown) (Section 6.1)
+ *  - DNS Server not reachable is treated as a PERMFAIL, not as a TEMPFAIL (Section 6.1.2)
+ *  - no check that hash declared in DKIM-Signature is included in the hashs 
+ *    declared in the key record (Section 6.1.2)
+ *  - check that the hash function in the public key is the same as in the header (Section 6.1.2)
+ *
+ * possible feature  additions
+ * ===========================
+ *  - read and write Authentication-Results header (http://tools.ietf.org/html/rfc5451)
+ *  - at the moment, the message must be in "network normal" format (more in Section 5.3 of RFC 6376);
+ *    there is no check that this applies
+ *  - option to show all signed header fields
+ *  - at the moment, no differentiation between missing or ill-formed tags
+ *  - support multiple signatures (more in Section 4 of RFC 6376)
+ *  - differentiation between DNS errors
+ *  - make verifying non blocking
+ *  - and support concurrent verifications
+ *  - display warnings
+ *  - warning if the Signature is expired
+ *  - warning if the Signature is in the future
+ *  - warning if SDID and from are different
+ *  - support multiple key records
+ *
+ */
+
+/*
+ * DKIM Verifier module
+ * public me
+ */
+var DKIMVerifier = (function() {
+	// set hash funktions used by rsasign-1.2.js
+	_RSASIGN_HASHHEXFUNC['sha1'] = function(s){return dkim_hash(s, "sha1", "hex");};
+	_RSASIGN_HASHHEXFUNC['sha256'] = function(s){return dkim_hash(s, "sha256", "hex");};
+
+/*
+ * private variables
+ */
+	// preferences
+
+	// DKIM debug on/off
+	var prefDKIMDebug = true;
+	
+	// all warnings about the signature will go in her
+	var warnings = [];
+
+	// WSP help pattern as specified in Section 2.8 of RFC 6376
+	var pattWSP = "[ \t]";
+	// FWS help pattern as specified in Section 2.8 of RFC 6376
+	var pattFWS = "(?:" + pattWSP + "*(?:\r\n)?" + pattWSP + "+)";
+	// Pattern for hyphenated-word as specified in Section 2.10 of RFC 6376
+	var hyphenated_word = "(?:[A-z](?:[A-z0-9-]*[A-z0-9])?)";
+	// Pattern for ALPHADIGITPS as specified in Section 2.10 of RFC 6376
+	var ALPHADIGITPS = "[A-z0-9+/]";
+	// Pattern for base64string as specified in Section 2.10 of RFC 6376
+	var base64string = "(?:"+ALPHADIGITPS+"(?:"+pattFWS+"?"+ALPHADIGITPS+")*(?:"+pattFWS+"?=){0,2})";
+	// Pattern for dkim-safe-char as specified in Section 2.11 of RFC 6376
+	var dkim_safe_char = "[33-:<>-~]";
+	// Pattern for hex-octet as specified in Section 6.7 of RFC 2045
+	var hex_octet = "(?:=[0-9ABCDEF]{2})";
+	// Pattern for qp-hdr-value as specified in Section 2.10 of RFC 6376
+	// same as dkim-quoted-printable as specified in Section 2.11 of RFC 6376
+	var qp_hdr_value = "(?:"+pattFWS+"|"+hex_octet+"|"+dkim_safe_char+")";
+	// Pattern for field-name as specified in Section 3.6.8 of RFC 5322 without ";"
+	// used as hdr-name in RFC 6376
+	var hdr_name = "(?:[!-9<-~]+)";
+
+/*
+ * private methods
+ */
+
+	/*
+	 * wrapper for hash functions
+	 * hashAlgorithm: "md2", "md5", "sha1", "sha256", "sha384", "sha512"
+	 * outputFormat: "hex", "b64"
+	 * 
+	 * from https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsICryptoHash
+	 */
+	function dkim_hash(str, hashAlgorithm, outputFormat) {
+		var hasher = Components.classes["@mozilla.org/security/hash;1"].
+			createInstance(Components.interfaces.nsICryptoHash);
+		hasher.initWithString(hashAlgorithm);
+
+		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
+			createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+		converter.charset = "iso-8859-1";
+
+		// data is an array of bytes
+		var data = converter.convertToByteArray(str, {});
+		
+		hasher.update(data, data.length);
+		
+		switch (outputFormat) {
+			case "hex":
+				// true for base-64, false for binary data output
+				var hash = hasher.finish(false);
+				
+				// return the two-digit hexadecimal code for a byte
+				function toHexString(charCode)
+				{
+				  return ("0" + charCode.toString(16)).slice(-2);
+				}
+
+				// convert the binary hash data to a hex string.
+				return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+			case "b64":
+				// true for base-64, false for binary data output
+				return hasher.finish(true);
+			default:
+				throw new DKIM_InternalError("unsupported hash output selected");
+
+		}
+	}
+
+	/*
+	 * reads the message and parse it into header and body
+	 * returns msg.headerPlain and msg.bodyPlain
+	 */
+	function parseMsg(msgURI) {
+		var header = "";
+		var c;
+		var body = "";
+		// return value
+		var msg = {};
+
+		// get inputStream for msg
+	//	var messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(Components.interfaces.nsIMessenger);
+		var messageService = messenger.messageServiceFromURI(msgURI);
+		var nsIInputStream = Components.classes["@mozilla.org/network/sync-stream-listener;1"].
+			createInstance(Components.interfaces.nsIInputStream);
+		var inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].
+			createInstance(Components.interfaces.nsIScriptableInputStream);
+		inputStream.init(nsIInputStream);
+		messageService.streamMessage(msgURI, nsIInputStream, msgWindow, null, false, null);
+		
+		// read header
+		while(true) {
+			// read one character
+			c = inputStream.read(1);
+			
+			// if end of line
+			if (c === "\n") {
+				// check for following empty line (end of header)
+				c = inputStream.read(2);
+				header += "\n" + c;
+				if (c === "\r\n") {
+					// empty line found, stop
+					break;
+				}
+			} else {
+				header += c;
+			}
+		}
+		
+		// read body
+		var i;
+		while (i = inputStream.available()) {
+			body = body + inputStream.read(i);
+		}
+		
+		// close inputStream
+		inputStream.close();
+		nsIInputStream.close();
+		
+		msg.headerPlain = header;
+		msg.bodyPlain = body;
+		
+		return msg;
+	}
+
+	/*
+	 * parse the message header
+	 */
+	function parseHeader(header) {
+		var headerFields = {};
+		
+		var temp;
+
+		// split header fields
+		var headerArray = header.split(/\r\n(?=\S)/);
+		var hName;
+		for(var i = 0; i < headerArray.length; i++) {
+			// store fields under header field name (in lower case)
+			hName = headerArray[i].match(/\S+(?=\s*:)/);
+			if (hName !== null) {
+				headerFields[hName[0].toLowerCase()] = headerArray[i]+"\r\n";
+			}
+		}
+		
+		return headerFields;
+	}
+
+	/*
+	 * construct RegExp for finding Tag=Value Pair in tag list as specified in Section 3.2 of RFC 6376
+	 */
+	function tag_spec(tag_name, tag_spec) {
+		return new RegExp("(?:^|;)"+pattFWS+"?"+tag_name+pattFWS+"?="+pattFWS+"?("+tag_spec+")"+pattFWS+"?(?:;|\r\n$|$)"); 
+	}
+
+	/*
+	 * parse the DKIM-Signature header field
+	 * header field is specified in Section 3.5 of RFC 6376
+	 */
+	function parseDKIMSignature(DKIMSignatureHeader) {
+		var DKIMSignature = {
+			v : null, // Version
+			a_sig : null, // signature algorithm (signing part)
+			a_hash : null, // signature algorithm (hashing part)
+			b : null, // signature (unfolded)
+			b_folded : null, // signature (still folded)
+			bh : null, // body hash
+			c_header : null, // canonicalization for header
+			c_body : null, // canonicalization for body
+			d : null, // Signing Domain Identifier (SDID) claiming responsibility
+			h : null, // Signed header fields
+			h_array : [], // array of Signed header fields
+			i : null, // Agent or User Identifier (AUID) on behalf of which the SDID is taking responsibility
+			l : null, // Body length count
+			q : null, // query methods for public key retrievel
+			s : null, // selector
+			t : null, // Signature Timestamp
+			x : null, // Signature Expiration
+			z : null // Copied header fields
+		};
+		
+		// strip DKIM-Signatur header name
+		DKIMSignatureHeader = DKIMSignatureHeader.replace(/^DKIM-Signature[ \t]*:/,"");
+		
+		// get Version (plain-text; REQUIRED)
+		// must be "1"
+		var versionTag = DKIMSignatureHeader.match(tag_spec("v","[0-9]+"));
+		if (versionTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_V);
+		}
+		if (versionTag[1] === "1") {
+			DKIMSignature.v = "1";
+		} else {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_VERSION);
+		}
+
+		// get signature algorithm (plain-text;REQUIRED)
+		// currently only "rsa-sha1" or "rsa-sha256"
+		var sig_a_tag_k = "(rsa|[A-z](?:[A-z]|[0-9])*)";
+		var sig_a_tag_h = "(sha1|sha256|[A-z](?:[A-z]|[0-9])*)";
+		var sig_a_tag_alg = sig_a_tag_k+"-"+sig_a_tag_h;
+		var algorithmTag = DKIMSignatureHeader.match(tag_spec("a",sig_a_tag_alg));
+		if (algorithmTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_A);
+		}
+		if (algorithmTag[1] === "rsa-sha1" || algorithmTag[1] === "rsa-sha256") {
+			DKIMSignature.a_sig = algorithmTag[2];
+			DKIMSignature.a_hash = algorithmTag[3];
+		} else {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_A);
+		}
+
+		// get signature data (base64;REQUIRED)
+		var signatureDataTag = DKIMSignatureHeader.match(tag_spec("b",base64string));
+		if (signatureDataTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_B);
+		}
+		DKIMSignature.b = signatureDataTag[1].replace(new RegExp(pattFWS,"g"), "");
+		DKIMSignature.b_folded = signatureDataTag[1];
+
+		// get body hash (base64;REQUIRED)
+		var bodyHashTag = DKIMSignatureHeader.match(tag_spec("bh",base64string));
+		if (bodyHashTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_BH);
+		}
+		DKIMSignature.bh = bodyHashTag[1].replace(new RegExp(pattFWS,"g"), "");
+
+		// get Message canonicalization (plain-text; OPTIONAL, default is "simple/simple")
+		// currently only "simple" or "relaxed" for both header and body
+		var sig_c_tag_alg = "(simple|relaxed|"+hyphenated_word+")";
+		var msCanonTag = DKIMSignatureHeader.match(tag_spec("c",sig_c_tag_alg+"(?:/"+sig_c_tag_alg+")?"));
+		if (msCanonTag === null) {
+			DKIMSignature.c_header = "simple";
+			DKIMSignature.c_body = "simple";
+		} else {
+			// canonicalization for header
+			if (msCanonTag[2] === "simple" || msCanonTag[2] === "relaxed") {
+				DKIMSignature.c_header = msCanonTag[2];
+			} else {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_C_H);
+			}
+				
+			// canonicalization for body
+			if (msCanonTag[3] === null) {
+				DKIMSignature.c_body = "simple";
+			} else {
+				if (msCanonTag[3] === "simple" || msCanonTag[3] === "relaxed") {
+					DKIMSignature.c_body = msCanonTag[3];
+				} else {
+					throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_C_B);
+				}
+			}
+		}
+		
+		// get SDID (plain-text; REQUIRED)	
+		// Pattern for sub-domain as specified in Section 4.1.2 of RFC 5321
+		var sub_domain = "(?:[A-z0-9](?:[A-z0-9-]*[A-z0-9])?)";
+		var domain_name = "(?:"+sub_domain+"(?:."+sub_domain+")+)";
+		var SDIDTag = DKIMSignatureHeader.match(tag_spec("d",sub_domain+"(?:."+sub_domain+")*"));
+		if (SDIDTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_D);
+		}
+		DKIMSignature.d = SDIDTag[1];
+
+		// get Signed header fields (plain-text, but see description; REQUIRED)
+		var sig_h_tag = "("+hdr_name+")"+"(?:"+pattFWS+"?:"+pattFWS+"?"+hdr_name+")*";
+		var signedHeadersTag = DKIMSignatureHeader.match(tag_spec("h",sig_h_tag));
+		if (signedHeadersTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_H);
+		}
+		DKIMSignature.h = signedHeadersTag[1].replace(new RegExp(pattFWS,"g"), "");
+		/*
+		// get captured header field names and store them in lower case in an array
+		var i = 2;
+		while (signedHeadersTag[i] !== undefined) {
+			DKIMSignature.h_array.push(signedHeadersTag[i].toLowerCase());
+			i++;
+		}
+		*/
+		// get the header field names and store them in lower case in an array
+		var regExpHeaderName = new RegExp(pattFWS+"?("+hdr_name+")"+pattFWS+"?(?::|$)", "g");
+		while (true) {
+			var tmp = regExpHeaderName.exec(signedHeadersTag[1]);
+			if (tmp === null) {
+				break;
+			} else {
+				DKIMSignature.h_array.push(tmp[1].toLowerCase());
+			}
+		}
+
+		// get AUID (dkim-quoted-printable; OPTIONAL, default is an empty local-part
+		// followed by an "@" followed by the domain from the "d=" tag)
+		// The domain part of the address MUST be the same as, or a subdomain of,
+		// the value of the "d=" tag
+		/*
+		RFC 5321 (the one specified to be used in RFC 5322)
+		========
+		
+		Local-part = Dot-string / Quoted-string
+		Dot-string = Atom *("." Atom)
+		Atom = 1*atext
+		Quoted-string = DQUOTE *QcontentSMTP DQUOTE
+		QcontentSMTP = qtextSMTP / quoted-pairSMTP
+		quoted-pairSMTP = %d92 %d32-126
+		qtextSMTP = %d32-33 / %d35-91 / %d93-126
+		
+		RFC 5322
+		========
+		
+		local-part = dot-atom / quoted-string / obs-local-part
+		quoted-string = [CFWS]
+						DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+						[CFWS]
+		dot-atom = [CFWS] dot-atom-text [CFWS]
+		atext = ALPHA / DIGIT / ; Printable US-ASCII
+				"!" / "#" / ; characters not including
+				"$" / "%" / ; specials. Used for atoms.
+				"&" / "’" /
+				"*" / "+" /
+				"-" / "/" /
+				"=" / "?" /
+				"^" / "_" /
+				"‘" / "{" /
+				"|" / "}" /
+				"~"
+		DQUOTE, ASCII value 34
+		qcontent = qtext / quoted-pair
+		qtext = %d33 / ; Printable US-ASCII
+				%d35-91 / ; characters not including
+				%d93-126 / ; "\" or the quote character
+				obs-qtext	obs-local-part = word *("." word)
+		word = atom / quoted-string
+		atom = [CFWS] 1*atext [CFWS]
+		CFWS = (1*([FWS] comment) [FWS]) / FWS
+		quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
+		VCHAR          =  %x21-7E ; visible (printing) characters
+		obs-qtext = obs-NO-WS-CTL
+		obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
+						%d11 / ; characters that do not
+						%d12 / ; include the carriage
+						%d14-31 / ; return, line feed, and
+						%d127 ; white space characters
+						
+		about RegEx and valid mail addresses:
+		http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address
+		*/
+		
+		var atext = "[A-z0-9!#$%&'*+/=?^_`{|}~-]";
+		var local_part = "(?:"+atext+"(?:."+atext+")*)";
+		var sig_i_tag = local_part+"?@"+domain_name;
+		var AUIDTag = DKIMSignatureHeader.match(tag_spec("i", sig_i_tag));
+		if (AUIDTag === null) {
+			DKIMSignature.i = "@"+DKIMSignature.d;
+		} else {
+			if (!(new RegExp(DKIMSignature.d+"$")).test(AUIDTag[1])) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_DOMAIN_I);
+			}
+			DKIMSignature.i = AUIDTag[1];
+		}
+
+		// get Body length count (plain-text unsigned decimal integer; OPTIONAL, default is entire body)
+		var BodyLengthTag = DKIMSignatureHeader.match(tag_spec("l", "[0-9]{1,76}"));
+		if (BodyLengthTag !== null) {
+			DKIMSignature.l = parseInt(BodyLengthTag[1], 10);
+		}
+
+		// get query methods (plain-text; OPTIONAL, default is "dns/txt")
+		var sig_q_tag_method = "(?:dns/txt|"+hyphenated_word+"(?:/"+qp_hdr_value+")?)";
+		var sig_q_tag = sig_q_tag_method+"(?:"+pattFWS+"?:"+pattFWS+"?"+sig_q_tag_method+")*";
+		var QueryMetTag = DKIMSignatureHeader.match(tag_spec("q", "[0-9]{1,76}"));
+		if (QueryMetTag === null) {
+			DKIMSignature.q = "dns/txt";
+		} else {
+			if (!(new RegExp("dns/txt")).test(QueryMetTag[1])) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_Q);
+			}
+			DKIMSignature.q = "dns/txt";
+		}
+
+		// get selector subdividing the namespace for the "d=" (domain) tag (plain-text; REQUIRED)
+		var SelectorTag = DKIMSignatureHeader.match(tag_spec("s", sub_domain+"(?:."+sub_domain+")*"));
+		if (SelectorTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_MISSING_S);
+		}
+		DKIMSignature.s = SelectorTag[1];
+		
+		// get Signature Timestamp (plain-text unsigned decimal integer; RECOMMENDED,
+		// default is an unknown creation time)
+		var SigTimeTag = DKIMSignatureHeader.match(tag_spec("t", "[0-9]+"));
+		if (SigTimeTag !== null) {
+			DKIMSignature.t = parseInt(SigTimeTag[1], 10);
+		}
+
+		// get Signature Expiration (plain-text unsigned decimal integer; 
+		// RECOMMENDED, default is no expiration)
+		// The value of the "x=" tag MUST be greater than the value of the "t=" tag if both are present
+		var ExpTimeTag = DKIMSignatureHeader.match(tag_spec("x", "[0-9]+"));
+		if (ExpTimeTag !== null) {
+			DKIMSignature.x = parseInt(ExpTimeTag[1], 10);
+			if (DKIMSignature.t !== null && DKIMSignature.x < DKIMSignature.t) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_TIMESTAMPS);
+			}
+		}
+		
+		// get Copied header fields (dkim-quoted-printable, but see description; OPTIONAL, default is null)
+		var sig_z_tag_copy = hdr_name+pattFWS+"?:"+qp_hdr_value;
+		var sig_z_tag = sig_z_tag_copy+"(\\|"+pattFWS+"?"+sig_z_tag_copy+")*";
+		var CopyHeaderFieldsTag = DKIMSignatureHeader.match(tag_spec("z", sig_z_tag));
+		if (CopyHeaderFieldsTag !== null) {
+			DKIMSignature.z = CopyHeaderFieldsTag[1].replace(new RegExp(pattFWS,"g"), "");
+		}
+		
+		return DKIMSignature;
+	}
+
+	/*
+	 * parse the DKIM key record
+	 * key record is specified in Section 3.6.1 of RFC 6376
+	 */
+	function parseDKIMKeyRecord(DKIMKeyRecord) {
+		var DKIMKey = {
+			v : null, // Version
+			h : null, // hash algorithms
+			k : null, // key type
+			n : null, // notes
+			p : null, // Public-key data
+			s : null, // Service Type
+			t : null // flags
+		}
+		
+		// get version (plain-text; RECOMMENDED, default is "DKIM1")
+		// If specified, this tag MUST be set to "DKIM1"
+		// This tag MUST be the first tag in the record
+		var key_v_tag = "^"+pattFWS+"?v"+pattFWS+"?="+pattFWS+"?("+dkim_safe_char+")*;";
+		var versionTag = DKIMKeyRecord.match(new RegExp(key_v_tag));
+		if (versionTag === null || versionTag[1] === "DKIM1") {
+			DKIMKey.v = "DKIM1";
+		} else {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEY_INVALID_V);
+		}
+		
+		// get Acceptable hash algorithms (plain-text; OPTIONAL, defaults toallowing all algorithms)
+		var key_h_tag_alg = "(?:sha1|sha256|"+hyphenated_word+")";
+		var key_h_tag = key_h_tag_alg+"(?:"+pattFWS+"?:"+pattFWS+"?"+key_h_tag_alg+")*";
+		var algorithmTag = DKIMKeyRecord.match(tag_spec("h",key_h_tag));
+		if (algorithmTag !== null) {
+			DKIMKey.h = algorithmTag[1];
+		} 
+		
+		// get Key type (plain-text; OPTIONAL, default is "rsa")
+		var key_k_tag_type = "(?:rsa|"+hyphenated_word+")";
+		var keyTypeTag = DKIMKeyRecord.match(tag_spec("k",key_k_tag_type));
+		if (keyTypeTag === null || keyTypeTag[1] === "rsa") {
+			DKIMKey.k = "rsa";
+		} else {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEY_UNKNOWN_K);
+		}
+
+		// get Notes (qp-section; OPTIONAL, default is empty)
+		var notesTag = DKIMKeyRecord.match(tag_spec("n",dkim_safe_char+"*"));
+		if (notesTag !== null) {
+			DKIMKey.n = notesTag[1];
+		}
+		
+		// get Public-key data (base64; REQUIRED)
+		// empty value means that this public key has been revoked
+		var keyTag = DKIMKeyRecord.match(tag_spec("p",base64string+"?"));
+		if (keyTag === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEY_MISSING_P);
+		} else {
+			if (keyTag[1] === "") {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEY_REVOKED);
+			} else {
+				DKIMKey.p = keyTag[1];
+			}
+		}
+
+		// get Service Type (plain-text; OPTIONAL; default is "*")
+		var key_s_tag_type = "(?:email|\\*|"+hyphenated_word+")";
+		var key_s_tag = key_s_tag_type+"(?:"+pattFWS+"?:"+pattFWS+"?"+key_s_tag_type+")*";
+		var serviceTypeTag = DKIMKeyRecord.match(tag_spec("s",key_s_tag));
+		if (serviceTypeTag === null) {
+			DKIMKey.s = "*";
+		} else {
+			if (/email/.test(serviceTypeTag[1])) {
+				DKIMKey.s = serviceTypeTag[1];
+			} else {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEY_NOTEMAILKEY);
+			}
+		}
+
+		// get Flags (plaintext; OPTIONAL, default is no flags set)
+		var key_t_tag_flag = "(?:y|s|"+hyphenated_word+")";
+		var key_t_tag = key_t_tag_flag+"(?:"+pattFWS+"?:"+pattFWS+"?"+key_t_tag_flag+")*";
+		var flagsTag = DKIMKeyRecord.match(tag_spec("t",key_t_tag));
+		if (flagsTag !== null) {
+			DKIMKey.t = flagsTag[1];
+		} else {
+			DKIMKey.t = "";
+		}
+		
+		return DKIMKey;
+	}
+
+	/*
+	 * canonicalize a single header field using the relaxed algorithm
+	 * specified in Section 3.4.2 of RFC 6376
+	 */
+	function CanonicalizationHeaderFieldRelaxed(headerField) {
+		// Convert header field name (not the header field values) to lowercase
+		headerField = headerField.replace(
+			/^\S[^:]*/,
+			function(match) {
+				return match.toLowerCase();
+			}
+		);
+
+		// Unfold header field continuation lines
+		headerField = headerField.replace(/\r\n[ \t]/g," ");
+		
+		// Convert all sequences of one or more WSP characters to a single SP character.
+		// WSP characters here include those before and after a line folding boundary.
+		headerField = headerField.replace(/[ \t]+/g," ");
+		
+		// Delete all WSP characters at the end of each unfolded header field value.
+		headerField = headerField.replace(/[ \t]+\r\n/,"\r\n");
+		
+		// Delete any WSP characters remaining before and after the colon 
+		// separating the header field name from the header field value.
+		// The colon separator MUST be retained.
+		headerField = headerField.replace(/[ \t]*:[ \t]*/,":");
+
+		return headerField;
+	}
+
+	/*
+	 * canonicalize the body using the simple algorithm
+	 * specified in Section 3.4.3 of RFC 6376
+	 */
+	function CanonicalizationBodySimple(body) {
+		// Ignore all empty lines at the end of the message body
+		// If there is no body or no trailing CRLF on the message body, a CRLF is added
+		body = body.replace(/(\r\n)+$/,"\r\n");
+		
+		return body;
+	}
+
+	/*
+	 * canonicalize the body using the relaxed algorithm
+	 * specified in Section 3.4.4 of RFC 6376
+	 */
+	function CanonicalizationBodyRelaxed(body) {
+		// noch change for empty body
+		if (body == "")
+			return body;
+
+		// Ignore all whitespace at the end of lines
+		body = body.replace(/[ \t]+\r\n/g,"\r\n");
+		// Reduce all sequences of WSP within a line to a single SP character
+		body = body.replace(/[ \t]+/g," ");
+		
+		// Ignore all empty lines at the end of the message body
+		// If the body is non-empty but does not end with a CRLF, a CRLF is added
+		body = body.replace(/(\r\n)+$/,"\r\n");
+		
+		return body;
+	}
+
+	/*
+	 * Computing the Message Hash for the body 
+	 * specified in Section 3.7 of RFC 6376
+	 */
+	function computeBodyHash(msg) {
+		// canonicalize body
+		var bodyCanon;
+		switch (msg.DKIMSignature.c_body) {
+			case "simple":
+				bodyCanon = CanonicalizationBodySimple(msg.bodyPlain);
+				break;
+			case "relaxed":
+				bodyCanon = CanonicalizationBodyRelaxed(msg.bodyPlain);
+				break;
+			default:
+				throw new DKIM_InternalError("unsupported canonicalization algorithm got parsed");
+		}
+
+		// truncated body to the length specified in the "l=" tag
+		if (msg.DKIMSignature.l !== null) {
+			bodyCanon = bodyCanon.subst(0, msg.DKIMSignature.l);
+		}
+		
+		// compute body hash
+		var bodyHash;
+		switch (msg.DKIMSignature.a_hash) {
+			case "sha1":
+				bodyHash = dkim_hash(bodyCanon, "sha1", "b64");
+				break;
+			case "sha256":
+				bodyHash = dkim_hash(bodyCanon, "sha256", "b64");
+				break;
+			default:
+				throw new DKIM_InternalError("unsupported hash algorithm (body) got parsed");
+		}
+
+		return bodyHash;
+	}
+	
+	/*
+	 * Computing the input for the header Hash
+	 * specified in Section 3.7 of RFC 6376
+	 */
+	function computeHeaderHashInput(msg) {
+		var hashInput = "";
+		var temp;
+
+		// set header canonicalization algorithm
+		var headerCanonAlgo;
+		switch (msg.DKIMSignature.c_header) {
+			case "simple":
+				headerCanonAlgo = function (headerField) {return headerField;};
+				break;
+			case "relaxed":
+				headerCanonAlgo = CanonicalizationHeaderFieldRelaxed;
+				break;
+			default:
+				throw new DKIM_InternalError("unsupported canonicalization algorithm (header) got parsed");
+		}
+		
+		// get header fields specified by the "h=" tag
+		// and join their canonicalized form
+		for(var i = 0; i <  msg.DKIMSignature.h_array.length; i++) {
+			temp = msg.headerFields[msg.DKIMSignature.h_array[i]];
+			if (temp) {
+				hashInput += headerCanonAlgo(temp);
+			}
+		}
+		
+		// add DKIM-Signature header to the hash input
+		// with the value of the "b=" tag (including all surrounding whitespace) deleted
+		var pos = msg.headerFields["dkim-signature"].indexOf(msg.DKIMSignature.b_folded);
+		var tempBegin = msg.headerFields["dkim-signature"].substr(0, pos);
+		tempBegin = tempBegin.replace(new RegExp(pattFWS+"?$"), "");
+		var tempEnd = msg.headerFields["dkim-signature"].substr(pos+msg.DKIMSignature.b_folded.length);
+		tempEnd = tempEnd.replace(new RegExp("^"+pattFWS+"?"), "");
+		temp = tempBegin + tempEnd;
+		// canonicalized using the header canonicalization algorithm specified in the "c=" tag
+		temp = headerCanonAlgo(temp);
+		// without a trailing CRLF
+		hashInput += temp.substr(0, temp.length - 2);
+		
+		return hashInput;
+	}
+	
+	/*
+	 * 1. part of verifying the signature
+	 * will verify until key query, the rest is in verifySignaturePart2
+	 */
+	function verifySignaturePart1(msg) {
+		try {
+			// parse the DKIMSignatureHeader
+			msg.DKIMSignature = parseDKIMSignature(msg.headerFields["dkim-signature"]);
+			DKIM_Debug("Parsed DKIM-Signature: "+msg.DKIMSignature.toSource());
+			
+			// check the value of the body lenght tag
+			if (msg.DKIMSignature.l !== null) {
+				if (msg.DKIMSignature.l > msg.body.length) {
+					// lenght tag exceeds body size
+					throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_TOOLARGE_L);
+				} else if (msg.DKIMSignature.l < msg.body.length){
+					// lenght tag smaller when body size
+					warnings.push(DKIM_STRINGS.DKIM_SIGWARNING_SMALL_L);
+				}
+			}
+			
+			// Compute the Message Hashe for the body 
+			var bodyHash = computeBodyHash(msg);
+			DKIM_Debug("computed body hash: "+bodyHash);
+			
+			// compare body hash
+			if (bodyHash !== msg.DKIMSignature.bh) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_CORRUPT_BH);
+			}
+
+			// get the DKIM key
+			// this function will continue the verification
+			queryDNS(
+				msg.DKIMSignature.s+"._domainkey."+msg.DKIMSignature.d,
+				"TXT",
+				that.dnsCallback,
+				msg
+			);
+		} catch(e) {
+			var dkimMsgHdrRes = document.getElementById("dkim_verifier_msgHdrRes");
+			if (e instanceof DKIM_SigError) {				
+				dkimMsgHdrRes.value = DKIM_STRINGS.PERMFAIL + " (" + e.message + ")";
+			} else {
+				dkimMsgHdrRes.value = "Internal Error";
+			}
+			if (prefDKIMDebug) {
+				Components.utils.reportError(e+"\n"+e.stack);
+			}
+		}
+	}
+	
+	/*
+	 * 2. part of verifying the signature
+	 * will continue verifying after key is received
+	 */
+	function verifySignaturePart2(msg) {
+		try {
+			msg.DKIMKey = parseDKIMKeyRecord(msg.keyQueryResult);
+			DKIM_Debug("Parsed DKIM-Key: "+msg.DKIMKey.toSource());
+			
+			// Compute the input for the header hash
+			var headerHashInput = computeHeaderHashInput(msg);
+			DKIM_Debug("Header hash inputP: " + headerHashInput);
+
+			// get RSA-key
+			/*
+			the rsa key must be in the following ASN.1 DER format
+				
+			SEQUENCE(2 elem) -- our posTopArray
+				SEQUENCE(2 elem)
+					OBJECT IDENTIFIER 1.2.840.113549.1.1.1 (Comment: PKCS #1; Description: rsaEncryption)
+					NULL
+				BIT STRING(1 elem)
+					SEQUENCE(2 elem) -- our posKeyArray
+						INTEGER (modulus)
+						INTEGER (publicExponent)
+			*/
+			var asnKey = b64tohex(msg.DKIMKey.p);
+			var posTopArray = null;
+			var posKeyArray = null;
+			
+			// check format by comparing the 1. child in the top element
+			posTopArray = ASN1HEX.getPosArrayOfChildren_AtObj(asnKey,0)
+			if (posTopArray === null || posTopArray.length !== 2) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEYDECODE);
+			}
+			if (ASN1HEX.getHexOfTLV_AtObj(asnKey, posTopArray[0]) !==
+				"300d06092a864886f70d0101010500") {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEYDECODE);
+			}
+			
+			// get pos of SEQUENCE under BIT STRING
+			// asn1hex does not support BIT STRING, so we will compute the position
+			var pos = ASN1HEX.getStartPosOfV_AtObj(asnKey, posTopArray[1]) + 2;
+			
+			// get pos of modulus and publicExponent
+			posKeyArray = ASN1HEX.getPosArrayOfChildren_AtObj(asnKey, pos);
+			if (posKeyArray === null || posKeyArray.length !== 2) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEYDECODE);
+			}
+			
+			// get modulus
+			var m_hex = ASN1HEX.getHexOfV_AtObj(asnKey,posKeyArray[0]);
+			// get public exponent
+			var e_hex = ASN1HEX.getHexOfV_AtObj(asnKey,posKeyArray[1]);
+
+			// set RSA-key
+			var rsa = new RSAKey();
+			rsa.setPublic(m_hex, e_hex);
+			
+			// verify Signature
+			var isValid = rsa.verifyString(headerHashInput, b64tohex(msg.DKIMSignature.b));
+			
+			if (!isValid) {
+				throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_CORRUPT_B);
+			}
+			
+			// show result
+			var dkimMsgHdrRes = document.getElementById("dkim_verifier_msgHdrRes");
+			dkimMsgHdrRes.value = DKIM_STRINGS.SUCCESS(msg.DKIMSignature.d);
+			
+		} catch(e) {
+			var dkimMsgHdrRes = document.getElementById("dkim_verifier_msgHdrRes");
+			if (e instanceof DKIM_SigError) {				
+				dkimMsgHdrRes.value = DKIM_STRINGS.PERMFAIL + " (" + e.message + ")";
+			} else {
+				dkimMsgHdrRes.value = "Internal Error";
+			}
+			
+			if (prefDKIMDebug) {
+				Components.utils.reportError(e+"\n"+e.stack);
+			}
+		}
+	}
+
+	/*
+	 * DKIM_SIGERROR
+	 */
+	function DKIM_SigError(message) {
+		this.name = DKIM_STRINGS.DKIM_SIGERROR;
+		this.message = message || DKIM_STRINGS.DKIM_SIGERROR_DEFAULT;
+
+		// modify stack and lineNumber, to show where this object was created,
+		// not where Error() was
+		var err = new Error();
+		this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
+		this.lineNumber = parseInt(this.stack.match(/[^:]*$/m), 10);
+	}
+	DKIM_SigError.prototype = new Error();
+	DKIM_SigError.prototype.constructor = DKIM_SigError;
+
+	/*
+	 * DKIM internal error
+	 */
+	function DKIM_InternalError(message) {
+		this.name = DKIM_STRINGS.DKIM_INTERNALERROR;
+		this.message = message || DKIM_STRINGS.DKIM_INTERNALERROR_DEFAULT;
+		
+		// modify stack and lineNumber, to show where this object was created,
+		// not where Error() was
+		var err = new Error();
+		this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
+		this.lineNumber = parseInt(this.stack.match(/[^:]*$/m), 10);
+	}
+	DKIM_InternalError.prototype = new Error();
+	DKIM_InternalError.prototype.constructor = DKIM_InternalError;
+
+	/*
+	 * DKIM_Debug
+	 */
+	function DKIM_Debug(message) {
+		if (prefDKIMDebug) {
+			Application.console.log("DKIM: "+message);
+		}
+	}
+	
+var that = {
+/*
+ * public methods/variables
+ */
+ 
+	/*
+	 * get called if a new message ist viewed
+	 */
+	messageLoaded : function () {	
+		// get msg uri
+		var msgURI = gDBView.URIForFirstSelectedMessage ;
+		
+		// parse msg into msg.header and msg.body
+		var msg = parseMsg(msgURI);
+		msg.msgURI = msgURI
+		
+		// parse the header
+		msg.headerFields = parseHeader(msg.headerPlain);
+
+		// check if DKIMSignatureHeader exist
+		if (msg.headerFields["dkim-signature"] === undefined) {
+			// no signature to check, return
+			return;
+		}
+		
+		// show the dkim verifier header box
+		var dkimVerifierBox = document.getElementById("dkim_verifier_msgHdrBox");
+		dkimVerifierBox.collapsed = false;
+		
+		verifySignaturePart1(msg);
+	},
+
+	/*
+	 * collapse the dkim verifier header box
+	 */
+	clearHeader : function () {
+		var dkimMsgHdrBox = document.getElementById("dkim_verifier_msgHdrBox");
+		dkimMsgHdrBox.collapsed = "true";
+		var dkimMsgHdrRes = document.getElementById("dkim_verifier_msgHdrRes");
+		dkimMsgHdrRes.value = DKIM_STRINGS.loading;
+	},
+	
+	/*
+	 * callback for the dns result
+	 * the message to be verified is passed as the 2. parameter
+	 */
+	dnsCallback : function (dnsResult, msg) {
+		DKIM_Debug("DNS result: " + dnsResult);
+		if (dnsResult === null) {
+			throw new DKIM_SigError(DKIM_STRINGS.DKIM_SIGERROR_KEYFAIL);
+		}
+		
+		msg.keyQueryResult = dnsResult[0]
+		
+		verifySignaturePart2(msg)
+	}
+}
+return that;
+}()); // the parens here cause the anonymous function to execute and return
+
+// add event listener for message display
+gMessageListeners.push({
+//  onStartHeaders: function () {},
+//  onEndHeaders: function () {},
+//  onEndAttachments: function () {},
+//  onBeforeShowHeaderPane: function () {}
+	onStartHeaders: DKIMVerifier.clearHeader,
+	onEndHeaders: DKIMVerifier.messageLoaded
+});
diff --git a/chrome/content/dns.js b/chrome/content/dns.js
new file mode 100644
index 00000000..14779c04
--- /dev/null
+++ b/chrome/content/dns.js
@@ -0,0 +1,553 @@
+/*
+ * DNS LIBRARY IN JAVASCRIPT
+ *
+ * Copyright 2005 Joshua Tauberer <http://razor.occams.info>
+ *
+ * Feel free to use this file however you want, but
+ * credit would be nice.
+ *
+ * A major limitation of this library is that Mozilla
+ * only provides TCP sockets, and DNS servers sometimes
+ * only respond on UDP. Especially public DNS servers
+ * that have the authoritative information for their
+ * domain. In that case, we really need a "local" server
+ * that responds to TCP.
+ *
+ * We have a few options.  First, we could try the public
+ * DNS system starting at one of the root servers of the
+ * world and hope the name servers on the path to the final
+ * answer all respond to TCP. For that, the root name server
+ * could be, e.g., J.ROOT-SERVERS.NET.
+ *
+ * Or we can just go with Google's new public DNS service
+ * at 8.8.8.8 or 8.8.4.4 which responds on TCP. Good solution!
+ *
+ * The next option is to ask the user for a local name server
+ * that responds to TCP. Some routers cause trouble for this.
+ * It would be nice to have a local server so that it caches
+ * nicely. The user can override the otherwise default root
+ * server by setting the dns.nameserver option.
+ *
+ * We can also try to auto-detect the user's local name server.
+ * The Windows registry and the file /etc/resolv.conf (on Linux)
+ * can be scanned for a DNS server to use.
+ */
+
+var DNS_ROOT_NAME_SERVER = "8.8.8.8"; // This is Google Public DNS. Could be "J.ROOT-SERVERS.NET", but public DNS may not respond to TCP. 
+var DNS_FOUND_NAME_SERVER_AUTOMATICALLY = 0;
+
+// Any settings changes aren't going to be picked up later.
+DNS_LoadPrefs();
+
+function DNS_LoadPrefs() {
+	var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+
+	if (prefs.getPrefType("dns.nameserver") == prefs.PREF_STRING
+		&& prefs.getCharPref("dns.nameserver") != null && prefs.getCharPref("dns.nameserver") != "" && prefs.getCharPref("dns.nameserver") != "occams.info:9053") {
+		DNS_ROOT_NAME_SERVER = prefs.getCharPref("dns.nameserver");
+		//DNS_Log("DNS: Got server from user preference: " + DNS_ROOT_NAME_SERVER);
+	} else if (false) {
+		// Try getting a nameserver from /etc/resolv.conf.
+		
+		// No need to do this while Google Public DNS is running.
+		
+		try {
+			var resolvconf = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
+			resolvconf.initWithPath("/etc/resolv.conf");
+			
+			var stream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance();
+			var stream_filestream = stream.QueryInterface(Components.interfaces.nsIFileInputStream);
+			stream_filestream.init(resolvconf, 0, 0, 0); // don't know what the flags are...
+			
+			var stream_reader = stream.QueryInterface(Components.interfaces.nsILineInputStream);
+			
+			var out_line = Object();
+			while (stream_reader.readLine(out_line)) {
+				if (DNS_StartsWith(out_line.value, "nameserver ")) {
+					DNS_ROOT_NAME_SERVER = out_line.value.substring("nameserver ".length);
+					DNS_FOUND_NAME_SERVER_AUTOMATICALLY = 1;
+					break;
+				}
+			}
+			
+			stream_filestream.close();
+			
+			//DNS_Log("DNS: Got server from resolv.conf: " + DNS_ROOT_NAME_SERVER);
+		} catch (e) {
+			//DNS_Log("DNS: Reading resolv.conf: " + e);
+		}
+		
+		// Try getting a nameserver from the windows registry
+		try {
+			var registry_class = Components.classes["@mozilla.org/windows-registry-key;1"];
+			if (registry_class != null) {
+			var registry_object = registry_class.createInstance();
+			var registry = registry_object.QueryInterface(Components.interfaces.nsIWindowsRegKey);
+			
+			registry.open(registry.ROOT_KEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", registry.ACCESS_QUERY_VALUE);
+			var ns = "";
+			if (registry.hasValue("DhcpNameServer")) ns = registry.readStringValue("DhcpNameServer");
+			if (ns == "" && registry.hasValue("NameServer")) ns = registry.readStringValue("NameServer");
+			registry.close();
+			
+			if (ns != "") {
+				var servers = ns.split(' ');
+				if (servers.length > 0 && servers[0] != "") {
+					DNS_ROOT_NAME_SERVER = servers[0];
+					DNS_FOUND_NAME_SERVER_AUTOMATICALLY = 1;
+					//DNS_Log("DNS: Got server from Windows registry: " + DNS_ROOT_NAME_SERVER);
+				}
+			}
+			}
+		} catch (e) {
+			//DNS_Log("DNS: Reading Registry: " + e);
+		}
+		
+		//DNS_Log("DNS: Autoconfigured server: " + DNS_ROOT_NAME_SERVER);
+	}
+}
+
+var dns_test_domains = Array("for.net", "www.for.net", "yy.for.net", "www.gmail.net");
+var dns_test_domidx = 0;
+//DNS_Test();
+function DNS_Test() {
+	queryDNS(dns_test_domains[dns_test_domidx], "MX",
+		function(data) {
+			var str;
+			var i;
+			if (data == null) { str = "no data"; }
+			else {
+				for (i = 0; i < data.length; i++) {
+					if (data[i].host != null)
+						data[i] = "host=" + data[i].host + ";address=" + data[i].address;
+					
+					if (str != null) str += ", "; else str = "";
+					str += data[i];
+				}
+			}
+			
+			alert(dns_test_domains[dns_test_domidx] + " => " + str);
+			dns_test_domidx++;
+			DNS_Test();
+		} );
+}
+
+//queryDNS("www.example.com", "A", function(data) { alert(data); } );
+//reverseDNS("123.456.789.123", function(addrs) { for (var i = 0; i < addrs.length; i++) { alert(addrs[i]); } } );
+
+// queryDNS: This is the main entry point for external callers.
+function queryDNS(host, recordtype, callback, callbackdata) {
+	queryDNSRecursive(DNS_ROOT_NAME_SERVER, host, recordtype, callback, callbackdata, 0);
+}
+
+function reverseDNS(ip, callback, callbackdata) {
+	// Get a list of reverse-DNS hostnames,
+	// and then make sure that each hostname
+	// resolves to the original IP.
+	
+	queryDNS(DNS_ReverseIPHostname(ip), "PTR",
+		function(hostnames, mydata, queryError) {
+			// No reverse DNS info available.
+			if (hostnames == null) { callback(null, callbackdata, queryError); return; }
+			
+			var obj = new Object();
+			obj.ret = Array(0);
+			obj.retctr = 0;
+			obj.resolvectr = 0;
+			
+			var i;
+			
+			// Check that each one resolves forward.
+			for (i = 0; i < hostnames.length; i++) {
+				var o2 = new Object();
+				o2.retobj = obj;
+				o2.curhostname = hostnames[i];
+				
+				queryDNS(hostnames[i], "A",
+				function(arecs, cb) {
+					if (arecs != null) {
+						var j;
+						var matched = false;
+						for (j = 0; j < arecs.length; j++) {
+							if (arecs[j] == ip) { matched = true; break; }
+						}
+					}
+					
+					if (matched)
+						cb.retobj.ret[cb.retobj.retctr++] = cb.curhostname;
+					
+					if (++cb.retobj.resolvectr == hostnames.length) {
+						if (cb.retobj.retctr == 0)
+							callback(null, callbackdata);
+						else
+							callback(cb.retobj.ret, callbackdata);
+					}
+				}, o2);
+			}
+		});
+}
+
+function DNS_ReverseIPHostname(ip) {
+	var q = ip.split(".");
+	return q[3] + "." + q[2] + "." + q[1] + "." + q[0] + ".in-addr.arpa";
+}
+
+function queryDNSRecursive(server, host, recordtype, callback, callbackdata, hops) {
+	if (hops == 10) {
+		DNS_Debug("DNS: Maximum number of recursive steps taken in resolving " + host);
+		callback(null, callbackdata, DNS_STRINGS.TOO_MANY_HOPS);
+		return;
+	}
+	
+	DNS_Debug("DNS: Resolving " + host + " " + recordtype + " by querying " + server);
+		
+	var query =
+		// HEADER
+		  "00" // ID
+		+ String.fromCharCode(1) // QR=0, OPCODE=0, AA=0, TC=0, RD=1 (Recursion desired)
+		+ String.fromCharCode(0) // all zeroes
+		+ DNS_wordToStr(1) // 1 query
+		+ DNS_wordToStr(0) // ASCOUNT=0
+		+ DNS_wordToStr(0) // NSCOUNT=0
+		+ DNS_wordToStr(0) // ARCOUNT=0
+		;
+		
+	var hostparts = host.split(".");
+	for (var hostpartidx = 0; hostpartidx < hostparts.length; hostpartidx++)
+		query += DNS_octetToStr(hostparts[hostpartidx].length) + hostparts[hostpartidx];
+	query += DNS_octetToStr(0);
+	if (recordtype == "A")
+		query += DNS_wordToStr(1);
+	else if (recordtype == "NS")
+		query += DNS_wordToStr(2); 
+	else if (recordtype == "CNAME")
+		query += DNS_wordToStr(5); 
+	else if (recordtype == "PTR")
+		query += DNS_wordToStr(12); 
+	else if (recordtype == "MX")
+		query += DNS_wordToStr(15); 
+	else if (recordtype == "TXT")
+		query += DNS_wordToStr(16); 
+	else
+		throw "Invalid record type.";
+	query += DNS_wordToStr(1); // IN
+		
+	// Prepend query message length
+	query = DNS_wordToStr(query.length) + query;
+	
+	var listener = {
+		msgsize : null,
+		readcount : 0,
+		responseHeader : "",
+		responseBody : "",
+		done : false,
+		finished : function(data, status) {
+			if (status != 0) {
+				if (status == 2152398861) {
+					DNS_Debug("DNS: Resolving " + host + "/" + recordtype + ": DNS server " + server + " refused a TCP connection.");
+					callback(null, callbackdata, DNS_STRINGS.CONNECTION_REFUSED(server));
+				} else if (status == 2152398868) {
+					DNS_Debug("DNS: Resolving " + host + "/" + recordtype + ": DNS server " + server + " timed out on a TCP connection.");
+					callback(null, callbackdata, DNS_STRINGS.TIMED_OUT(server));
+				} else {
+					DNS_Debug("DNS: Resolving " + host + "/" + recordtype + ": Failed to connect to DNS server " + server + " with error code " + status + ".");
+					callback(null, callbackdata, DNS_STRINGS.SERVER_ERROR(server));
+				}
+				return;
+			}
+			
+			this.process(data);
+			if (!this.done) {
+				DNS_Debug("DNS: Resolving " + host + "/" + recordtype + ": Response was incomplete.");
+				callback(null, callbackdata, DNS_STRINGS.INCOMPLETE_RESPONSE(server));
+			}
+		},
+		process : function(data){
+			if (this.done) return false;
+			
+			this.readcount += data.length;
+			
+			while (this.responseHeader.length < 14 && data.length > 0) {
+				this.responseHeader += data.charAt(0);
+				data = data.substr(1);
+			}
+			if (this.responseHeader.length == 14) {
+				this.msgsize = DNS_strToWord(this.responseHeader.substr(0, 2));
+				this.responseBody += data;
+
+				//DNS_Debug("DNS: Received Reply: " + (this.readcount-2) + " of " + this.msgsize + " bytes");
+
+				if (this.readcount >= this.msgsize+2) {
+					this.responseHeader = this.responseHeader.substr(2); // chop the length field
+					this.done = true;
+					DNS_getRDData(this.responseHeader + this.responseBody, server, host, recordtype, callback, callbackdata, hops);
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+	
+	// allow server to be either a hostname or hostname:port
+	var server_hostname = server;
+	var port = 53;
+	if (server.indexOf(':') != -1) {
+		server_hostname = server.substring(0, server.indexOf(':'));
+		port = server.substring(server.indexOf(':')+1);
+	}
+
+	var ex = DNS_readAllFromSocket(server_hostname, port, query, listener);
+	if (ex != null) {
+	  alert(ex);
+	}
+}
+
+function DNS_readDomain(ctx) {
+	var domainname = "";
+	var ctr = 20;
+	while (ctr-- > 0) {
+		var l = ctx.str.charCodeAt(ctx.idx++);
+		if (l == 0) break;
+		
+		if (domainname != "") domainname += ".";
+		
+		if ((l >> 6) == 3) {
+			// Pointer
+			var ptr = ((l & 63) << 8) + ctx.str.charCodeAt(ctx.idx++);
+			var ctx2 = { str : ctx.str, idx : ptr };
+			domainname += DNS_readDomain(ctx2);
+			break;
+		} else {
+			domainname += ctx.str.substr(ctx.idx, l);
+			ctx.idx += l;
+		}
+	}
+	return domainname;
+}
+
+function DNS_readRec(ctx) {
+	var rec = new Object();
+	var ctr;
+	var txtlen;
+	
+	rec.dom = DNS_readDomain(ctx);
+	rec.type = DNS_strToWord(ctx.str.substr(ctx.idx, 2)); ctx.idx += 2;
+	rec.cls = DNS_strToWord(ctx.str.substr(ctx.idx, 2)); ctx.idx += 2;
+	rec.ttl = DNS_strToWord(ctx.str.substr(ctx.idx, 2)); ctx.idx += 4; // 32bit
+	rec.rdlen = DNS_strToWord(ctx.str.substr(ctx.idx, 2)); ctx.idx += 2;
+	rec.recognized = 1;
+	
+	var ctxnextidx = ctx.idx + rec.rdlen;
+	
+	if (rec.type == 16) {
+		rec.type = "TXT";
+		rec.rddata = "";
+		ctr = 10;
+		while (rec.rdlen > 0 && ctr-- > 0) {
+			txtlen = DNS_strToOctet(ctx.str.substr(ctx.idx,1)); ctx.idx++; rec.rdlen--;
+			rec.rddata += ctx.str.substr(ctx.idx, txtlen); ctx.idx += txtlen; rec.rdlen -= txtlen;
+		}
+	} else if (rec.type == 1) {
+		// Return as a dotted-quad
+		rec.type = "A";
+		rec.rddata = ctx.str.substr(ctx.idx, rec.rdlen);
+		rec.rddata = rec.rddata.charCodeAt(0) + "." + rec.rddata.charCodeAt(1) + "." + rec.rddata.charCodeAt(2) + "." + rec.rddata.charCodeAt(3);
+	} else if (rec.type == 15) {
+		rec.type = "MX";
+		rec.rddata = new Object();
+		rec.rddata.preference = DNS_strToWord(ctx.str.substr(ctx.idx,2)); ctx.idx += 2;
+		rec.rddata.host = DNS_readDomain(ctx);
+	} else if (rec.type == 2) {
+		rec.type = "NS";
+		rec.rddata = DNS_readDomain(ctx);
+	} else if (rec.type == 12) {
+		rec.type = "PTR";
+		rec.rddata = DNS_readDomain(ctx);
+	} else {
+		rec.recognized = 0;
+	}
+	
+	ctx.idx = ctxnextidx;
+	
+	return rec;
+}
+
+function DNS_getRDData(str, server, host, recordtype, callback, callbackdata, hops) {
+	var qcount = DNS_strToWord(str.substr(4, 2));
+	var ancount = DNS_strToWord(str.substr(6, 2));
+	var aucount = DNS_strToWord(str.substr(8, 2));
+	var adcount = DNS_strToWord(str.substr(10, 2));
+	
+	var ctx = { str : str, idx : 12 };
+	
+	var i;
+	var j;
+	var dom;
+	var type;
+	var cls;
+	var ttl;
+	var rec;
+	
+	if (qcount != 1) throw "Invalid response: Question section didn't have exactly one record.";
+	if (ancount > 128) throw "Invalid response: Answer section had more than 128 records.";
+	if (aucount > 128) throw "Invalid response: Authority section had more than 128 records.";
+	if (adcount > 128) throw "Invalid response: Additional section had more than 128 records.";
+	
+	for (i = 0; i < qcount; i++) {
+		dom = DNS_readDomain(ctx);
+		type = DNS_strToWord(str.substr(ctx.idx, 2)); ctx.idx += 2;
+		cls = DNS_strToWord(str.substr(ctx.idx, 2)); ctx.idx += 2;
+	}
+	
+	var debugstr = "DNS: " + host + "/" + recordtype + ": ";
+	
+	var results = Array(ancount);
+	for (i = 0; i < ancount; i++) {
+		rec = DNS_readRec(ctx);
+		if (!rec.recognized) throw "Record type is not one that this library can understand.";
+		results[i] = rec.rddata;		
+		DNS_Debug(debugstr + "Answer: " + rec.rddata);
+	}
+
+	var authorities = Array(aucount);
+	for (i = 0; i < aucount; i++) {
+		rec = DNS_readRec(ctx);
+		authorities[i] = rec;
+		if (rec.recognized)
+			DNS_Debug(debugstr + "Authority: " + rec.type + " " + rec.rddata);
+		// Assuming the domain for this record is the domain we are asking about.
+	}
+	
+	for (i = 0; i < adcount; i++) {
+		rec = DNS_readRec(ctx);
+		if (rec.recognized)
+			DNS_Debug(debugstr + "Additional: " + rec.dom + " " + rec.type + " " + rec.rddata);
+		if (rec.type == "A") {
+			for (j = 0; j < results.length; j++) {
+				if (results[j].host && results[j].host == rec.dom) {
+					if (results[j].address == null) results[j].address = Array(0);
+					results[j].address[results[j].address.length] = rec.rddata;
+				}
+			}
+		}
+	}
+	
+	if (results.length > 0) {
+		// We have an answer.
+		callback(results, callbackdata);
+		
+	} else {
+		// No answer.  If there is an NS authority, recurse.
+		// Note that we can do without the IP address of the NS authority (given in the additional
+		// section) because we're able to do DNS lookups without knowing the IP address
+		// of the DNS server -- Thunderbird and the OS take care of that.
+		for (var i = 0; i < aucount; i++) {
+			if (authorities[i].type == "NS" && authorities[i].rddata != server) {
+				DNS_Debug(debugstr + "Recursing on Authority: " + authorities[i].rddata);
+				queryDNSRecursive(authorities[i].rddata, host, recordtype, callback, callbackdata, hops+1);
+				return;
+			}
+		}
+
+		// No authority was able to help us.
+		DNS_Debug(debugstr + "No answer, no authority to recurse on.  DNS lookup failed.");
+		callback(null, callbackdata);
+	}
+}
+
+function DNS_strToWord(str) {
+	return str.charCodeAt(1) + (str.charCodeAt(0) << 8);
+}
+
+function DNS_strToOctet(str) {
+	return str.charCodeAt(0);
+}
+
+function DNS_wordToStr(word) {
+	return DNS_octetToStr((word >> 8) % 256) + DNS_octetToStr(word % 256);
+}
+
+function DNS_octetToStr(octet) {
+	return String.fromCharCode(octet);
+}
+
+// This comes from http://xulplanet.com/tutorials/mozsdk/sockets.php
+
+function DNS_readAllFromSocket(host,port,outputData,listener)
+{
+  try {
+    var transportService =
+      Components.classes["@mozilla.org/network/socket-transport-service;1"]
+        .getService(Components.interfaces.nsISocketTransportService);
+		
+    var transport = transportService.createTransport(null,0,host,port,null);
+
+    var outstream = transport.openOutputStream(0,0,0);
+    outstream.write(outputData,outputData.length);
+
+    var stream = transport.openInputStream(0,0,0);
+    var instream = Components.classes["@mozilla.org/binaryinputstream;1"]
+      .createInstance(Components.interfaces.nsIBinaryInputStream);
+    instream.setInputStream(stream);
+
+    var dataListener = {
+		data : "",
+		onStartRequest: function(request, context){},
+		onStopRequest: function(request, context, status){
+			if (listener.finished != null) {
+				listener.finished(this.data, status);
+			}
+			outstream.close();
+			stream.close();
+			//DNS_Debug("DNS: Connection closed (" + host + ")");
+		},
+		onDataAvailable: function(request, context, inputStream, offset, count){
+			//DNS_Debug("DNS: Got data (" + host + ")");
+			for (var i = 0; i < count; i++) {
+			  this.data += String.fromCharCode(instream.read8());
+			}
+			if (listener.process != null) {
+				if (!listener.process(this.data)) {
+					outstream.close();
+					stream.close();
+				}
+				this.data = "";
+			}
+      }
+    };
+	
+    var pump = Components.
+      classes["@mozilla.org/network/input-stream-pump;1"].
+        createInstance(Components.interfaces.nsIInputStreamPump);
+    pump.init(stream, -1, -1, 0, 0, false);
+    pump.asyncRead(dataListener,null);
+  } catch (ex) {
+    return ex;
+  }
+  return null;
+}
+
+function DNS_Debug(message) {
+	if (false) {
+		DNS_Log(message);
+	}
+}
+
+function DNS_Log(message) {
+	var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
+	consoleService.logStringMessage(message);
+}
+
+function DNS_StartsWith(a, b) {
+	if (b.length > a.length) return false;
+	return a.substring(0, b.length) == b;
+}
+
+function DNS_IsDottedQuad(ip) {
+	var q = ip.split(".");
+	if (q.length != 4)
+		return false;
+	if (isNaN(parseInt(q[0])) || isNaN(parseInt(q[1])) || isNaN(parseInt(q[2])) || isNaN(parseInt(q[3])))
+		return false;
+	return true;
+}
diff --git a/chrome/content/jsbn.js b/chrome/content/jsbn.js
new file mode 100644
index 00000000..40bb9e2b
--- /dev/null
+++ b/chrome/content/jsbn.js
@@ -0,0 +1,559 @@
+// Copyright (c) 2005  Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+  if(a != null)
+    if("number" == typeof a) this.fromNumber(a,b,c);
+    else if(b == null && "string" != typeof a) this.fromString(a,256);
+    else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+  while(--n >= 0) {
+    var v = x*this[i++]+w[j]+c;
+    c = Math.floor(v/0x4000000);
+    w[j++] = v&0x3ffffff;
+  }
+  return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+  var xl = x&0x7fff, xh = x>>15;
+  while(--n >= 0) {
+    var l = this[i]&0x7fff;
+    var h = this[i++]>>15;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);
+    c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+    w[j++] = l&0x3fffffff;
+  }
+  return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+  var xl = x&0x3fff, xh = x>>14;
+  while(--n >= 0) {
+    var l = this[i]&0x3fff;
+    var h = this[i++]>>14;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x3fff)<<14)+w[j]+c;
+    c = (l>>28)+(m>>14)+xh*h;
+    w[j++] = l&0xfffffff;
+  }
+  return c;
+}
+if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+  BigInteger.prototype.am = am2;
+  dbits = 30;
+}
+else if(j_lm && (navigator.appName != "Netscape")) {
+  BigInteger.prototype.am = am1;
+  dbits = 26;
+}
+else { // Mozilla/Netscape seems to prefer am3
+  BigInteger.prototype.am = am3;
+  dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+  var c = BI_RC[s.charCodeAt(i)];
+  return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+  for(var i = this.t-1; i >= 0; --i) r[i] = this[i];
+  r.t = this.t;
+  r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+  this.t = 1;
+  this.s = (x<0)?-1:0;
+  if(x > 0) this[0] = x;
+  else if(x < -1) this[0] = x+DV;
+  else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 256) k = 8; // byte array
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else { this.fromRadix(s,b); return; }
+  this.t = 0;
+  this.s = 0;
+  var i = s.length, mi = false, sh = 0;
+  while(--i >= 0) {
+    var x = (k==8)?s[i]&0xff:intAt(s,i);
+    if(x < 0) {
+      if(s.charAt(i) == "-") mi = true;
+      continue;
+    }
+    mi = false;
+    if(sh == 0)
+      this[this.t++] = x;
+    else if(sh+k > this.DB) {
+      this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+      this[this.t++] = (x>>(this.DB-sh));
+    }
+    else
+      this[this.t-1] |= x<<sh;
+    sh += k;
+    if(sh >= this.DB) sh -= this.DB;
+  }
+  if(k == 8 && (s[0]&0x80) != 0) {
+    this.s = -1;
+    if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+  }
+  this.clamp();
+  if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+  var c = this.s&this.DM;
+  while(this.t > 0 && this[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+  if(this.s < 0) return "-"+this.negate().toString(b);
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else return this.toRadix(b);
+  var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+  var p = this.DB-(i*this.DB)%k;
+  if(i-- > 0) {
+    if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+    while(i >= 0) {
+      if(p < k) {
+        d = (this[i]&((1<<p)-1))<<(k-p);
+        d |= this[--i]>>(p+=this.DB-k);
+      }
+      else {
+        d = (this[i]>>(p-=k))&km;
+        if(p <= 0) { p += this.DB; --i; }
+      }
+      if(d > 0) m = true;
+      if(m) r += int2char(d);
+    }
+  }
+  return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+  var r = this.s-a.s;
+  if(r != 0) return r;
+  var i = this.t;
+  r = i-a.t;
+  if(r != 0) return (this.s<0)?-r:r;
+  while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+  return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+  var r = 1, t;
+  if((t=x>>>16) != 0) { x = t; r += 16; }
+  if((t=x>>8) != 0) { x = t; r += 8; }
+  if((t=x>>4) != 0) { x = t; r += 4; }
+  if((t=x>>2) != 0) { x = t; r += 2; }
+  if((t=x>>1) != 0) { x = t; r += 1; }
+  return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+  if(this.t <= 0) return 0;
+  return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+  var i;
+  for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+  for(i = n-1; i >= 0; --i) r[i] = 0;
+  r.t = this.t+n;
+  r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+  for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+  r.t = Math.max(this.t-n,0);
+  r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<cbs)-1;
+  var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+  for(i = this.t-1; i >= 0; --i) {
+    r[i+ds+1] = (this[i]>>cbs)|c;
+    c = (this[i]&bm)<<bs;
+  }
+  for(i = ds-1; i >= 0; --i) r[i] = 0;
+  r[ds] = c;
+  r.t = this.t+ds+1;
+  r.s = this.s;
+  r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+  r.s = this.s;
+  var ds = Math.floor(n/this.DB);
+  if(ds >= this.t) { r.t = 0; return; }
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<bs)-1;
+  r[0] = this[ds]>>bs;
+  for(var i = ds+1; i < this.t; ++i) {
+    r[i-ds-1] |= (this[i]&bm)<<cbs;
+    r[i-ds] = this[i]>>bs;
+  }
+  if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs;
+  r.t = this.t-ds;
+  r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+  var i = 0, c = 0, m = Math.min(a.t,this.t);
+  while(i < m) {
+    c += this[i]-a[i];
+    r[i++] = c&this.DM;
+    c >>= this.DB;
+  }
+  if(a.t < this.t) {
+    c -= a.s;
+    while(i < this.t) {
+      c += this[i];
+      r[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += this.s;
+  }
+  else {
+    c += this.s;
+    while(i < a.t) {
+      c -= a[i];
+      r[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c -= a.s;
+  }
+  r.s = (c<0)?-1:0;
+  if(c < -1) r[i++] = this.DV+c;
+  else if(c > 0) r[i++] = c;
+  r.t = i;
+  r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+  var x = this.abs(), y = a.abs();
+  var i = x.t;
+  r.t = i+y.t;
+  while(--i >= 0) r[i] = 0;
+  for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+  r.s = 0;
+  r.clamp();
+  if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+  var x = this.abs();
+  var i = r.t = 2*x.t;
+  while(--i >= 0) r[i] = 0;
+  for(i = 0; i < x.t-1; ++i) {
+    var c = x.am(i,x[i],r,2*i,0,1);
+    if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+      r[i+x.t] -= x.DV;
+      r[i+x.t+1] = 1;
+    }
+  }
+  if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+  r.s = 0;
+  r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m.  q or r may be null.
+function bnpDivRemTo(m,q,r) {
+  var pm = m.abs();
+  if(pm.t <= 0) return;
+  var pt = this.abs();
+  if(pt.t < pm.t) {
+    if(q != null) q.fromInt(0);
+    if(r != null) this.copyTo(r);
+    return;
+  }
+  if(r == null) r = nbi();
+  var y = nbi(), ts = this.s, ms = m.s;
+  var nsh = this.DB-nbits(pm[pm.t-1]);	// normalize modulus
+  if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+  else { pm.copyTo(y); pt.copyTo(r); }
+  var ys = y.t;
+  var y0 = y[ys-1];
+  if(y0 == 0) return;
+  var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0);
+  var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+  var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+  y.dlShiftTo(j,t);
+  if(r.compareTo(t) >= 0) {
+    r[r.t++] = 1;
+    r.subTo(t,r);
+  }
+  BigInteger.ONE.dlShiftTo(ys,t);
+  t.subTo(y,y);	// "negative" y so we can replace sub with am later
+  while(y.t < ys) y[y.t++] = 0;
+  while(--j >= 0) {
+    // Estimate quotient digit
+    var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+    if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) {	// Try it out
+      y.dlShiftTo(j,t);
+      r.subTo(t,r);
+      while(r[i] < --qd) r.subTo(t,r);
+    }
+  }
+  if(q != null) {
+    r.drShiftTo(ys,q);
+    if(ts != ms) BigInteger.ZERO.subTo(q,q);
+  }
+  r.t = ys;
+  r.clamp();
+  if(nsh > 0) r.rShiftTo(nsh,r);	// Denormalize remainder
+  if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+  var r = nbi();
+  this.abs().divRemTo(a,null,r);
+  if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+  return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+  if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+  else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+//         xy == 1 (mod m)
+//         xy =  1+km
+//   xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+  if(this.t < 1) return 0;
+  var x = this[0];
+  if((x&1) == 0) return 0;
+  var y = x&3;		// y == 1/x mod 2^2
+  y = (y*(2-(x&0xf)*y))&0xf;	// y == 1/x mod 2^4
+  y = (y*(2-(x&0xff)*y))&0xff;	// y == 1/x mod 2^8
+  y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff;	// y == 1/x mod 2^16
+  // last step - calculate inverse mod DV directly;
+  // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+  y = (y*(2-x*y%this.DV))%this.DV;		// y == 1/x mod 2^dbits
+  // we really want the negative inverse, and -DV < y < DV
+  return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+  this.m = m;
+  this.mp = m.invDigit();
+  this.mpl = this.mp&0x7fff;
+  this.mph = this.mp>>15;
+  this.um = (1<<(m.DB-15))-1;
+  this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+  var r = nbi();
+  x.abs().dlShiftTo(this.m.t,r);
+  r.divRemTo(this.m,null,r);
+  if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+  return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+  var r = nbi();
+  x.copyTo(r);
+  this.reduce(r);
+  return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+  while(x.t <= this.mt2)	// pad x so am has enough room later
+    x[x.t++] = 0;
+  for(var i = 0; i < this.m.t; ++i) {
+    // faster way of calculating u0 = x[i]*mp mod DV
+    var j = x[i]&0x7fff;
+    var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+    // use am to combine the multiply-shift-add into one call
+    j = i+this.m.t;
+    x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+    // propagate carry
+    while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+  }
+  x.clamp();
+  x.drShiftTo(this.m.t,x);
+  if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+  if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+  var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+  g.copyTo(r);
+  while(--i >= 0) {
+    z.sqrTo(r,r2);
+    if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+    else { var t = r; r = r2; r2 = t; }
+  }
+  return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+  var z;
+  if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+  return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
diff --git a/chrome/content/jsbn2.js b/chrome/content/jsbn2.js
new file mode 100644
index 00000000..5b2b725c
--- /dev/null
+++ b/chrome/content/jsbn2.js
@@ -0,0 +1,656 @@
+// Copyright (c) 2005-2009  Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Extended JavaScript BN functions, required for RSA private ops.
+
+// Version 1.1: new BigInteger("0", 10) returns "proper" zero
+// Version 1.2: square() API, isProbablePrime fix
+
+// (public)
+function bnClone() { var r = nbi(); this.copyTo(r); return r; }
+
+// (public) return value as integer
+function bnIntValue() {
+  if(this.s < 0) {
+    if(this.t == 1) return this[0]-this.DV;
+    else if(this.t == 0) return -1;
+  }
+  else if(this.t == 1) return this[0];
+  else if(this.t == 0) return 0;
+  // assumes 16 < DB < 32
+  return ((this[1]&((1<<(32-this.DB))-1))<<this.DB)|this[0];
+}
+
+// (public) return value as byte
+function bnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; }
+
+// (public) return value as short (assumes DB>=16)
+function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; }
+
+// (protected) return x s.t. r^x < DV
+function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); }
+
+// (public) 0 if this == 0, 1 if this > 0
+function bnSigNum() {
+  if(this.s < 0) return -1;
+  else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0;
+  else return 1;
+}
+
+// (protected) convert to radix string
+function bnpToRadix(b) {
+  if(b == null) b = 10;
+  if(this.signum() == 0 || b < 2 || b > 36) return "0";
+  var cs = this.chunkSize(b);
+  var a = Math.pow(b,cs);
+  var d = nbv(a), y = nbi(), z = nbi(), r = "";
+  this.divRemTo(d,y,z);
+  while(y.signum() > 0) {
+    r = (a+z.intValue()).toString(b).substr(1) + r;
+    y.divRemTo(d,y,z);
+  }
+  return z.intValue().toString(b) + r;
+}
+
+// (protected) convert from radix string
+function bnpFromRadix(s,b) {
+  this.fromInt(0);
+  if(b == null) b = 10;
+  var cs = this.chunkSize(b);
+  var d = Math.pow(b,cs), mi = false, j = 0, w = 0;
+  for(var i = 0; i < s.length; ++i) {
+    var x = intAt(s,i);
+    if(x < 0) {
+      if(s.charAt(i) == "-" && this.signum() == 0) mi = true;
+      continue;
+    }
+    w = b*w+x;
+    if(++j >= cs) {
+      this.dMultiply(d);
+      this.dAddOffset(w,0);
+      j = 0;
+      w = 0;
+    }
+  }
+  if(j > 0) {
+    this.dMultiply(Math.pow(b,j));
+    this.dAddOffset(w,0);
+  }
+  if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) alternate constructor
+function bnpFromNumber(a,b,c) {
+  if("number" == typeof b) {
+    // new BigInteger(int,int,RNG)
+    if(a < 2) this.fromInt(1);
+    else {
+      this.fromNumber(a,c);
+      if(!this.testBit(a-1))	// force MSB set
+        this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
+      if(this.isEven()) this.dAddOffset(1,0); // force odd
+      while(!this.isProbablePrime(b)) {
+        this.dAddOffset(2,0);
+        if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this);
+      }
+    }
+  }
+  else {
+    // new BigInteger(int,RNG)
+    var x = new Array(), t = a&7;
+    x.length = (a>>3)+1;
+    b.nextBytes(x);
+    if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
+    this.fromString(x,256);
+  }
+}
+
+// (public) convert to bigendian byte array
+function bnToByteArray() {
+  var i = this.t, r = new Array();
+  r[0] = this.s;
+  var p = this.DB-(i*this.DB)%8, d, k = 0;
+  if(i-- > 0) {
+    if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p)
+      r[k++] = d|(this.s<<(this.DB-p));
+    while(i >= 0) {
+      if(p < 8) {
+        d = (this[i]&((1<<p)-1))<<(8-p);
+        d |= this[--i]>>(p+=this.DB-8);
+      }
+      else {
+        d = (this[i]>>(p-=8))&0xff;
+        if(p <= 0) { p += this.DB; --i; }
+      }
+      if((d&0x80) != 0) d |= -256;
+      if(k == 0 && (this.s&0x80) != (d&0x80)) ++k;
+      if(k > 0 || d != this.s) r[k++] = d;
+    }
+  }
+  return r;
+}
+
+function bnEquals(a) { return(this.compareTo(a)==0); }
+function bnMin(a) { return(this.compareTo(a)<0)?this:a; }
+function bnMax(a) { return(this.compareTo(a)>0)?this:a; }
+
+// (protected) r = this op a (bitwise)
+function bnpBitwiseTo(a,op,r) {
+  var i, f, m = Math.min(a.t,this.t);
+  for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]);
+  if(a.t < this.t) {
+    f = a.s&this.DM;
+    for(i = m; i < this.t; ++i) r[i] = op(this[i],f);
+    r.t = this.t;
+  }
+  else {
+    f = this.s&this.DM;
+    for(i = m; i < a.t; ++i) r[i] = op(f,a[i]);
+    r.t = a.t;
+  }
+  r.s = op(this.s,a.s);
+  r.clamp();
+}
+
+// (public) this & a
+function op_and(x,y) { return x&y; }
+function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; }
+
+// (public) this | a
+function op_or(x,y) { return x|y; }
+function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; }
+
+// (public) this ^ a
+function op_xor(x,y) { return x^y; }
+function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; }
+
+// (public) this & ~a
+function op_andnot(x,y) { return x&~y; }
+function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; }
+
+// (public) ~this
+function bnNot() {
+  var r = nbi();
+  for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i];
+  r.t = this.t;
+  r.s = ~this.s;
+  return r;
+}
+
+// (public) this << n
+function bnShiftLeft(n) {
+  var r = nbi();
+  if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r);
+  return r;
+}
+
+// (public) this >> n
+function bnShiftRight(n) {
+  var r = nbi();
+  if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r);
+  return r;
+}
+
+// return index of lowest 1-bit in x, x < 2^31
+function lbit(x) {
+  if(x == 0) return -1;
+  var r = 0;
+  if((x&0xffff) == 0) { x >>= 16; r += 16; }
+  if((x&0xff) == 0) { x >>= 8; r += 8; }
+  if((x&0xf) == 0) { x >>= 4; r += 4; }
+  if((x&3) == 0) { x >>= 2; r += 2; }
+  if((x&1) == 0) ++r;
+  return r;
+}
+
+// (public) returns index of lowest 1-bit (or -1 if none)
+function bnGetLowestSetBit() {
+  for(var i = 0; i < this.t; ++i)
+    if(this[i] != 0) return i*this.DB+lbit(this[i]);
+  if(this.s < 0) return this.t*this.DB;
+  return -1;
+}
+
+// return number of 1 bits in x
+function cbit(x) {
+  var r = 0;
+  while(x != 0) { x &= x-1; ++r; }
+  return r;
+}
+
+// (public) return number of set bits
+function bnBitCount() {
+  var r = 0, x = this.s&this.DM;
+  for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x);
+  return r;
+}
+
+// (public) true iff nth bit is set
+function bnTestBit(n) {
+  var j = Math.floor(n/this.DB);
+  if(j >= this.t) return(this.s!=0);
+  return((this[j]&(1<<(n%this.DB)))!=0);
+}
+
+// (protected) this op (1<<n)
+function bnpChangeBit(n,op) {
+  var r = BigInteger.ONE.shiftLeft(n);
+  this.bitwiseTo(r,op,r);
+  return r;
+}
+
+// (public) this | (1<<n)
+function bnSetBit(n) { return this.changeBit(n,op_or); }
+
+// (public) this & ~(1<<n)
+function bnClearBit(n) { return this.changeBit(n,op_andnot); }
+
+// (public) this ^ (1<<n)
+function bnFlipBit(n) { return this.changeBit(n,op_xor); }
+
+// (protected) r = this + a
+function bnpAddTo(a,r) {
+  var i = 0, c = 0, m = Math.min(a.t,this.t);
+  while(i < m) {
+    c += this[i]+a[i];
+    r[i++] = c&this.DM;
+    c >>= this.DB;
+  }
+  if(a.t < this.t) {
+    c += a.s;
+    while(i < this.t) {
+      c += this[i];
+      r[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += this.s;
+  }
+  else {
+    c += this.s;
+    while(i < a.t) {
+      c += a[i];
+      r[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += a.s;
+  }
+  r.s = (c<0)?-1:0;
+  if(c > 0) r[i++] = c;
+  else if(c < -1) r[i++] = this.DV+c;
+  r.t = i;
+  r.clamp();
+}
+
+// (public) this + a
+function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; }
+
+// (public) this - a
+function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; }
+
+// (public) this * a
+function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; }
+
+// (public) this^2
+function bnSquare() { var r = nbi(); this.squareTo(r); return r; }
+
+// (public) this / a
+function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; }
+
+// (public) this % a
+function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; }
+
+// (public) [this/a,this%a]
+function bnDivideAndRemainder(a) {
+  var q = nbi(), r = nbi();
+  this.divRemTo(a,q,r);
+  return new Array(q,r);
+}
+
+// (protected) this *= n, this >= 0, 1 < n < DV
+function bnpDMultiply(n) {
+  this[this.t] = this.am(0,n-1,this,0,0,this.t);
+  ++this.t;
+  this.clamp();
+}
+
+// (protected) this += n << w words, this >= 0
+function bnpDAddOffset(n,w) {
+  if(n == 0) return;
+  while(this.t <= w) this[this.t++] = 0;
+  this[w] += n;
+  while(this[w] >= this.DV) {
+    this[w] -= this.DV;
+    if(++w >= this.t) this[this.t++] = 0;
+    ++this[w];
+  }
+}
+
+// A "null" reducer
+function NullExp() {}
+function nNop(x) { return x; }
+function nMulTo(x,y,r) { x.multiplyTo(y,r); }
+function nSqrTo(x,r) { x.squareTo(r); }
+
+NullExp.prototype.convert = nNop;
+NullExp.prototype.revert = nNop;
+NullExp.prototype.mulTo = nMulTo;
+NullExp.prototype.sqrTo = nSqrTo;
+
+// (public) this^e
+function bnPow(e) { return this.exp(e,new NullExp()); }
+
+// (protected) r = lower n words of "this * a", a.t <= n
+// "this" should be the larger one if appropriate.
+function bnpMultiplyLowerTo(a,n,r) {
+  var i = Math.min(this.t+a.t,n);
+  r.s = 0; // assumes a,this >= 0
+  r.t = i;
+  while(i > 0) r[--i] = 0;
+  var j;
+  for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t);
+  for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i);
+  r.clamp();
+}
+
+// (protected) r = "this * a" without lower n words, n > 0
+// "this" should be the larger one if appropriate.
+function bnpMultiplyUpperTo(a,n,r) {
+  --n;
+  var i = r.t = this.t+a.t-n;
+  r.s = 0; // assumes a,this >= 0
+  while(--i >= 0) r[i] = 0;
+  for(i = Math.max(n-this.t,0); i < a.t; ++i)
+    r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n);
+  r.clamp();
+  r.drShiftTo(1,r);
+}
+
+// Barrett modular reduction
+function Barrett(m) {
+  // setup Barrett
+  this.r2 = nbi();
+  this.q3 = nbi();
+  BigInteger.ONE.dlShiftTo(2*m.t,this.r2);
+  this.mu = this.r2.divide(m);
+  this.m = m;
+}
+
+function barrettConvert(x) {
+  if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m);
+  else if(x.compareTo(this.m) < 0) return x;
+  else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; }
+}
+
+function barrettRevert(x) { return x; }
+
+// x = x mod m (HAC 14.42)
+function barrettReduce(x) {
+  x.drShiftTo(this.m.t-1,this.r2);
+  if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); }
+  this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);
+  this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);
+  while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1);
+  x.subTo(this.r2,x);
+  while(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = x^2 mod m; x != r
+function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = x*y mod m; x,y != r
+function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Barrett.prototype.convert = barrettConvert;
+Barrett.prototype.revert = barrettRevert;
+Barrett.prototype.reduce = barrettReduce;
+Barrett.prototype.mulTo = barrettMulTo;
+Barrett.prototype.sqrTo = barrettSqrTo;
+
+// (public) this^e % m (HAC 14.85)
+function bnModPow(e,m) {
+  var i = e.bitLength(), k, r = nbv(1), z;
+  if(i <= 0) return r;
+  else if(i < 18) k = 1;
+  else if(i < 48) k = 3;
+  else if(i < 144) k = 4;
+  else if(i < 768) k = 5;
+  else k = 6;
+  if(i < 8)
+    z = new Classic(m);
+  else if(m.isEven())
+    z = new Barrett(m);
+  else
+    z = new Montgomery(m);
+
+  // precomputation
+  var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
+  g[1] = z.convert(this);
+  if(k > 1) {
+    var g2 = nbi();
+    z.sqrTo(g[1],g2);
+    while(n <= km) {
+      g[n] = nbi();
+      z.mulTo(g2,g[n-2],g[n]);
+      n += 2;
+    }
+  }
+
+  var j = e.t-1, w, is1 = true, r2 = nbi(), t;
+  i = nbits(e[j])-1;
+  while(j >= 0) {
+    if(i >= k1) w = (e[j]>>(i-k1))&km;
+    else {
+      w = (e[j]&((1<<(i+1))-1))<<(k1-i);
+      if(j > 0) w |= e[j-1]>>(this.DB+i-k1);
+    }
+
+    n = k;
+    while((w&1) == 0) { w >>= 1; --n; }
+    if((i -= n) < 0) { i += this.DB; --j; }
+    if(is1) {	// ret == 1, don't bother squaring or multiplying it
+      g[w].copyTo(r);
+      is1 = false;
+    }
+    else {
+      while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; }
+      if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; }
+      z.mulTo(r2,g[w],r);
+    }
+
+    while(j >= 0 && (e[j]&(1<<i)) == 0) {
+      z.sqrTo(r,r2); t = r; r = r2; r2 = t;
+      if(--i < 0) { i = this.DB-1; --j; }
+    }
+  }
+  return z.revert(r);
+}
+
+// (public) gcd(this,a) (HAC 14.54)
+function bnGCD(a) {
+  var x = (this.s<0)?this.negate():this.clone();
+  var y = (a.s<0)?a.negate():a.clone();
+  if(x.compareTo(y) < 0) { var t = x; x = y; y = t; }
+  var i = x.getLowestSetBit(), g = y.getLowestSetBit();
+  if(g < 0) return x;
+  if(i < g) g = i;
+  if(g > 0) {
+    x.rShiftTo(g,x);
+    y.rShiftTo(g,y);
+  }
+  while(x.signum() > 0) {
+    if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x);
+    if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y);
+    if(x.compareTo(y) >= 0) {
+      x.subTo(y,x);
+      x.rShiftTo(1,x);
+    }
+    else {
+      y.subTo(x,y);
+      y.rShiftTo(1,y);
+    }
+  }
+  if(g > 0) y.lShiftTo(g,y);
+  return y;
+}
+
+// (protected) this % n, n < 2^26
+function bnpModInt(n) {
+  if(n <= 0) return 0;
+  var d = this.DV%n, r = (this.s<0)?n-1:0;
+  if(this.t > 0)
+    if(d == 0) r = this[0]%n;
+    else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n;
+  return r;
+}
+
+// (public) 1/this % m (HAC 14.61)
+function bnModInverse(m) {
+  var ac = m.isEven();
+  if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
+  var u = m.clone(), v = this.clone();
+  var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);
+  while(u.signum() != 0) {
+    while(u.isEven()) {
+      u.rShiftTo(1,u);
+      if(ac) {
+        if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); }
+        a.rShiftTo(1,a);
+      }
+      else if(!b.isEven()) b.subTo(m,b);
+      b.rShiftTo(1,b);
+    }
+    while(v.isEven()) {
+      v.rShiftTo(1,v);
+      if(ac) {
+        if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); }
+        c.rShiftTo(1,c);
+      }
+      else if(!d.isEven()) d.subTo(m,d);
+      d.rShiftTo(1,d);
+    }
+    if(u.compareTo(v) >= 0) {
+      u.subTo(v,u);
+      if(ac) a.subTo(c,a);
+      b.subTo(d,b);
+    }
+    else {
+      v.subTo(u,v);
+      if(ac) c.subTo(a,c);
+      d.subTo(b,d);
+    }
+  }
+  if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO;
+  if(d.compareTo(m) >= 0) return d.subtract(m);
+  if(d.signum() < 0) d.addTo(m,d); else return d;
+  if(d.signum() < 0) return d.add(m); else return d;
+}
+
+var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997];
+var lplim = (1<<26)/lowprimes[lowprimes.length-1];
+
+// (public) test primality with certainty >= 1-.5^t
+function bnIsProbablePrime(t) {
+  var i, x = this.abs();
+  if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) {
+    for(i = 0; i < lowprimes.length; ++i)
+      if(x[0] == lowprimes[i]) return true;
+    return false;
+  }
+  if(x.isEven()) return false;
+  i = 1;
+  while(i < lowprimes.length) {
+    var m = lowprimes[i], j = i+1;
+    while(j < lowprimes.length && m < lplim) m *= lowprimes[j++];
+    m = x.modInt(m);
+    while(i < j) if(m%lowprimes[i++] == 0) return false;
+  }
+  return x.millerRabin(t);
+}
+
+// (protected) true if probably prime (HAC 4.24, Miller-Rabin)
+function bnpMillerRabin(t) {
+  var n1 = this.subtract(BigInteger.ONE);
+  var k = n1.getLowestSetBit();
+  if(k <= 0) return false;
+  var r = n1.shiftRight(k);
+  t = (t+1)>>1;
+  if(t > lowprimes.length) t = lowprimes.length;
+  var a = nbi();
+  for(var i = 0; i < t; ++i) {
+    //Pick bases at random, instead of starting at 2
+    a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]);
+    var y = a.modPow(r,this);
+    if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) {
+      var j = 1;
+      while(j++ < k && y.compareTo(n1) != 0) {
+        y = y.modPowInt(2,this);
+        if(y.compareTo(BigInteger.ONE) == 0) return false;
+      }
+      if(y.compareTo(n1) != 0) return false;
+    }
+  }
+  return true;
+}
+
+// protected
+BigInteger.prototype.chunkSize = bnpChunkSize;
+BigInteger.prototype.toRadix = bnpToRadix;
+BigInteger.prototype.fromRadix = bnpFromRadix;
+BigInteger.prototype.fromNumber = bnpFromNumber;
+BigInteger.prototype.bitwiseTo = bnpBitwiseTo;
+BigInteger.prototype.changeBit = bnpChangeBit;
+BigInteger.prototype.addTo = bnpAddTo;
+BigInteger.prototype.dMultiply = bnpDMultiply;
+BigInteger.prototype.dAddOffset = bnpDAddOffset;
+BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo;
+BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
+BigInteger.prototype.modInt = bnpModInt;
+BigInteger.prototype.millerRabin = bnpMillerRabin;
+
+// public
+BigInteger.prototype.clone = bnClone;
+BigInteger.prototype.intValue = bnIntValue;
+BigInteger.prototype.byteValue = bnByteValue;
+BigInteger.prototype.shortValue = bnShortValue;
+BigInteger.prototype.signum = bnSigNum;
+BigInteger.prototype.toByteArray = bnToByteArray;
+BigInteger.prototype.equals = bnEquals;
+BigInteger.prototype.min = bnMin;
+BigInteger.prototype.max = bnMax;
+BigInteger.prototype.and = bnAnd;
+BigInteger.prototype.or = bnOr;
+BigInteger.prototype.xor = bnXor;
+BigInteger.prototype.andNot = bnAndNot;
+BigInteger.prototype.not = bnNot;
+BigInteger.prototype.shiftLeft = bnShiftLeft;
+BigInteger.prototype.shiftRight = bnShiftRight;
+BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit;
+BigInteger.prototype.bitCount = bnBitCount;
+BigInteger.prototype.testBit = bnTestBit;
+BigInteger.prototype.setBit = bnSetBit;
+BigInteger.prototype.clearBit = bnClearBit;
+BigInteger.prototype.flipBit = bnFlipBit;
+BigInteger.prototype.add = bnAdd;
+BigInteger.prototype.subtract = bnSubtract;
+BigInteger.prototype.multiply = bnMultiply;
+BigInteger.prototype.divide = bnDivide;
+BigInteger.prototype.remainder = bnRemainder;
+BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder;
+BigInteger.prototype.modPow = bnModPow;
+BigInteger.prototype.modInverse = bnModInverse;
+BigInteger.prototype.pow = bnPow;
+BigInteger.prototype.gcd = bnGCD;
+BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
+
+// JSBN-specific extension
+BigInteger.prototype.square = bnSquare;
+
+// BigInteger interfaces not implemented in jsbn:
+
+// BigInteger(int signum, byte[] magnitude)
+// double doubleValue()
+// float floatValue()
+// int hashCode()
+// long longValue()
+// static BigInteger valueOf(long val)
diff --git a/chrome/content/msgHdrViewOverlay.xul b/chrome/content/msgHdrViewOverlay.xul
new file mode 100644
index 00000000..20737993
--- /dev/null
+++ b/chrome/content/msgHdrViewOverlay.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://dkim_verifier/locale/xulstrings.dtd">
+<overlay id="sample" 
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+         xmlns:html="http://www.w3.org/1999/xhtml">
+
+	<!-- localisation scripts -->
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/locale/dkim.js" />
+	<script type="application/javascript"
+            src="chrome://dkim_verifier/locale/dns.js" />
+
+	<!-- DNS -->
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/dns.js"/>
+	<!-- RSA -->
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/prng4.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/rng.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/jsbn.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/jsbn2.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/base64.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/rsa.js"/>
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/rsasign-1.2.js"/>
+	<!-- ASN.1 -->
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/asn1hex-1.1.js"/>
+
+	<!-- main script -->
+	<script type="application/javascript"
+	        src="chrome://dkim_verifier/content/dkim.js"/>
+
+	<vbox id="expandedHeaderView">
+		<hbox id="dkim_verifier_msgHdrBox" collapsed="true">
+			<label value="&dkim_verifier.header.title;"/>
+			<label id="dkim_verifier_msgHdrRes" value=""/>
+		</hbox>
+	</vbox>
+
+</overlay>
\ No newline at end of file
diff --git a/chrome/content/prng4.js b/chrome/content/prng4.js
new file mode 100644
index 00000000..3034f3f1
--- /dev/null
+++ b/chrome/content/prng4.js
@@ -0,0 +1,45 @@
+// prng4.js - uses Arcfour as a PRNG
+
+function Arcfour() {
+  this.i = 0;
+  this.j = 0;
+  this.S = new Array();
+}
+
+// Initialize arcfour context from key, an array of ints, each from [0..255]
+function ARC4init(key) {
+  var i, j, t;
+  for(i = 0; i < 256; ++i)
+    this.S[i] = i;
+  j = 0;
+  for(i = 0; i < 256; ++i) {
+    j = (j + this.S[i] + key[i % key.length]) & 255;
+    t = this.S[i];
+    this.S[i] = this.S[j];
+    this.S[j] = t;
+  }
+  this.i = 0;
+  this.j = 0;
+}
+
+function ARC4next() {
+  var t;
+  this.i = (this.i + 1) & 255;
+  this.j = (this.j + this.S[this.i]) & 255;
+  t = this.S[this.i];
+  this.S[this.i] = this.S[this.j];
+  this.S[this.j] = t;
+  return this.S[(t + this.S[this.i]) & 255];
+}
+
+Arcfour.prototype.init = ARC4init;
+Arcfour.prototype.next = ARC4next;
+
+// Plug in your RNG constructor here
+function prng_newstate() {
+  return new Arcfour();
+}
+
+// Pool size must be a multiple of 4 and greater than 32.
+// An array of bytes the size of the pool will be passed to init()
+var rng_psize = 256;
diff --git a/chrome/content/rng.js b/chrome/content/rng.js
new file mode 100644
index 00000000..03afc3a9
--- /dev/null
+++ b/chrome/content/rng.js
@@ -0,0 +1,68 @@
+// Random number generator - requires a PRNG backend, e.g. prng4.js
+
+// For best results, put code like
+// <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'>
+// in your main HTML document.
+
+var rng_state;
+var rng_pool;
+var rng_pptr;
+
+// Mix in a 32-bit integer into the pool
+function rng_seed_int(x) {
+  rng_pool[rng_pptr++] ^= x & 255;
+  rng_pool[rng_pptr++] ^= (x >> 8) & 255;
+  rng_pool[rng_pptr++] ^= (x >> 16) & 255;
+  rng_pool[rng_pptr++] ^= (x >> 24) & 255;
+  if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
+}
+
+// Mix in the current time (w/milliseconds) into the pool
+function rng_seed_time() {
+  rng_seed_int(new Date().getTime());
+}
+
+// Initialize the pool with junk if needed.
+if(rng_pool == null) {
+  rng_pool = new Array();
+  rng_pptr = 0;
+  var t;
+  if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
+    // Extract entropy (256 bits) from NS4 RNG if available
+    var z = window.crypto.random(32);
+    for(t = 0; t < z.length; ++t)
+      rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
+  }  
+  while(rng_pptr < rng_psize) {  // extract some randomness from Math.random()
+    t = Math.floor(65536 * Math.random());
+    rng_pool[rng_pptr++] = t >>> 8;
+    rng_pool[rng_pptr++] = t & 255;
+  }
+  rng_pptr = 0;
+  rng_seed_time();
+  //rng_seed_int(window.screenX);
+  //rng_seed_int(window.screenY);
+}
+
+function rng_get_byte() {
+  if(rng_state == null) {
+    rng_seed_time();
+    rng_state = prng_newstate();
+    rng_state.init(rng_pool);
+    for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
+      rng_pool[rng_pptr] = 0;
+    rng_pptr = 0;
+    //rng_pool = null;
+  }
+  // TODO: allow reseeding after first request
+  return rng_state.next();
+}
+
+function rng_get_bytes(ba) {
+  var i;
+  for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
+}
+
+function SecureRandom() {}
+
+SecureRandom.prototype.nextBytes = rng_get_bytes;
diff --git a/chrome/content/rsa.js b/chrome/content/rsa.js
new file mode 100644
index 00000000..9f866403
--- /dev/null
+++ b/chrome/content/rsa.js
@@ -0,0 +1,112 @@
+// Depends on jsbn.js and rng.js
+
+// Version 1.1: support utf-8 encoding in pkcs1pad2
+
+// convert a (hex) string to a bignum object
+function parseBigInt(str,r) {
+  return new BigInteger(str,r);
+}
+
+function linebrk(s,n) {
+  var ret = "";
+  var i = 0;
+  while(i + n < s.length) {
+    ret += s.substring(i,i+n) + "\n";
+    i += n;
+  }
+  return ret + s.substring(i,s.length);
+}
+
+function byte2Hex(b) {
+  if(b < 0x10)
+    return "0" + b.toString(16);
+  else
+    return b.toString(16);
+}
+
+// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
+function pkcs1pad2(s,n) {
+  if(n < s.length + 11) { // TODO: fix for utf-8
+    alert("Message too long for RSA");
+    return null;
+  }
+  var ba = new Array();
+  var i = s.length - 1;
+  while(i >= 0 && n > 0) {
+    var c = s.charCodeAt(i--);
+    if(c < 128) { // encode using utf-8
+      ba[--n] = c;
+    }
+    else if((c > 127) && (c < 2048)) {
+      ba[--n] = (c & 63) | 128;
+      ba[--n] = (c >> 6) | 192;
+    }
+    else {
+      ba[--n] = (c & 63) | 128;
+      ba[--n] = ((c >> 6) & 63) | 128;
+      ba[--n] = (c >> 12) | 224;
+    }
+  }
+  ba[--n] = 0;
+  var rng = new SecureRandom();
+  var x = new Array();
+  while(n > 2) { // random non-zero pad
+    x[0] = 0;
+    while(x[0] == 0) rng.nextBytes(x);
+    ba[--n] = x[0];
+  }
+  ba[--n] = 2;
+  ba[--n] = 0;
+  return new BigInteger(ba);
+}
+
+// "empty" RSA key constructor
+function RSAKey() {
+  this.n = null;
+  this.e = 0;
+  this.d = null;
+  this.p = null;
+  this.q = null;
+  this.dmp1 = null;
+  this.dmq1 = null;
+  this.coeff = null;
+}
+
+// Set the public key fields N and e from hex strings
+function RSASetPublic(N,E) {
+  if(N != null && E != null && N.length > 0 && E.length > 0) {
+    this.n = parseBigInt(N,16);
+    this.e = parseInt(E,16);
+  }
+  else
+    alert("Invalid RSA public key");
+}
+
+// Perform raw public operation on "x": return x^e (mod n)
+function RSADoPublic(x) {
+  return x.modPowInt(this.e, this.n);
+}
+
+// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
+function RSAEncrypt(text) {
+  var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
+  if(m == null) return null;
+  var c = this.doPublic(m);
+  if(c == null) return null;
+  var h = c.toString(16);
+  if((h.length & 1) == 0) return h; else return "0" + h;
+}
+
+// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
+//function RSAEncryptB64(text) {
+//  var h = this.encrypt(text);
+//  if(h) return hex2b64(h); else return null;
+//}
+
+// protected
+RSAKey.prototype.doPublic = RSADoPublic;
+
+// public
+RSAKey.prototype.setPublic = RSASetPublic;
+RSAKey.prototype.encrypt = RSAEncrypt;
+//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
diff --git a/chrome/content/rsasign-1.2.js b/chrome/content/rsasign-1.2.js
new file mode 100644
index 00000000..a2d12b97
--- /dev/null
+++ b/chrome/content/rsasign-1.2.js
@@ -0,0 +1,392 @@
+/*! rsasign-1.2.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license
+ */
+//
+// rsa-sign.js - adding signing functions to RSAKey class.
+//
+//
+// version: 1.2.1 (08 May 2013)
+//
+// Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com)
+//
+// This software is licensed under the terms of the MIT License.
+// http://kjur.github.com/jsrsasign/license/
+//
+// The above copyright and license notice shall be 
+// included in all copies or substantial portions of the Software.
+
+//
+// Depends on:
+//   function sha1.hex(s) of sha1.js
+//   jsbn.js
+//   jsbn2.js
+//   rsa.js
+//   rsa2.js
+//
+
+// keysize / pmstrlen
+//  512 /  128
+// 1024 /  256
+// 2048 /  512
+// 4096 / 1024
+
+/**
+ * @fileOverview
+ * @name rsasign-1.2.js
+ * @author Kenji Urushima kenji.urushima@gmail.com
+ * @version 1.2.1
+ * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a>
+ */
+
+/**
+ * @property {Dictionary} _RSASIGN_DIHEAD
+ * @description Array of head part of hexadecimal DigestInfo value for hash algorithms.
+ * You can add any DigestInfo hash algorith for signing.
+ * See PKCS#1 v2.1 spec (p38).
+ */
+var _RSASIGN_DIHEAD = [];
+_RSASIGN_DIHEAD['sha1'] =      "3021300906052b0e03021a05000414";
+_RSASIGN_DIHEAD['sha256'] =    "3031300d060960864801650304020105000420";
+_RSASIGN_DIHEAD['sha384'] =    "3041300d060960864801650304020205000430";
+_RSASIGN_DIHEAD['sha512'] =    "3051300d060960864801650304020305000440";
+_RSASIGN_DIHEAD['md2'] =       "3020300c06082a864886f70d020205000410";
+_RSASIGN_DIHEAD['md5'] =       "3020300c06082a864886f70d020505000410";
+_RSASIGN_DIHEAD['ripemd160'] = "3021300906052b2403020105000414";
+
+/**
+ * @property {Dictionary} _RSASIGN_HASHHEXFUNC
+ * @description Array of functions which calculate hash and returns it as hexadecimal.
+ * You can add any hash algorithm implementations.
+ */
+var _RSASIGN_HASHHEXFUNC = [];
+_RSASIGN_HASHHEXFUNC['sha1'] =      function(s){return hex_sha1(s);};  // http://pajhome.org.uk/crypt/md5/md5.html
+_RSASIGN_HASHHEXFUNC['sha256'] =    function(s){return hex_sha256(s);} // http://pajhome.org.uk/crypt/md5/md5.html
+_RSASIGN_HASHHEXFUNC['sha512'] =    function(s){return hex_sha512(s);} // http://pajhome.org.uk/crypt/md5/md5.html
+_RSASIGN_HASHHEXFUNC['md5'] =       function(s){return hex_md5(s);};   // http://pajhome.org.uk/crypt/md5/md5.html
+_RSASIGN_HASHHEXFUNC['ripemd160'] = function(s){return hex_rmd160(s);};   // http://pajhome.org.uk/crypt/md5/md5.html
+
+//_RSASIGN_HASHHEXFUNC['sha1'] =   function(s){return sha1.hex(s);}   // http://user1.matsumoto.ne.jp/~goma/js/hash.html
+//_RSASIGN_HASHHEXFUNC['sha256'] = function(s){return sha256.hex;}    // http://user1.matsumoto.ne.jp/~goma/js/hash.html
+
+var _RE_HEXDECONLY = new RegExp("");
+_RE_HEXDECONLY.compile("[^0-9a-f]", "gi");
+
+// ========================================================================
+// Signature Generation
+// ========================================================================
+
+function _rsasign_getHexPaddedDigestInfoForString(s, keySize, hashAlg) {
+    var pmStrLen = keySize / 4;
+    var hashFunc = _RSASIGN_HASHHEXFUNC[hashAlg];
+    var sHashHex = hashFunc(s);
+
+    var sHead = "0001";
+    var sTail = "00" + _RSASIGN_DIHEAD[hashAlg] + sHashHex;
+    var sMid = "";
+    var fLen = pmStrLen - sHead.length - sTail.length;
+    for (var i = 0; i < fLen; i += 2) {
+	sMid += "ff";
+    }
+    sPaddedMessageHex = sHead + sMid + sTail;
+    return sPaddedMessageHex;
+}
+
+function _zeroPaddingOfSignature(hex, bitLength) {
+    var s = "";
+    var nZero = bitLength / 4 - hex.length;
+    for (var i = 0; i < nZero; i++) {
+	s = s + "0";
+    }
+    return s + hex;
+}
+
+/**
+ * sign for a message string with RSA private key.<br/>
+ * @name signString
+ * @memberOf RSAKey#
+ * @function
+ * @param {String} s message string to be signed.
+ * @param {String} hashAlg hash algorithm name for signing.<br/>
+ * @return returns hexadecimal string of signature value.
+ */
+function _rsasign_signString(s, hashAlg) {
+    //alert("this.n.bitLength() = " + this.n.bitLength());
+    var hPM = _rsasign_getHexPaddedDigestInfoForString(s, this.n.bitLength(), hashAlg);
+    var biPaddedMessage = parseBigInt(hPM, 16);
+    var biSign = this.doPrivate(biPaddedMessage);
+    var hexSign = biSign.toString(16);
+    return _zeroPaddingOfSignature(hexSign, this.n.bitLength());
+}
+
+function _rsasign_signStringWithSHA1(s) {
+    return _rsasign_signString.call(this, s, 'sha1');
+}
+
+function _rsasign_signStringWithSHA256(s) {
+    return _rsasign_signString.call(this, s, 'sha256');
+}
+
+// PKCS#1 (PSS) mask generation function
+function pss_mgf1_str(seed, len, hash) {
+    var mask = '', i = 0;
+
+    while (mask.length < len) {
+        mask += hash(seed + String.fromCharCode.apply(String, [
+                (i & 0xff000000) >> 24,
+                (i & 0x00ff0000) >> 16,
+                (i & 0x0000ff00) >> 8,
+                i & 0x000000ff]));
+        i += 1;
+    }
+
+    return mask;
+}
+
+/**
+ * sign for a message string with RSA private key by PKCS#1 PSS signing.<br/>
+ * @name signStringPSS
+ * @memberOf RSAKey#
+ * @function
+ * @param {String} s message string to be signed.
+ * @param {String} hashAlg hash algorithm name for signing.<br/>
+ * @return returns hexadecimal string of signature value.
+ */
+function _rsasign_signStringPSS(s, hashAlg, sLen) {
+    var hashFunc = _RSASIGN_HASHRAWFUNC[hashAlg];
+    var mHash = hashFunc(s);
+    var hLen = mHash.length;
+    var emBits = this.n.bitLength() - 1;
+    var emLen = Math.ceil(emBits / 8);
+    var i;
+
+    if (sLen === -1) {
+        sLen = hLen; // same has hash length
+    } else if ((sLen === -2) || (sLen === undefined)) {
+        sLen = emLen - hLen - 2; // maximum
+    } else if (sLen < -2) {
+        throw "invalid salt length";
+    }
+
+    if (emLen < (hLen + sLen + 2)) {
+        throw "data too long";
+    }
+
+    var salt = '';
+
+    if (sLen > 0) {
+        salt = new Array(sLen);
+        new SecureRandom().nextBytes(salt);
+        salt = String.fromCharCode.apply(String, salt);
+    }
+
+    var H = hashFunc('\x00\x00\x00\x00\x00\x00\x00\x00' + mHash + salt);
+    var PS = [];
+
+    for (i = 0; i < emLen - sLen - hLen - 2; i += 1) {
+        PS[i] = 0x00;
+    }
+
+    var DB = String.fromCharCode.apply(String, PS) + '\x01' + salt;
+    var dbMask = pss_mgf1_str(H, DB.length, hashFunc);
+    var maskedDB = [];
+
+    for (i = 0; i < DB.length; i += 1) {
+        maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i);
+    }
+
+    var mask = (0xff00 >> (8 * emLen - emBits)) & 0xff;
+    maskedDB[0] &= ~mask;
+
+    for (i = 0; i < hLen; i++) {
+        maskedDB.push(H.charCodeAt(i));
+    }
+
+    maskedDB.push(0xbc);
+
+    return _zeroPaddingOfSignature(
+            this.doPrivate(new BigInteger(maskedDB)).toString(16),
+            this.n.bitLength());
+}
+
+// ========================================================================
+// Signature Verification
+// ========================================================================
+
+function _rsasign_getDecryptSignatureBI(biSig, hN, hE) {
+    var rsa = new RSAKey();
+    rsa.setPublic(hN, hE);
+    var biDecryptedSig = rsa.doPublic(biSig);
+    return biDecryptedSig;
+}
+
+function _rsasign_getHexDigestInfoFromSig(biSig, hN, hE) {
+    var biDecryptedSig = _rsasign_getDecryptSignatureBI(biSig, hN, hE);
+    var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, '');
+    return hDigestInfo;
+}
+
+function _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo) {
+    for (var algName in _RSASIGN_DIHEAD) {
+	var head = _RSASIGN_DIHEAD[algName];
+	var len = head.length;
+	if (hDigestInfo.substring(0, len) == head) {
+	    var a = [algName, hDigestInfo.substring(len)];
+	    return a;
+	}
+    }
+    return [];
+}
+
+function _rsasign_verifySignatureWithArgs(sMsg, biSig, hN, hE) {
+    var hDigestInfo = _rsasign_getHexDigestInfoFromSig(biSig, hN, hE);
+    var digestInfoAry = _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo);
+    if (digestInfoAry.length == 0) return false;
+    var algName = digestInfoAry[0];
+    var diHashValue = digestInfoAry[1];
+    var ff = _RSASIGN_HASHHEXFUNC[algName];
+    var msgHashValue = ff(sMsg);
+    return (diHashValue == msgHashValue);
+}
+
+function _rsasign_verifyHexSignatureForMessage(hSig, sMsg) {
+    var biSig = parseBigInt(hSig, 16);
+    var result = _rsasign_verifySignatureWithArgs(sMsg, biSig,
+						  this.n.toString(16),
+						  this.e.toString(16));
+    return result;
+}
+
+/**
+ * verifies a sigature for a message string with RSA public key.<br/>
+ * @name verifyString
+ * @memberOf RSAKey#
+ * @function
+ * @param {String} sMsg message string to be verified.
+ * @param {String} hSig hexadecimal string of siganture.<br/>
+ *                 non-hexadecimal charactors including new lines will be ignored.
+ * @return returns 1 if valid, otherwise 0
+ */
+function _rsasign_verifyString(sMsg, hSig) {
+    hSig = hSig.replace(_RE_HEXDECONLY, '');
+    if (hSig.length != this.n.bitLength() / 4) return 0;
+    hSig = hSig.replace(/[ \n]+/g, "");
+    var biSig = parseBigInt(hSig, 16);
+    var biDecryptedSig = this.doPublic(biSig);
+    var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, '');
+    var digestInfoAry = _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo);
+  
+    if (digestInfoAry.length == 0) return false;
+    var algName = digestInfoAry[0];
+    var diHashValue = digestInfoAry[1];
+    var ff = _RSASIGN_HASHHEXFUNC[algName];
+    var msgHashValue = ff(sMsg);
+    return (diHashValue == msgHashValue);
+}
+
+/**
+ * verifies a sigature for a message string with RSA public key by PKCS#1 PSS sign.<br/>
+ * @name verifyStringPSS
+ * @memberOf RSAKey#
+ * @function
+ * @param {String} sMsg message string to be verified.
+ * @param {String} hSig hexadecimal string of siganture.<br/>
+ *                 non-hexadecimal charactors including new lines will be ignored.
+ * @return returns 1 if valid, otherwise 0
+ */
+function _rsasign_verifyStringPSS(sMsg, hSig, hashAlg, sLen) {
+    if (hSig.length !== this.n.bitLength() / 4) {
+        return false;
+    }
+
+    var hashFunc = _RSASIGN_HASHRAWFUNC[hashAlg];
+    var mHash = hashFunc(sMsg);
+    var hLen = mHash.length;
+    var emBits = this.n.bitLength() - 1;
+    var emLen = Math.ceil(emBits / 8);
+    var i;
+
+    if (sLen === -1) {
+        sLen = hLen; // same has hash length
+    } else if ((sLen === -2) || (sLen === undefined)) {
+        sLen = emLen - hLen - 2; // maximum
+    } else if (sLen < -2) {
+        throw "invalid salt length";
+    }
+
+    if (emLen < (hLen + sLen + 2)) {
+        throw "data too long";
+    }
+
+    var em = this.doPublic(parseBigInt(hSig, 16)).toByteArray();
+
+    for (i = 0; i < em.length; i += 1) {
+        em[i] &= 0xff;
+    }
+
+    while (em.length < emLen) {
+        em.unshift(0);
+    }
+
+    if (em[emLen -1] !== 0xbc) {
+        throw "encoded message does not end in 0xbc";
+    }
+
+    em = String.fromCharCode.apply(String, em);
+
+    var maskedDB = em.substr(0, emLen - hLen - 1);
+    var H = em.substr(maskedDB.length, hLen);
+
+    var mask = (0xff00 >> (8 * emLen - emBits)) & 0xff;
+
+    if ((maskedDB.charCodeAt(0) & mask) !== 0) {
+        throw "bits beyond keysize not zero";
+    }
+
+    var dbMask = pss_mgf1_str(H, maskedDB.length, hashFunc);
+    var DB = [];
+
+    for (i = 0; i < maskedDB.length; i += 1) {
+        DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i);
+    }
+
+    DB[0] &= ~mask;
+
+    var checkLen = emLen - hLen - sLen - 2;
+
+    for (i = 0; i < checkLen; i += 1) {
+        if (DB[i] !== 0x00) {
+            throw "leftmost octets not zero";
+        }
+    }
+
+    if (DB[checkLen] !== 0x01) {
+        throw "0x01 marker not found";
+    }
+
+    return H === hashFunc('\x00\x00\x00\x00\x00\x00\x00\x00' + mHash +
+                          String.fromCharCode.apply(String, DB.slice(-sLen)));
+}
+
+RSAKey.prototype.signString = _rsasign_signString;
+RSAKey.prototype.signStringWithSHA1 = _rsasign_signStringWithSHA1;
+RSAKey.prototype.signStringWithSHA256 = _rsasign_signStringWithSHA256;
+RSAKey.prototype.sign = _rsasign_signString;
+RSAKey.prototype.signWithSHA1 = _rsasign_signStringWithSHA1;
+RSAKey.prototype.signWithSHA256 = _rsasign_signStringWithSHA256;
+RSAKey.prototype.signStringPSS = _rsasign_signStringPSS;
+RSAKey.prototype.signPSS = _rsasign_signStringPSS;
+RSAKey.SALT_LEN_HLEN = -1;
+RSAKey.SALT_LEN_MAX = -2;
+
+RSAKey.prototype.verifyString = _rsasign_verifyString;
+RSAKey.prototype.verifyHexSignatureForMessage = _rsasign_verifyHexSignatureForMessage;
+RSAKey.prototype.verify = _rsasign_verifyString;
+RSAKey.prototype.verifyHexSignatureForByteArrayMessage = _rsasign_verifyHexSignatureForMessage;
+RSAKey.prototype.verifyStringPSS = _rsasign_verifyStringPSS;
+RSAKey.prototype.verifyPSS = _rsasign_verifyStringPSS;
+RSAKey.SALT_LEN_RECOVER = -2;
+
+/**
+ * @name RSAKey
+ * @class key of RSA public key algorithm
+ * @description Tom Wu's RSA Key class and extension
+ */
diff --git a/chrome/locale/en-US/dkim.js b/chrome/locale/en-US/dkim.js
new file mode 100644
index 00000000..1ab91c83
--- /dev/null
+++ b/chrome/locale/en-US/dkim.js
@@ -0,0 +1,76 @@
+var DKIM_STRINGS = {};
+
+// DKIM_STRINGS
+DKIM_STRINGS.loading = "Validating...";
+DKIM_STRINGS.SUCCESS = function(domain) {return "Valid (Signed by "+domain+")";};
+DKIM_STRINGS.PERMFAIL = "Invalid";
+DKIM_STRINGS.TEMPFAIL = function(domain) {
+	return "Temporary validating error (For Signature by "+domain+")";};
+
+// DKIM_INTERNALERROR
+DKIM_STRINGS.DKIM_INTERNALERROR					= "DKIM verifier internal error";
+DKIM_STRINGS.DKIM_INTERNALERROR_DEFAULT			= "error";
+
+// DKIM_SIGERROR
+DKIM_STRINGS.DKIM_SIGERROR					= "DKIM Signature Error";
+DKIM_STRINGS.DKIM_SIGERROR_DEFAULT			= "error";
+// DKIM_SIGERROR - DKIM-Signature Header
+DKIM_STRINGS.DKIM_SIGERROR_VERSION			= "Unsupported version";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_V		= "DKIM version missing";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_A		= "Missing signature algorithm";
+DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_A		= "Unsupported Signature algorithm";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_B		= "Missing signature";
+DKIM_STRINGS.DKIM_SIGERROR_CORRUPT_B		= "Signature wrong";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_BH		= "Missing body hash";
+DKIM_STRINGS.DKIM_SIGERROR_CORRUPT_BH		= "Wrong body hash";
+DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_C_H		= "Unsupported canonicalization algorithm for header";
+DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_C_B		= "Unsupported canonicalization algorithm for body";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_D		= "Missing Signing Domain Identifier (SDID)";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_H		= "Missing signed header fields";
+DKIM_STRINGS.DKIM_SIGERROR_DOMAIN_I			= "AUID is not in a subdomain of SDID";
+DKIM_STRINGS.DKIM_SIGERROR_TOOLARGE_L		= "Value of the body lenght tag exceeds body size";
+DKIM_STRINGS.DKIM_SIGERROR_UNKNOWN_Q		= "Unsupported query methods for public key retrievel";
+DKIM_STRINGS.DKIM_SIGERROR_MISSING_S		= "Missing selector tag";
+DKIM_STRINGS.DKIM_SIGERROR_TIMESTAMPS		= "Signature Expiration before Signature Timestamp";
+// DKIM_SIGERROR - key query
+DKIM_STRINGS.DKIM_SIGERROR_KEYFAIL			= "DNS query for key failed";
+// DKIM_SIGERROR - Key record
+DKIM_STRINGS.DKIM_SIGERROR_KEY_INVALID_V	= "Invalid Version of the DKIM key record";
+DKIM_STRINGS.DKIM_SIGERROR_KEY_UNKNOWN_K	= "Unsupported key type";
+DKIM_STRINGS.DKIM_SIGERROR_KEY_MISSING_P	= "Missing key";
+DKIM_STRINGS.DKIM_SIGERROR_KEY_REVOKED		= "Key revoked";
+DKIM_STRINGS.DKIM_SIGERROR_KEY_NOTEMAILKEY	= "Key is not an e-mail key";
+// DKIM_SIGERROR - key decode
+DKIM_STRINGS.DKIM_SIGERROR_KEYDECODE		= "Key couldn't be decoded";
+
+// DKIM_SIGWARNING
+DKIM_STRINGS.DKIM_SIGWARNING_SMALL_L		= "Not the entire body is singned";
+
+
+// #define DKIM_SIGERROR_UNKNOWN		(-1)	/* unknown error */
+// #define DKIM_SIGERROR_OK		0	/* no error */
+// #define DKIM_SIGERROR_EXPIRED		3	/* signature expired */
+// #define DKIM_SIGERROR_FUTURE		4	/* signature in the future */
+	// #define DKIM_SIGERROR_INVALID_HC	7	/* c= invalid (header) */
+	// #define DKIM_SIGERROR_INVALID_BC	8	/* c= invalid (body) */
+// #define DKIM_SIGERROR_INVALID_L		12	/* l= invalid */
+	// #define DKIM_SIGERROR_INVALID_Q		13	/* q= invalid */
+	// #define DKIM_SIGERROR_INVALID_QO	14	/* q= option invalid */
+// #define DKIM_SIGERROR_EMPTY_D		16	/* d= empty */
+// #define DKIM_SIGERROR_EMPTY_S		18	/* s= empty */
+// #define DKIM_SIGERROR_EMPTY_B		20	/* b= empty */
+// #define DKIM_SIGERROR_NOKEY		22	/* no key found in DNS */
+// #define DKIM_SIGERROR_DNSSYNTAX		23	/* DNS reply corrupt */
+// #define DKIM_SIGERROR_EMPTY_BH		26	/* bh= empty */
+// #define DKIM_SIGERROR_BADSIG		28	/* signature mismatch */
+// #define DKIM_SIGERROR_MULTIREPLY	30	/* multiple records returned */
+// #define DKIM_SIGERROR_EMPTY_H		31	/* h= empty */
+// #define DKIM_SIGERROR_INVALID_H		32	/* h= missing req'd entries */
+// #define DKIM_SIGERROR_MBSFAILED		34	/* "must be signed" failure */
+	// #define DKIM_SIGERROR_KEYVERSION	35	/* unknown key version */
+// #define DKIM_SIGERROR_KEYUNKNOWNHASH	36	/* unknown key hash */
+// #define DKIM_SIGERROR_KEYHASHMISMATCH	37	/* sig-key hash mismatch */
+// #define DKIM_SIGERROR_KEYTYPEMISSING	40	/* key type missing */
+// #define DKIM_SIGERROR_EMPTY_V		45	/* v= tag empty */
+// #define DKIM_SIGERROR_KEYTOOSMALL	46	/* too few key bits */
+
diff --git a/chrome/locale/en-US/dns.js b/chrome/locale/en-US/dns.js
new file mode 100644
index 00000000..183c38a5
--- /dev/null
+++ b/chrome/locale/en-US/dns.js
@@ -0,0 +1,7 @@
+var DNS_STRINGS = new Object();
+DNS_STRINGS.TOO_MANY_HOPS = "Too many hops.";
+DNS_STRINGS.CONNECTION_REFUSED = function(server) { return "DNS server " + server + " refused a TCP connection."; };
+DNS_STRINGS.TIMED_OUT = function(server) { return "DNS server " + server + " timed out on a TCP connection."; };
+DNS_STRINGS.SERVER_ERROR = function(server) { return "Error connecting to DNS server " + server + "."; };
+DNS_STRINGS.INCOMPLETE_RESPONSE = function(server) { return "Incomplete response from " + server + "."; };
+
diff --git a/chrome/locale/en-US/xulstrings.dtd b/chrome/locale/en-US/xulstrings.dtd
new file mode 100644
index 00000000..283cefc8
--- /dev/null
+++ b/chrome/locale/en-US/xulstrings.dtd
@@ -0,0 +1 @@
+<!ENTITY dkim_verifier.header.title "DKIM-Signature:">
diff --git a/install.rdf b/install.rdf
new file mode 100644
index 00000000..ba07635e
--- /dev/null
+++ b/install.rdf
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+	<Description about="urn:mozilla:install-manifest">
+		<em:id>dkim_verifier@pl</em:id>
+		<em:type>2</em:type> <!-- Extensions -->
+		
+		<em:name>DKIM Verifier</em:name>
+		<em:description>Verifies the DKIM-Signature.</em:description>
+
+		<em:version>0.1</em:version>
+
+		<em:creator>Philippe Lieser</em:creator>
+		
+		<!-- DNS Library -->
+		<em:contributor>Joshua Tauberer</em:contributor>
+		<!-- RSA and BigInteger Library -->
+		<em:contributor>Tom Wu</em:contributor>
+		<!-- RSA signing and verification and asn1hex Library -->
+		<em:contributor>Kenji Urushima</em:contributor>
+		
+		<!--
+		<em:optionsURL>chrome://myext/content/options.xul</em:optionsURL>
+		<em:aboutURL>chrome://myext/content/about.xul</em:aboutURL>
+		<em:iconURL>chrome://myext/skin/icon.png</em:iconURL>
+		<em:homepageURL>http://www.foo.com/</em:homepageURL>
+		-->
+
+		<em:targetApplication>
+			<Description>
+				<em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id> <!-- Thunderbird -->
+				<em:minVersion>17.0</em:minVersion>
+				<em:maxVersion>17.*</em:maxVersion>
+			</Description>
+		</em:targetApplication>
+
+	</Description>     
+</RDF>
\ No newline at end of file