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