[tor-commits] [torbutton/master] Commit Edward Pastuszenski's SafeCache+SafeCookie work.

mikeperry at torproject.org mikeperry at torproject.org
Fri Jul 1 00:58:04 UTC 2011


commit eade74695421eb15f3149cc74482107a4fb99fc4
Author: Mike Perry <mikeperry-git at fscked.org>
Date:   Thu Jun 30 10:43:33 2011 -0700

    Commit Edward Pastuszenski's SafeCache+SafeCookie work.
    
    http://crypto.stanford.edu/cs294s/projects/browser.html
    
    We're going to chop this up a bit, but I figured I'd commit a pristine copy of
    his work before doing so.
---
 src/chrome/content/md5.js                         |  256 +++++++++++++++
 src/chrome/content/pref-privacy.xul               |   31 ++
 src/chrome/content/preferences.js                 |   31 ++
 src/chrome/content/preferences.xul                |   16 +
 src/chrome/content/stanford-safecache-overlay.xul |   10 +
 src/chrome/content/stanford-safecache.js          |  351 +++++++++++++++++++++
 src/chrome/content/torbutton.xul                  |    5 +
 src/chrome/locale/en/pref-privacy.dtd             |    1 +
 src/chrome/locale/en/torbutton.dtd                |    5 +
 src/defaults/preferences/preferences.js           |    3 +
 10 files changed, 709 insertions(+), 0 deletions(-)

diff --git a/src/chrome/content/md5.js b/src/chrome/content/md5.js
new file mode 100644
index 0000000..46d2aab
--- /dev/null
+++ b/src/chrome/content/md5.js
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
diff --git a/src/chrome/content/pref-privacy.xul b/src/chrome/content/pref-privacy.xul
new file mode 100644
index 0000000..afa304e
--- /dev/null
+++ b/src/chrome/content/pref-privacy.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE window [
+<!ENTITY % SSH_prefPrivacyDTD SYSTEM "chrome://torbutton/locale/pref-privacy.dtd">
+%SSH_prefPrivacyDTD;
+]>
+<overlay id="SafeCacheConfigOverlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+    <prefwindow id="BrowserPreferences">
+      <prefpane id="panePrivacy">
+        <preferences id="privacyPreferences">
+           <preference id="stanford-safecache.enabled" 
+             name="stanford-safecache.enabled" type="bool"/>
+        </preferences>
+	<groupbox id="historyGroup" />
+	<groupbox id="cookiesGroup" />
+	<groupbox id="privateDataGroup">
+            <hbox id="safeCacheContainer">
+              <image
+                src="chrome://torbutton/skin/stanford-safecache.png"/>
+              <checkbox 
+                preference="stanford-safecache.enabled" 
+                id="enableSafeCache"
+                label="&safeCache.label;"/>
+            </hbox>
+        </groupbox>  
+      </prefpane>
+    </prefwindow>
+
+</overlay>
+
diff --git a/src/chrome/content/preferences.js b/src/chrome/content/preferences.js
index d72509a..3a7f99f 100644
--- a/src/chrome/content/preferences.js
+++ b/src/chrome/content/preferences.js
@@ -208,6 +208,24 @@ function torbutton_prefs_init(doc) {
         o_torprefs.setBoolPref('block_cache', true);
         o_torprefs.setBoolPref('clear_cache', false);
     }
+    
+    switch(o_torprefs.getIntPref('safecache')) {
+        case 0: // always
+            doc.getElementById("torbutton_safecacheGroup").selectedItem =
+                doc.getElementById('torbutton_safecacheAlways');
+            break;
+        case 1: // during tor
+            doc.getElementById("torbutton_safecacheGroup").selectedItem =
+                doc.getElementById('torbutton_safecacheDuringTor');
+            break;
+        case 2: // never
+            doc.getElementById("torbutton_safecacheGroup").selectedItem =
+                doc.getElementById('torbutton_safecacheNever');
+            break;
+    }
+    
+    doc.getElementById('torbutton_cookie_js_allow').checked = 
+        o_torprefs.getBoolPref('cookie_js_allow');
 
     if(o_torprefs.getBoolPref('clear_cookies')) {
         doc.getElementById('torbutton_cookieGroup').selectedItem = 
@@ -501,6 +519,19 @@ function torbutton_prefs_save(doc) {
 
     o_torprefs.setBoolPref('clear_cache', doc.getElementById('torbutton_clearCache').selected);
     o_torprefs.setBoolPref('block_cache', doc.getElementById('torbutton_blockCache').selected);
+    
+    if(doc.getElementById('torbutton_safecacheGroup').selectedItem ==
+            doc.getElementById('torbutton_safecacheAlways')) {
+        o_torprefs.setIntPref('safecache', 0);
+    } else if(doc.getElementById('torbutton_safecacheGroup').selectedItem ==
+            doc.getElementById('torbutton_safecacheDuringTor')) {
+        o_torprefs.setIntPref('safecache', 1);
+    } else {
+        o_torprefs.setIntPref('safecache', 2);
+    }
+    
+    o_torprefs.setBoolPref('cookie_js_allow', doc.getElementById('torbutton_cookie_js_allow').checked);
+    
     o_torprefs.setBoolPref('clear_cookies', doc.getElementById('torbutton_clearCookies').selected);
     o_torprefs.setBoolPref('cookie_jars', doc.getElementById('torbutton_cookieJars').selected);
     o_torprefs.setBoolPref('dual_cookie_jars', doc.getElementById('torbutton_dualCookieJars').selected || doc.getElementById('torbutton_cookieProtections').selected);
diff --git a/src/chrome/content/preferences.xul b/src/chrome/content/preferences.xul
index 1795fc1..b7c2d33 100644
--- a/src/chrome/content/preferences.xul
+++ b/src/chrome/content/preferences.xul
@@ -238,6 +238,22 @@
                    label="&torbutton.prefs.block_cache;" 
                    oncommand="torbutton_prefs_set_field_attributes(document)"/>
             </radiogroup>
+            <hbox>
+             <label value="&torbutton.prefs.safecache;" control="torbutton_safecacheGroup"/>
+             <radiogroup align="center" orient="horizontal" id="torbutton_safecacheGroup">
+              <radio id="torbutton_safecacheAlways" 
+                   label="&torbutton.prefs.always;" 
+                   oncommand="torbutton_prefs_set_field_attributes(document)"/>
+              <radio id="torbutton_safecacheDuringTor" 
+                   label="&torbutton.prefs.during_tor;" 
+                   oncommand="torbutton_prefs_set_field_attributes(document)"/>
+              <radio id="torbutton_safecacheNever" 
+                   label="&torbutton.prefs.never;" 
+                   oncommand="torbutton_prefs_set_field_attributes(document)"/>
+             </radiogroup>
+            </hbox>
+            <checkbox id="torbutton_cookie_js_allow" label="&torbutton.prefs.cookie_js_allow;" 
+                  oncommand="torbutton_prefs_set_field_attributes(document)"/>
            </vbox>
           </tabpanel>
           <tabpanel id="cookies">
diff --git a/src/chrome/content/stanford-safecache-overlay.xul b/src/chrome/content/stanford-safecache-overlay.xul
new file mode 100644
index 0000000..a51432b
--- /dev/null
+++ b/src/chrome/content/stanford-safecache-overlay.xul
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+
+<overlay id="stanford-safecache-overlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+<script src="chrome://torbutton/content/stanford-safecache.js" />
+<script src="chrome://torbutton/content/md5.js" />
+
+</overlay>
diff --git a/src/chrome/content/stanford-safecache.js b/src/chrome/content/stanford-safecache.js
new file mode 100644
index 0000000..4c6c155
--- /dev/null
+++ b/src/chrome/content/stanford-safecache.js
@@ -0,0 +1,351 @@
+/*
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+    * Neither the name of Stanford University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+// Dual-keyed cache/cookie same-origin policy
+// Ensures that cache and cookies set in HTTP headers cannot be used for 
+// non-cooperative or semi-cooperative tracking.
+// 
+// Author: Edward Pastuszenski
+// 
+// Based on Stanford SafeCache
+// Author: Collin Jackson
+// Other contributors: Andrew Bortz, John Mitchell, Dan Boneh
+// 
+
+//////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const kSSC_ENABLED_PREF = "extensions.torbutton.safecache";
+const kSSC_TORBUTTON_PREF = "extensions.torbutton.tor_enabled";
+const kSSC_COOKIE_JS_PREF = "extensions.torbutton.cookie_js_allow";
+const kSSC_COOKIE_BEHAVIOR_PREF = "network.cookie.cookieBehavior";
+
+////////////////////////////////////////////////////////////////////////////
+// Debug stuff
+
+/**
+ * Dump information to the console?
+ */
+var SSC_debug = false;
+
+/**
+ * Sends data to the console if we're in debug mode
+ * @param msg The string containing the message to display
+ */
+function SSC_dump(msg) {
+  if (SSC_debug)
+    dump("|||||||||| SSC: " + msg + "\n");
+}
+
+////////////////////////////////////////////////////////////////////////////
+// "Major" objects/classes
+
+/**
+ * SafeCache HTTP Request Listener
+ * Watches for the authentication requests and presents password dialog
+ */
+
+function SSC_RequestListener(controller) {
+  this.controller = controller;
+}
+
+SSC_RequestListener.prototype =
+{
+  controller: null,  // The SSC_Controller that created this
+
+  observe: function(subject, topic, data) { 
+    try { 
+      if(this.controller.getEnabled() == 2) return;
+      if(this.controller.getEnabled() == 1
+          && !this.controller.getTorButton()) return;
+      if (topic == 'http-on-modify-request') {
+        subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+        subject.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+        subject.QueryInterface(Components.interfaces.nsICachingChannel);
+        this.onModifyRequest(subject);
+      } else if (topic == 'http-on-examine-response') {
+        subject.QueryInterface(Components.interfaces.nsIHttpChannel);
+        subject.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+        subject.QueryInterface(Components.interfaces.nsICachingChannel);
+        this.onExamineResponse(subject);
+      }
+    } catch(e) {try {SSC_dump(e);} catch(ex) {}} 
+  },
+
+  bypassCache: function(channel) {
+    channel.loadFlags |= channel.LOAD_BYPASS_CACHE;  
+      // INHIBIT_PERSISTENT_CACHING instead?
+    channel.cacheKey = this.newCacheKey(0);
+    SSC_dump("Bypassed cache for " + channel.URI.spec + "\n");
+  },
+
+  setCacheKey: function(channel, str) {
+    var oldData = this.readCacheKey(channel.cacheKey);
+    var newKey = this.newCacheKey(this.getHash(str) + oldData);
+    channel.cacheKey = newKey;
+     //SSC_dump("Set cache key to hash(" + str + ") = " + 
+              //newKey.data + "\n   for " + channel.URI.spec + "\n");
+  },
+
+  onModifyRequest: function(channel) {
+    
+    var parent = window.content.location;
+    if (channel.documentURI && channel.documentURI == channel.URI) {
+      parent = null;  // first party interaction
+    }
+    
+
+    var cookie;
+    try{
+        cookie = channel.getRequestHeader("Cookie");
+        //SSC_dump("Cookie: " + cookie);
+    } catch(e) {cookie = null;}
+
+    // Same-origin policy
+    var referrer;
+    if (parent && parent.hostname != channel.URI.host) {
+      //SSC_dump("Segmenting " + channel.URI.host + 
+               //" content loaded by " + parent.host);
+      this.setCacheKey(channel, parent.hostname);
+      referrer = parent.hostname;
+    } else {
+      referrer = channel.URI.host;  
+      if(!this.readCacheKey(channel.cacheKey)) {
+        this.setCacheKey(channel, channel.URI.host);
+      } else {
+        // SSC_dump("Existing cache key detected; leaving it unchanged.");
+      }
+    }
+    
+    if(cookie) {
+        //Strip the secondary key from every referrer-matched cookie
+        var newHeader = "";
+        var i = 0;
+        var lastStart = 0;
+        //State 0: no information on next cookie
+        //State 1: cookie will be sent.
+        //State 2: cookie will not be sent.
+        var state = 0;
+        while (i < cookie.length) {
+            //Dual-keyed cookie
+            if(state == 0 && cookie.charAt(i) == '|'){
+                //If referrers match, strip key and send cookie
+                var cookieReferrer = cookie.toString().substr(lastStart, i - lastStart);
+                if (referrer == cookieReferrer){
+                    lastStart = i+1;
+                    state = 1;
+                } else {
+                    state = 2;
+                }
+            }
+            //Single-keyed cookie that was set via scripting.
+            if (state == 0 && cookie.charAt(i) == '='){
+                if(this.controller.getCookieJS())  state = 1;
+                else {
+                    if (referrer == channel.getRequestHeader("Host")) state = 1;
+                    else state = 2;
+                }
+            }
+            //End of a cookie
+            if (cookie.charAt(i) == ';') {
+                var thisCookie = cookie.toString().substr(lastStart, i - lastStart + 2);
+                if (state == 1){
+                    newHeader += thisCookie; 
+                }
+                if (state == 2){
+                    SSC_dump("Declining to send " + thisCookie +  
+                        " for request by embedded domain " + channel.URI.host +
+                        + "   " + channel.getRequestHeader("Host") +
+                        " on embedding page " + referrer);
+                }
+                lastStart = i+2;
+                state = 0;
+            }
+            //End of the string
+            if (i == cookie.length - 1){
+                thisCookie = cookie.toString().substr(lastStart, i - lastStart + 1);
+                if (state == 1){
+                    newHeader += thisCookie; 
+                }
+                if (state == 2){
+                    SSC_dump("Declining to send " + thisCookie + 
+                        " for request by embedded domain " + channel.URI.host +
+                        + "   " + channel.getRequestHeader("Host") +
+                        " on embedding page " + referrer);
+                }
+                lastStart = i+1;
+            }                
+            i++;
+        }
+        channel.setRequestHeader("Cookie", newHeader, false);
+    }
+
+    // Third-party blocking policy
+    switch(this.controller.getCookieBehavior()) {
+      case this.controller.ACCEPT_COOKIES:
+        break;
+      case this.controller.NO_FOREIGN_COOKIES:
+        if(parent && parent.hostname != channel.URI.host) {
+          //SSC_dump("Third party cache blocked for " + channel.URI.spec +
+                   //" content loaded by " + parent.spec);
+          this.bypassCache(channel);
+        }
+        break;
+      case this.controller.REJECT_COOKIES:
+        this.bypassCache(channel);
+        break;
+      default:
+        SSC_dump(controller.getCookieBehavior() + 
+                 " is not a valid cookie behavior.");
+        break;
+    }
+  },
+
+  onExamineResponse: function(channel) {
+    var setCookie;
+    try{
+        setCookie = channel.getResponseHeader("Set-Cookie");
+    } catch(e) {setCookie = null;}
+    
+    if(setCookie) {
+        var parent = window.content.location;
+        if (channel.documentURI && channel.documentURI == channel.URI) {
+            parent = null;  // first party interaction
+        }
+
+        var referrer;
+        // Same-origin policy
+        if (parent && parent.hostname != channel.URI.host) {
+            //SSC_dump("Segmenting " + channel.URI.host + 
+            //" content loaded by " + parent.host);
+            referrer = parent.hostname;
+        } else {
+            referrer = channel.URI.host;
+        }
+        //Dual-key each cookie set in the header
+        var newHeader = "";
+        var i = 0;
+        var lastStart = 0;
+        //Some messy code that prevents multiple embedding-domain keys
+        //from being concatenated to cookie names.
+        var passedname = false;
+        var namebar = false;
+        while (i < setCookie.length) {
+            if (setCookie.charAt(i) == '=') passedname = true;
+            else if (setCookie.charAt(i) == '|' && passedname == false)
+                namebar = true;
+            if (i == setCookie.length - 1 || setCookie.charAt(i) == '\n'){
+                if(!namebar){
+                  newHeader += referrer + "|" + 
+                      setCookie.toString().substr(lastStart, i - lastStart + 1);   
+                }
+                lastStart = i+1;
+                passedname = false;
+                namebar = false;
+            }
+            i++;
+        }
+        //SSC_dump("MODIFIED Set-Cookie: " + newHeader);
+        channel.setResponseHeader("Set-Cookie", newHeader, false);
+    }
+  },
+
+  // Read the integer data contained in a cache key
+  readCacheKey: function(key) {
+    key.QueryInterface(Components.interfaces.nsISupportsPRUint32);
+    return key.data;
+  },
+
+  // Construct a new cache key with some integer data
+  newCacheKey: function(data) {
+    var cacheKey = 
+      Components.classes["@mozilla.org/supports-PRUint32;1"]
+                .createInstance(Components.interfaces.nsISupportsPRUint32);
+    cacheKey.data = data;
+    return cacheKey;
+  },
+
+  // Get an integer hash of a string
+  getHash: function(str) {
+    var hash = str_md5(str); 
+    var intHash = 0;    
+    for(var i = 0; i < hash.length && i < 8; i++)
+      intHash += hash.charCodeAt(i) << (i << 3);
+    return intHash;
+  },
+}
+
+/**
+ * Master control object. Adds and removes the RequestListener
+ */
+function SSC_Controller() {
+  this.addListener(new SSC_RequestListener(this));
+}
+
+SSC_Controller.prototype = {
+
+  getEnabled: function() {
+    return (Components.classes["@mozilla.org/preferences-service;1"]
+                     .getService(Components.interfaces.nsIPrefService)
+                     .getIntPref(kSSC_ENABLED_PREF));
+  },
+
+  getTorButton: function() {
+    return (Components.classes["@mozilla.org/preferences-service;1"]
+                     .getService(Components.interfaces.nsIPrefBranch)
+                     .getBoolPref(kSSC_TORBUTTON_PREF));  
+  },
+  
+  getCookieJS: function() {
+      return (Components.classes["@mozilla.org/preferences-service;1"]
+                     .getService(Components.interfaces.nsIPrefBranch)
+                     .getBoolPref(kSSC_COOKIE_JS_PREF));  
+  },
+
+  // Returns the value of the network.cookie.cookieBehavior pref
+  ACCEPT_COOKIES: 0,
+  NO_FOREIGN_COOKIES: 1,
+  REJECT_COOKIES: 2,
+  getCookieBehavior: function() {
+    return Components.classes["@mozilla.org/preferences-service;1"]
+                     .getService(Components.interfaces.nsIPrefService)
+                     .getIntPref(kSSC_COOKIE_BEHAVIOR_PREF);
+  },
+
+  addListener: function(listener) {
+    var observerService = 
+      Components.classes["@mozilla.org/observer-service;1"]
+        .getService(Components.interfaces.nsIObserverService);
+    observerService.addObserver(listener, "http-on-modify-request", false);
+    observerService.addObserver(listener, "http-on-examine-response", false);
+  },
+
+  removeListener: function(listener) {
+    var observerService = 
+      Components.classes["@mozilla.org/observer-service;1"]
+        .getService(Components.interfaces.nsIObserverService);
+    observerService.removeObserver(listener, "http-on-modify-request");
+    observerService.removeObserver(listener, "http-on-examine-response");
+  },
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Global stuff
+// "What script would be complete without a couple of globals?" --Fritz
+
+var SSC_controller;
+
+function SSC_startup(event) {
+  if(!SSC_controller) SSC_controller = new SSC_Controller();
+  SSC_dump("Loaded controller");
+}
+
+window.addEventListener("load", SSC_startup, false);
diff --git a/src/chrome/content/torbutton.xul b/src/chrome/content/torbutton.xul
index 8e9cde6..c17ca91 100644
--- a/src/chrome/content/torbutton.xul
+++ b/src/chrome/content/torbutton.xul
@@ -6,6 +6,11 @@
 
 <overlay id="torbutton-overlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- SAFECACHE -->
+    <script src="chrome://torbutton/content/stanford-safecache.js" />
+    <script src="chrome://torbutton/content/md5.js" />
+
     <script type="application/x-javascript" src="chrome://torbutton/content/torbutton_util.js" />
     <script type="application/x-javascript" src="chrome://torbutton/content/torbutton.js" />
     <script language="JavaScript">
diff --git a/src/chrome/locale/en/pref-privacy.dtd b/src/chrome/locale/en/pref-privacy.dtd
new file mode 100644
index 0000000..93aac71
--- /dev/null
+++ b/src/chrome/locale/en/pref-privacy.dtd
@@ -0,0 +1 @@
+<!ENTITY safeCache.label                 "Use SafeCache to impose cookie policy on cache">
diff --git a/src/chrome/locale/en/torbutton.dtd b/src/chrome/locale/en/torbutton.dtd
index 998eb76..9416ac0 100644
--- a/src/chrome/locale/en/torbutton.dtd
+++ b/src/chrome/locale/en/torbutton.dtd
@@ -136,3 +136,8 @@
 <!ENTITY torbutton.prefs.engine5 "duckduckgo.com">
 <!ENTITY torbutton.prefs.fix_google_srch "Strip platform and language off of Google Search Box queries">
 <!ENTITY torbutton.prefs.transparentTor "Transparent Torification (Requires custom transproxy or Tor router)">
+<!ENTITY torbutton.prefs.safecache "Enforce same-origin policy on cookies and cache:">
+<!ENTITY torbutton.prefs.always "Always">
+<!ENTITY torbutton.prefs.during_tor "During Tor">
+<!ENTITY torbutton.prefs.never "Never">
+<!ENTITY torbutton.prefs.cookie_js_allow "Allow cookies set with Javascript (unknown origin)">
diff --git a/src/defaults/preferences/preferences.js b/src/defaults/preferences/preferences.js
index 9a406f5..a6d565a 100644
--- a/src/defaults/preferences/preferences.js
+++ b/src/defaults/preferences/preferences.js
@@ -212,3 +212,6 @@ pref("extensions.torbutton.productsub_override","20100101");
 pref("extensions.torbutton.buildID_override","0");
 pref("extensions.torbutton.useragent_vendor", "");
 pref("extensions.torbutton.useragent_vendorSub","");
+
+pref("extensions.torbutton.safecache",1); // 0=always, 1=during tor, 2=never
+pref("extensions.torbutton.cookie_js_allow", true);
\ No newline at end of file





More information about the tor-commits mailing list