summaryrefslogtreecommitdiff
path: root/data/extensions/https-everywhere@eff.org/components
diff options
context:
space:
mode:
authorRuben Rodriguez <ruben@gnu.org>2014-10-20 02:24:51 +0200
committerRuben Rodriguez <ruben@gnu.org>2014-10-20 02:24:51 +0200
commit6e7918b6ccb69876d339a320091fdee811445395 (patch)
tree31cb88ee438d652fddefca1193f70289a8b3dcc8 /data/extensions/https-everywhere@eff.org/components
parent60e5b13c35d4d3ba21bb03b026750a0a414f6c77 (diff)
Generalize data directory
Diffstat (limited to 'data/extensions/https-everywhere@eff.org/components')
-rw-r--r--data/extensions/https-everywhere@eff.org/components/https-everywhere.js832
-rw-r--r--data/extensions/https-everywhere@eff.org/components/ssl-observatory.js1026
2 files changed, 1858 insertions, 0 deletions
diff --git a/data/extensions/https-everywhere@eff.org/components/https-everywhere.js b/data/extensions/https-everywhere@eff.org/components/https-everywhere.js
new file mode 100644
index 0000000..90f55e2
--- /dev/null
+++ b/data/extensions/https-everywhere@eff.org/components/https-everywhere.js
@@ -0,0 +1,832 @@
+// LOG LEVELS ---
+
+VERB=1;
+DBUG=2;
+INFO=3;
+NOTE=4;
+WARN=5;
+
+// PREFERENCE BRANCHES
+PREFBRANCH_ROOT=0;
+PREFBRANCH_RULE_TOGGLE=1;
+
+//---------------
+
+https_domains = {}; // maps domain patterns (with at most one
+ // wildcard) to RuleSets
+
+https_everywhere_blacklist = {}; // URLs we've given up on rewriting because
+ // of redirection loops
+
+https_blacklist_domains = {}; // domains for which there is at least one
+ // blacklisted URL
+
+//
+const CI = Components.interfaces;
+const CC = Components.classes;
+const CU = Components.utils;
+const CR = Components.results;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+const CP_SHOULDPROCESS = 4;
+
+const SERVICE_CTRID = "@eff.org/https-everywhere;1";
+const SERVICE_ID=Components.ID("{32c165b4-fe5e-4964-9250-603c410631b4}");
+const SERVICE_NAME = "Encrypts your communications with a number of major websites";
+
+const LLVAR = "LogLevel";
+
+const IOS = CC["@mozilla.org/network/io-service;1"].getService(CI.nsIIOService);
+const OS = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
+const LOADER = CC["@mozilla.org/moz/jssubscript-loader;1"].getService(CI.mozIJSSubScriptLoader);
+const _INCLUDED = {};
+
+// NoScript uses this blob to include js constructs that stored in the chrome/
+// directory, but are not attached to the Firefox UI (normally, js located
+// there is attached to an Overlay and therefore is part of the UI).
+
+// Reasons for this: things in components/ directory cannot be split into
+// separate files; things in chrome/ can be
+
+const INCLUDE = function(name) {
+ if (arguments.length > 1)
+ for (var j = 0, len = arguments.length; j < len; j++)
+ INCLUDE(arguments[j]);
+ else if (!_INCLUDED[name]) {
+ // we used to try/catch here, but that was less useful because it didn't
+ // produce line numbers for syntax errors
+ LOADER.loadSubScript("chrome://https-everywhere/content/code/"
+ + name + ".js");
+ _INCLUDED[name] = true;
+ }
+};
+
+const WP_STATE_START = CI.nsIWebProgressListener.STATE_START;
+const WP_STATE_STOP = CI.nsIWebProgressListener.STATE_STOP;
+const WP_STATE_DOC = CI.nsIWebProgressListener.STATE_IS_DOCUMENT;
+const WP_STATE_START_DOC = WP_STATE_START | WP_STATE_DOC;
+const WP_STATE_RESTORING = CI.nsIWebProgressListener.STATE_RESTORING;
+
+const LF_VALIDATE_ALWAYS = CI.nsIRequest.VALIDATE_ALWAYS;
+const LF_LOAD_BYPASS_ALL_CACHES = CI.nsIRequest.LOAD_BYPASS_CACHE | CI.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE;
+
+const NS_OK = 0;
+const NS_BINDING_ABORTED = 0x804b0002;
+const NS_BINDING_REDIRECTED = 0x804b0003;
+const NS_ERROR_UNKNOWN_HOST = 0x804b001e;
+const NS_ERROR_REDIRECT_LOOP = 0x804b001f;
+const NS_ERROR_CONNECTION_REFUSED = 0x804b000e;
+const NS_ERROR_NOT_AVAILABLE = 0x804b0111;
+
+const LOG_CONTENT_BLOCK = 1;
+const LOG_CONTENT_CALL = 2;
+const LOG_CONTENT_INTERCEPT = 4;
+const LOG_CHROME_WIN = 8;
+const LOG_XSS_FILTER = 16;
+const LOG_INJECTION_CHECK = 32;
+const LOG_DOM = 64;
+const LOG_JS = 128;
+const LOG_LEAKS = 1024;
+const LOG_SNIFF = 2048;
+const LOG_CLEARCLICK = 4096;
+const LOG_ABE = 8192;
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const WHERE_UNTRUSTED = 1;
+const WHERE_TRUSTED = 2;
+const ANYWHERE = 3;
+
+const N_COHORTS = 1000;
+
+const DUMMY_OBJ = {};
+DUMMY_OBJ.wrappedJSObject = DUMMY_OBJ;
+const DUMMY_FUNC = function() {};
+const DUMMY_ARRAY = [];
+
+const EARLY_VERSION_CHECK = !("nsISessionStore" in CI && typeof(/ /) === "object");
+
+// This is probably obsolete since the switch to the channel.redirectTo API
+const OBSERVER_TOPIC_URI_REWRITE = "https-everywhere-uri-rewrite";
+
+// XXX: Better plan for this?
+// We need it to exist to make our updates of ChannelReplacement.js easier.
+var ABE = {
+ consoleDump: false,
+ log: function(str) {
+ https_everywhereLog(WARN, str);
+ }
+};
+
+function xpcom_generateQI(iids) {
+ var checks = [];
+ for each (var iid in iids) {
+ checks.push("CI." + iid.name + ".equals(iid)");
+ }
+ var src = checks.length
+ ? "if (" + checks.join(" || ") + ") return this;\n"
+ : "";
+ return new Function("iid", src + "throw Components.results.NS_ERROR_NO_INTERFACE;");
+}
+
+function xpcom_checkInterfaces(iid,iids,ex) {
+ for (var j = iids.length; j-- >0;) {
+ if (iid.equals(iids[j])) return true;
+ }
+ throw ex;
+}
+
+INCLUDE('ChannelReplacement', 'IOUtil', 'HTTPSRules', 'HTTPS', 'Thread', 'ApplicableList');
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// This is black magic for storing Expando data w/ an nsIDOMWindow
+// See http://pastebin.com/qY28Jwbv ,
+// https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIControllers
+
+StorageController.prototype = {
+ QueryInterface: XPCOMUtils.generateQI(
+ [ Components.interfaces.nsISupports,
+ Components.interfaces.nsIController ]),
+ wrappedJSObject: null, // Initialized by constructor
+ supportsCommand: function (cmd) {return (cmd == this.command);},
+ isCommandEnabled: function (cmd) {return (cmd == this.command);},
+ onEvent: function(eventName) {return true;},
+ doCommand: function() {return true;}
+};
+
+function StorageController(command) {
+ this.command = command;
+ this.data = {};
+ this.wrappedJSObject = this;
+}
+
+/*var Controller = Class("Controller", XPCOM(CI.nsIController), {
+ init: function (command, data) {
+ this.command = command;
+ this.data = data;
+ },
+ supportsCommand: function (cmd) cmd === this.command
+});*/
+
+function HTTPSEverywhere() {
+
+ // Set up logging in each component:
+ HTTPS.log = HTTPSRules.log = RuleWriter.log = this.log = https_everywhereLog;
+
+ this.log = https_everywhereLog;
+ this.wrappedJSObject = this;
+ this.https_rules = HTTPSRules;
+ this.INCLUDE=INCLUDE;
+ this.ApplicableList = ApplicableList;
+ this.browser_initialised = false; // the browser is completely loaded
+
+ this.prefs = this.get_prefs();
+ this.rule_toggle_prefs = this.get_prefs(PREFBRANCH_RULE_TOGGLE);
+
+ // We need to use observers instead of categories for FF3.0 for these:
+ // https://developer.mozilla.org/en/Observer_Notifications
+ // https://developer.mozilla.org/en/nsIObserverService.
+ // https://developer.mozilla.org/en/nsIObserver
+ // We also use the observer service to let other extensions know about URIs
+ // we rewrite.
+ this.obsService = CC["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+
+ if(this.prefs.getBoolPref("globalEnabled")){
+ this.obsService.addObserver(this, "profile-before-change", false);
+ this.obsService.addObserver(this, "profile-after-change", false);
+ this.obsService.addObserver(this, "sessionstore-windows-restored", false);
+ this.obsService.addObserver(this, "browser:purge-session-history", false);
+ }
+
+ var pref_service = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranchInternal);
+ var branch = pref_service.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
+
+ branch.addObserver("extensions.https_everywhere.enable_mixed_rulesets",
+ this, false);
+ branch.addObserver("security.mixed_content.block_active_content",
+ this, false);
+
+ return;
+}
+
+
+// nsIContentPolicy interface
+// we use numeric constants for performance sake:
+const TYPE_OTHER = 1;
+const TYPE_SCRIPT = 2;
+const TYPE_IMAGE = 3;
+const TYPE_STYLESHEET = 4;
+const TYPE_OBJECT = 5;
+const TYPE_DOCUMENT = 6;
+const TYPE_SUBDOCUMENT = 7;
+const TYPE_REFRESH = 8;
+const TYPE_XBL = 9;
+const TYPE_PING = 10;
+const TYPE_XMLHTTPREQUEST = 11;
+const TYPE_OBJECT_SUBREQUEST = 12;
+const TYPE_DTD = 13;
+const TYPE_FONT = 14;
+const TYPE_MEDIA = 15;
+// --------------
+// REJECT_SERVER = -3
+// ACCEPT = 1
+
+
+// Some of these types are known by arbitrary assertion at
+// https://bugzilla.mozilla.org/show_bug.cgi?id=677643#c47
+// TYPE_FONT was required to fix https://trac.torproject.org/projects/tor/ticket/4194
+// TYPE_SUBDOCUMENT was required to fix https://trac.torproject.org/projects/tor/ticket/4149
+// I have NO IDEA why JS won't let me use the constants above in defining this
+const shouldLoadTargets = {
+ 1 : true,
+ 3 : true,
+ 5 : true,
+ 12 : true,
+ 14 : true,
+ 7 : true
+};
+
+
+
+/*
+In recent versions of Firefox and HTTPS Everywhere, the call stack for performing an HTTP -> HTTPS rewrite looks like this:
+
+1. HTTPSEverywhere.observe() gets a callback with the "http-on-modify-request" topic, and the channel as a subject
+
+ 2. HTTPS.replaceChannel()
+
+ 3. HTTPSRules.rewrittenURI()
+
+ 4. HTTPSRules.potentiallyApplicableRulesets uses <target host=""> elements to identify relevant rulesets
+
+ foreach RuleSet:
+
+ 4. RuleSet.transformURI()
+
+ 5. RuleSet.apply() does the tests and rewrites with RegExps, returning a string
+
+ 4. RuleSet.transformURI() makes a new uri object for the destination string, if required
+
+ 2. HTTPS.replaceChannel() calls channel.redirectTo() if a redirect is needed
+
+
+In addition, the following other important tasks happen along the way:
+
+HTTPSEverywhere.observe() aborts if there is a redirect loop
+ finds a reference to the ApplicableList or alist that represents the toolbar context menu
+
+HTTPS.replaceChannel() notices redirect loops (and used to do much more complex XPCOM API work in the NoScript-based past)
+
+HTTPSRules.rewrittenURI() works around weird URI types like about: and http://user:pass@example.com/
+ and notifies the alist of what it should display for each ruleset
+
+*/
+
+// This defines for Mozilla what stuff HTTPSEverywhere will implement.
+
+// ChannelEventSink used to be necessary in order to handle redirects (eg
+// HTTP redirects) correctly. It may now be obsolete? XXX
+
+HTTPSEverywhere.prototype = {
+ prefs: null,
+ // properties required for XPCOM registration:
+ classDescription: SERVICE_NAME,
+ classID: SERVICE_ID,
+ contractID: SERVICE_CTRID,
+
+ _xpcom_factory: {
+ createInstance: function (outer, iid) {
+ if (outer != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ if (!HTTPSEverywhere.instance)
+ HTTPSEverywhere.instance = new HTTPSEverywhere();
+ return HTTPSEverywhere.instance.QueryInterface(iid);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [ Components.interfaces.nsISupports,
+ Components.interfaces.nsIModule,
+ Components.interfaces.nsIFactory ])
+ },
+
+ // [optional] an array of categories to register this component in.
+ _xpcom_categories: [
+ {
+ category: "app-startup",
+ },
+ ],
+
+ // QueryInterface implementation, e.g. using the generateQI helper
+ QueryInterface: XPCOMUtils.generateQI(
+ [ Components.interfaces.nsIObserver,
+ Components.interfaces.nsISupports,
+ Components.interfaces.nsISupportsWeakReference,
+ Components.interfaces.nsIWebProgressListener,
+ Components.interfaces.nsIWebProgressListener2,
+ Components.interfaces.nsIChannelEventSink ]),
+
+ wrappedJSObject: null, // Initialized by constructor
+
+ getWeakReference: function () {
+ return Components.utils.getWeakReference(this);
+ },
+
+ // An "expando" is an attribute glued onto something. From NoScript.
+ getExpando: function(domWin, key) {
+ var c = domWin.controllers.getControllerForCommand("https-everywhere-storage");
+ try {
+ if (c) {
+ c = c.wrappedJSObject;
+ //this.log(DBUG, "Found a controller, returning data");
+ return c.data[key];
+ } else {
+ this.log(INFO, "No controller attached to " + domWin);
+ return null;
+ }
+ } catch(e) {
+ // Firefox 3.5
+ this.log(WARN,"exception in getExpando");
+ this.getExpando = this.getExpando_old;
+ this.setExpando = this.setExpando_old;
+ return this.getExpando_old(domWin, key, null);
+ }
+ },
+ setExpando: function(domWin, key, value) {
+ var c = domWin.controllers.getControllerForCommand("https-everywhere-storage");
+ try {
+ if (!c) {
+ this.log(DBUG, "Appending new StorageController for " + domWin);
+ c = new StorageController("https-everywhere-storage");
+ domWin.controllers.appendController(c);
+ } else {
+ c = c.wrappedJSObject;
+ }
+ c.data[key] = value;
+ } catch(e) {
+ this.log(WARN,"exception in setExpando");
+ this.getExpando = this.getExpando_old;
+ this.setExpando = this.setExpando_old;
+ this.setExpando_old(domWin, key, value);
+ }
+ },
+
+ // This method is straight out of NoScript... we fall back to it in FF 3.*?
+ getExpando_old: function(domWin, key, defValue) {
+ var domObject = domWin.document;
+ return domObject && domObject.__httpsEStorage && domObject.__httpsEStorage[key] ||
+ (defValue ? this.setExpando(domObject, key, defValue) : null);
+ },
+ setExpando_old: function(domWin, key, value) {
+ var domObject = domWin.document;
+ if (!domObject) return null;
+ if (!domObject.__httpsEStorage) domObject.__httpsEStorage = {};
+ if (domObject.__httpsEStorage) domObject.__httpsEStorage[key] = value;
+ else this.log(WARN, "Warning: cannot set expando " + key + " to value " + value);
+ return value;
+ },
+
+ // We use onLocationChange to make a fresh list of rulesets that could have
+ // applied to the content in the current page (the "applicable list" is used
+ // for the context menu in the UI). This will be appended to as various
+ // content is embedded / requested by JavaScript.
+ onLocationChange: function(wp, req, uri) {
+ if (wp instanceof CI.nsIWebProgress) {
+ if (!this.newApplicableListForDOMWin(wp.DOMWindow))
+ this.log(WARN,"Something went wrong in onLocationChange");
+ } else {
+ this.log(WARN,"onLocationChange: no nsIWebProgress");
+ }
+ },
+
+ getWindowForChannel: function(channel) {
+ // Obtain an nsIDOMWindow from a channel
+ let loadContext;
+ try {
+ loadContext = channel.notificationCallbacks.getInterface(CI.nsILoadContext);
+ } catch(e) {
+ try {
+ loadContext = channel.loadGroup.notificationCallbacks.getInterface(CI.nsILoadContext);
+ } catch(e) {
+ this.log(NOTE, "No loadContext for " + channel.URI.spec);
+ return null;
+ }
+ }
+
+ let domWin = loadContext.associatedWindow;
+ if (!domWin) {
+ this.log(NOTE, "failed to get DOMWin for " + channel.URI.spec);
+ return null;
+ }
+
+ domWin = domWin.top;
+ return domWin;
+ },
+
+ // the lists get made when the urlbar is loading something new, but they
+ // need to be appended to with reference only to the channel
+ getApplicableListForChannel: function(channel) {
+ var domWin = this.getWindowForChannel(channel);
+ return this.getApplicableListForDOMWin(domWin, "on-modify-request w " + domWin);
+ },
+
+ newApplicableListForDOMWin: function(domWin) {
+ if (!domWin || !(domWin instanceof CI.nsIDOMWindow)) {
+ this.log(WARN, "Get alist without domWin");
+ return null;
+ }
+ var dw = domWin.top;
+ var alist = new ApplicableList(this.log,dw.document,dw);
+ this.setExpando(dw,"applicable_rules",alist);
+ return alist;
+ },
+
+ getApplicableListForDOMWin: function(domWin, where) {
+ if (!domWin || !(domWin instanceof CI.nsIDOMWindow)) {
+ //this.log(WARN, "Get alist without domWin");
+ return null;
+ }
+ var dw = domWin.top;
+ var alist= this.getExpando(dw,"applicable_rules",null);
+ if (alist) {
+ //this.log(DBUG,"get AL success in " + where);
+ return alist;
+ } else {
+ //this.log(DBUG, "Making new AL in getApplicableListForDOMWin in " + where);
+ alist = new ApplicableList(this.log,dw.document,dw);
+ this.setExpando(dw,"applicable_rules",alist);
+ }
+ return alist;
+ },
+
+ observe: function(subject, topic, data) {
+ // Top level glue for the nsIObserver API
+ var channel = subject;
+ //this.log(VERB,"Got observer topic: "+topic);
+
+ if (topic == "http-on-modify-request") {
+ if (!(channel instanceof CI.nsIHttpChannel)) return;
+
+ this.log(DBUG,"Got http-on-modify-request: "+channel.URI.spec);
+ var lst = this.getApplicableListForChannel(channel); // null if no window is associated (ex: xhr)
+ if (channel.URI.spec in https_everywhere_blacklist) {
+ this.log(DBUG, "Avoiding blacklisted " + channel.URI.spec);
+ if (lst) lst.breaking_rule(https_everywhere_blacklist[channel.URI.spec]);
+ else this.log(NOTE,"Failed to indicate breakage in content menu");
+ return;
+ }
+ HTTPS.replaceChannel(lst, channel);
+ } else if (topic == "http-on-examine-response") {
+ this.log(DBUG, "Got http-on-examine-response @ "+ (channel.URI ? channel.URI.spec : '') );
+ HTTPS.handleSecureCookies(channel);
+ } else if (topic == "http-on-examine-merged-response") {
+ this.log(DBUG, "Got http-on-examine-merged-response ");
+ HTTPS.handleSecureCookies(channel);
+ } else if (topic == "cookie-changed") {
+ // Javascript can add cookies via document.cookie that are insecure.
+ if (data == "added" || data == "changed") {
+ // subject can also be an nsIArray! bleh.
+ try {
+ subject.QueryInterface(CI.nsIArray);
+ var elems = subject.enumerate();
+ while (elems.hasMoreElements()) {
+ var cookie = elems.getNext()
+ .QueryInterface(CI.nsICookie2);
+ if (!cookie.isSecure) {
+ HTTPS.handleInsecureCookie(cookie);
+ }
+ }
+ } catch(e) {
+ subject.QueryInterface(CI.nsICookie2);
+ if(!subject.isSecure) {
+ HTTPS.handleInsecureCookie(subject);
+ }
+ }
+ }
+ } else if (topic == "profile-before-change") {
+ this.log(INFO, "Got profile-before-change");
+ var catman = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ catman.deleteCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, true);
+ Thread.hostRunning = false;
+ } else if (topic == "profile-after-change") {
+ this.log(DBUG, "Got profile-after-change");
+
+ if(this.prefs.getBoolPref("globalEnabled")){
+ OS.addObserver(this, "cookie-changed", false);
+ OS.addObserver(this, "http-on-modify-request", false);
+ OS.addObserver(this, "http-on-examine-merged-response", false);
+ OS.addObserver(this, "http-on-examine-response", false);
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.addProgressListener(this, CI.nsIWebProgress.NOTIFY_LOCATION);
+ this.log(INFO,"ChannelReplacement.supported = "+ChannelReplacement.supported);
+
+ HTTPSRules.init();
+
+ Thread.hostRunning = true;
+ var catman = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ // hook on redirections (non persistent, otherwise crashes on 1.8.x)
+ catman.addCategoryEntry("net-channel-event-sinks", SERVICE_CTRID,
+ SERVICE_CTRID, false, true);
+ }
+ } else if (topic == "sessionstore-windows-restored") {
+ this.log(DBUG,"Got sessionstore-windows-restored");
+ this.maybeShowObservatoryPopup();
+ this.browser_initialised = true;
+ } else if (topic == "nsPref:changed") {
+ // If the user toggles the Mixed Content Blocker settings, reload the rulesets
+ // to enable/disable the mixedcontent ones
+
+ // this pref gets set to false and then true during FF 26 startup!
+ // so do nothing if we're being notified during startup
+ if (!this.browser_initialised)
+ return;
+ switch (data) {
+ case "security.mixed_content.block_active_content":
+ case "extensions.https_everywhere.enable_mixed_rulesets":
+ var p = CC["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefBranch);
+ var val = p.getBoolPref("security.mixed_content.block_active_content");
+ this.log(INFO,"nsPref:changed for "+data + " to " + val);
+ HTTPSRules.init();
+ break;
+ }
+ } else if (topic == "browser:purge-session-history") {
+ // The list of rulesets that have been loaded from the sqlite DB
+ // constitutes a parallel history store, so we have to clear it.
+ this.log(DBUG, "History cleared, reloading HTTPSRules to avoid information leak.");
+ HTTPSRules.init();
+ }
+ return;
+ },
+
+ maybeShowObservatoryPopup: function() {
+ // Show the popup at most once. Users who enabled the Observatory before
+ // a version that would have shown it to them, don't need to see it
+ // again.
+ var ssl_observatory = CC["@eff.org/ssl-observatory;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ var shown = ssl_observatory.myGetBoolPref("popup_shown");
+ var enabled = ssl_observatory.myGetBoolPref("enabled");
+ var that = this;
+ var obs_popup_callback = function(result) {
+ if (result) that.log(INFO, "Got positive proxy test.");
+ else that.log(INFO, "Got negative proxy text.");
+ // We are now ready to show the popup in its most informative state
+ that.chrome_opener("chrome://https-everywhere/content/observatory-popup.xul");
+ };
+ if (!shown && !enabled)
+ ssl_observatory.registerProxyTestNotification(obs_popup_callback);
+
+ if (shown && enabled)
+ this.maybeCleanupObservatoryPrefs(ssl_observatory);
+ },
+
+ maybeCleanupObservatoryPrefs: function(ssl_observatory) {
+ // Recover from a past UI processing bug that would leave the Obsevatory
+ // accidentally disabled for some users
+ // https://trac.torproject.org/projects/tor/ticket/10728
+ var clean = ssl_observatory.myGetBoolPref("clean_config");
+ if (clean) return;
+
+ // unchanged: returns true if a pref has not been modified
+ var unchanged = function(p){return !ssl_observatory.prefs.prefHasUserValue("extensions.https_everywhere._observatory."+p)};
+ var cleanup_obsprefs_callback = function(tor_avail) {
+ // we only run this once
+ ssl_observatory.prefs.setBoolPref("extensions.https_everywhere._observatory.clean_config", true);
+ if (!tor_avail) {
+ // use_custom_proxy is the variable that is often false when it should be true;
+ if (!ssl_observatory.myGetBoolPref("use_custom_proxy")) {
+ // however don't do anything if any of the prefs have been set by the user
+ if (unchanged("alt_roots") && unchanged("self_signed") && unchanged ("send_asn") && unchanged("priv_dns")) {
+ ssl_observatory.prefs.setBoolPref("extensions.https_everywhere._observatory.use_custom_proxy", true);
+ }
+ }
+ }
+ }
+ ssl_observatory.registerProxyTestNotification(cleanup_obsprefs_callback);
+ },
+
+
+ getExperimentalFeatureCohort: function() {
+ // This variable is used for gradually turning on features for testing and
+ // scalability purposes. It is a random integer [0,N_COHORTS) generated
+ // once and stored thereafter.
+ //
+ // This is not currently used/called in the development branch
+ var cohort;
+ try {
+ cohort = this.prefs.getIntPref("experimental_feature_cohort");
+ } catch(e) {
+ cohort = Math.round(Math.random() * N_COHORTS);
+ this.prefs.setIntPref("experimental_feature_cohort", cohort);
+ }
+ return cohort;
+ },
+
+ // nsIChannelEventSink implementation
+ // XXX This was here for rewrites in the past. Do we still need it?
+ onChannelRedirect: function(oldChannel, newChannel, flags) {
+ const uri = newChannel.URI;
+ this.log(DBUG,"Got onChannelRedirect to "+uri.spec);
+ if (!(newChannel instanceof CI.nsIHttpChannel)) {
+ this.log(DBUG, newChannel + " is not an instance of nsIHttpChannel");
+ return;
+ }
+ var alist = this.juggleApplicableListsDuringRedirection(oldChannel, newChannel);
+ HTTPS.replaceChannel(alist,newChannel);
+ },
+
+ juggleApplicableListsDuringRedirection: function(oldChannel, newChannel) {
+ // If the new channel doesn't yet have a list of applicable rulesets, start
+ // with the old one because that's probably a better representation of how
+ // secure the load process was for this page
+ var domWin = this.getWindowForChannel(oldChannel);
+ var old_alist = null;
+ if (domWin)
+ old_alist = this.getExpando(domWin,"applicable_rules", null);
+ domWin = this.getWindowForChannel(newChannel);
+ if (!domWin) return null;
+ var new_alist = this.getExpando(domWin,"applicable_rules", null);
+ if (old_alist && !new_alist) {
+ new_alist = old_alist;
+ this.setExpando(domWin,"applicable_rules",new_alist);
+ } else if (!new_alist) {
+ new_alist = new ApplicableList(this.log, domWin.document, domWin);
+ this.setExpando(domWin,"applicable_rules",new_alist);
+ }
+ return new_alist;
+ },
+
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
+ this.onChannelRedirect(oldChannel, newChannel, flags);
+ callback.onRedirectVerifyCallback(0);
+ },
+
+ get_prefs: function(prefBranch) {
+ if(!prefBranch) prefBranch = PREFBRANCH_ROOT;
+
+ // get our preferences branch object
+ // FIXME: Ugly hack stolen from https
+ var branch_name;
+ if(prefBranch == PREFBRANCH_RULE_TOGGLE)
+ branch_name = "extensions.https_everywhere.rule_toggle.";
+ else
+ branch_name = "extensions.https_everywhere.";
+ var o_prefs = false;
+ var o_branch = false;
+ // this function needs to be called from inside https_everywhereLog, so
+ // it needs to do its own logging...
+ var econsole = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+
+ o_prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+
+ if (!o_prefs)
+ {
+ econsole.logStringMessage("HTTPS Everywhere: Failed to get preferences-service!");
+ return false;
+ }
+
+ o_branch = o_prefs.getBranch(branch_name);
+ if (!o_branch)
+ {
+ econsole.logStringMessage("HTTPS Everywhere: Failed to get prefs branch!");
+ return false;
+ }
+
+ if(prefBranch == PREFBRANCH_ROOT) {
+ // make sure there's an entry for our log level
+ try {
+ o_branch.getIntPref(LLVAR);
+ } catch (e) {
+ econsole.logStringMessage("Creating new about:config https_everywhere.LogLevel variable");
+ o_branch.setIntPref(LLVAR, WARN);
+ }
+ }
+
+ return o_branch;
+ },
+
+ chrome_opener: function(uri, args) {
+ // we don't use window.open, because we need to work around TorButton's
+ // state control
+ args = args || 'chrome,centerscreen';
+ return CC['@mozilla.org/appshell/window-mediator;1']
+ .getService(CI.nsIWindowMediator)
+ .getMostRecentWindow('navigator:browser')
+ .open(uri,'', args );
+ },
+
+ tab_opener: function(uri) {
+ var gb = CC['@mozilla.org/appshell/window-mediator;1']
+ .getService(CI.nsIWindowMediator)
+ .getMostRecentWindow('navigator:browser')
+ .gBrowser;
+ var tab = gb.addTab(uri);
+ gb.selectedTab = tab;
+ return tab;
+ },
+
+ toggleEnabledState: function() {
+ if(this.prefs.getBoolPref("globalEnabled")){
+ try{
+ this.obsService.removeObserver(this, "profile-before-change");
+ this.obsService.removeObserver(this, "profile-after-change");
+ this.obsService.removeObserver(this, "sessionstore-windows-restored");
+ OS.removeObserver(this, "cookie-changed");
+ OS.removeObserver(this, "http-on-modify-request");
+ OS.removeObserver(this, "http-on-examine-merged-response");
+ OS.removeObserver(this, "http-on-examine-response");
+
+ var catman = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ catman.deleteCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, true);
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.removeProgressListener(this);
+
+ this.prefs.setBoolPref("globalEnabled", false);
+ }
+ catch(e){
+ this.log(WARN, "Couldn't remove observers: " + e);
+ }
+ }
+ else{
+ try{
+ this.obsService.addObserver(this, "profile-before-change", false);
+ this.obsService.addObserver(this, "profile-after-change", false);
+ this.obsService.addObserver(this, "sessionstore-windows-restored", false);
+ OS.addObserver(this, "cookie-changed", false);
+ OS.addObserver(this, "http-on-modify-request", false);
+ OS.addObserver(this, "http-on-examine-merged-response", false);
+ OS.addObserver(this, "http-on-examine-response", false);
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.addProgressListener(this, CI.nsIWebProgress.NOTIFY_LOCATION);
+
+ this.log(INFO,"ChannelReplacement.supported = "+ChannelReplacement.supported);
+
+ if(!Thread.hostRunning)
+ Thread.hostRunning = true;
+
+ var catman = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ // hook on redirections (non persistent, otherwise crashes on 1.8.x)
+ catman.addCategoryEntry("net-channel-event-sinks", SERVICE_CTRID,
+ SERVICE_CTRID, false, true);
+
+ HTTPSRules.init();
+ this.prefs.setBoolPref("globalEnabled", true);
+ }
+ catch(e){
+ this.log(WARN, "Couldn't add observers: " + e);
+ }
+ }
+ }
+};
+
+var prefs = 0;
+var econsole = 0;
+function https_everywhereLog(level, str) {
+ if (prefs == 0) {
+ prefs = HTTPSEverywhere.instance.get_prefs();
+ econsole = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ }
+ try {
+ var threshold = prefs.getIntPref(LLVAR);
+ } catch (e) {
+ econsole.logStringMessage( "HTTPS Everywhere: Failed to read about:config LogLevel");
+ threshold = WARN;
+ }
+ if (level >= threshold) {
+ dump("HTTPS Everywhere: "+str+"\n");
+ econsole.logStringMessage("HTTPS Everywhere: " +str);
+ }
+}
+
+/**
+* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
+* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
+*/
+if (XPCOMUtils.generateNSGetFactory)
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([HTTPSEverywhere]);
+else
+ var NSGetModule = XPCOMUtils.generateNSGetModule([HTTPSEverywhere]);
+
+/* vim: set tabstop=4 expandtab: */
diff --git a/data/extensions/https-everywhere@eff.org/components/ssl-observatory.js b/data/extensions/https-everywhere@eff.org/components/ssl-observatory.js
new file mode 100644
index 0000000..7b301d1
--- /dev/null
+++ b/data/extensions/https-everywhere@eff.org/components/ssl-observatory.js
@@ -0,0 +1,1026 @@
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+const CI = Components.interfaces;
+const CC = Components.classes;
+const CR = Components.results;
+const CU = Components.utils;
+
+// Log levels
+VERB=1;
+DBUG=2;
+INFO=3;
+NOTE=4;
+WARN=5;
+
+BASE_REQ_SIZE=4096;
+MAX_OUTSTANDING = 20; // Max # submission XHRs in progress
+MAX_DELAYED = 32; // Max # XHRs are waiting around to be sent or retried
+TIMEOUT = 60000;
+
+ASN_PRIVATE = -1; // Do not record the ASN this cert was seen on
+ASN_IMPLICIT = -2; // ASN can be learned from connecting IP
+ASN_UNKNOWABLE = -3; // Cert was seen in the absence of [trustworthy] Internet access
+
+// XXX: We should make the _observatory tree relative.
+LLVAR="extensions.https_everywhere.LogLevel";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+
+const OS = Cc['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
+
+const SERVICE_CTRID = "@eff.org/ssl-observatory;1";
+const SERVICE_ID=Components.ID("{0f9ab521-986d-4ad8-9c1f-6934e195c15c}");
+const SERVICE_NAME = "Anonymously Submits SSL certificates to EFF for security auditing.";
+const LOADER = CC["@mozilla.org/moz/jssubscript-loader;1"].getService(CI.mozIJSSubScriptLoader);
+
+const _INCLUDED = {};
+
+const INCLUDE = function(name) {
+ if (arguments.length > 1)
+ for (var j = 0, len = arguments.length; j < len; j++)
+ INCLUDE(arguments[j]);
+ else if (!_INCLUDED[name]) {
+ try {
+ LOADER.loadSubScript("chrome://https-everywhere/content/code/"
+ + name + ".js");
+ _INCLUDED[name] = true;
+ } catch(e) {
+ dump("INCLUDE " + name + ": " + e + "\n");
+ }
+ }
+};
+
+INCLUDE('Root-CAs');
+INCLUDE('sha256');
+INCLUDE('X509ChainWhitelist');
+INCLUDE('NSS');
+
+function SSLObservatory() {
+ this.prefs = CC["@mozilla.org/preferences-service;1"]
+ .getService(CI.nsIPrefBranch);
+
+ try {
+ // Check for torbutton
+ this.tor_logger = CC["@torproject.org/torbutton-logger;1"]
+ .getService(CI.nsISupports).wrappedJSObject;
+ this.torbutton_installed = true;
+ } catch(e) {
+ this.torbutton_installed = false;
+ }
+
+ this.HTTPSEverywhere = CC["@eff.org/https-everywhere;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ /* The proxy test result starts out null until the test is attempted.
+ * This is for UI notification purposes */
+ this.proxy_test_successful = null;
+ this.proxy_test_callback = null;
+ this.cto_url = "https://check.torproject.org/?TorButton=true";
+ // a regexp to match the above URL
+ this.cto_regexp = RegExp("^https://check\\.torproject\\.org/");
+
+ this.public_roots = root_ca_hashes;
+
+ // Clear these on cookies-cleared observer event
+ this.already_submitted = {};
+ this.delayed_submissions = {};
+
+ // Figure out the url to submit to
+ this.submit_host = null;
+ this.findSubmissionTarget();
+
+ // Used to track current number of pending requests to the server
+ this.current_outstanding_requests = 0;
+
+ // We can't always know private browsing state per request, sometimes
+ // we have to guess based on what we've seen in the past
+ this.everSeenPrivateBrowsing = false;
+
+ // Generate nonce to append to url, to catch in nsIProtocolProxyFilter
+ // and to protect against CSRF
+ this.csrf_nonce = "#"+Math.random().toString()+Math.random().toString();
+
+ this.compatJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+
+ var pref_service = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranchInternal);
+ var branch = pref_service.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
+
+ branch.addObserver("extensions.https_everywhere._observatory.enabled",
+ this, false);
+
+ if (this.myGetBoolPref("enabled")) {
+ OS.addObserver(this, "cookie-changed", false);
+ OS.addObserver(this, "http-on-examine-response", false);
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.addProgressListener(this,
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+ }
+
+ // Register protocolproxyfilter
+ this.pps = CC["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(CI.nsIProtocolProxyService);
+
+ this.pps.registerFilter(this, 0);
+ this.wrappedJSObject = this;
+
+ this.client_asn = ASN_PRIVATE;
+ if (this.myGetBoolPref("send_asn"))
+ this.setupASNWatcher();
+
+ try {
+ NSS.initialize("");
+ } catch(e) {
+ this.log(WARN, "Failed to initialize NSS component:" + e);
+ }
+
+ this.testProxySettings();
+
+ this.log(DBUG, "Loaded observatory component!");
+}
+
+SSLObservatory.prototype = {
+ // QueryInterface implementation, e.g. using the generateQI helper
+ QueryInterface: XPCOMUtils.generateQI(
+ [ CI.nsIObserver,
+ CI.nsIProtocolProxyFilter,
+ //CI.nsIWifiListener,
+ CI.nsIWebProgressListener,
+ CI.nsISupportsWeakReference,
+ CI.nsIInterfaceRequestor]),
+
+ wrappedJSObject: null, // Initialized by constructor
+
+ // properties required for XPCOM registration:
+ classDescription: SERVICE_NAME,
+ classID: SERVICE_ID,
+ contractID: SERVICE_CTRID,
+
+ // https://developer.mozilla.org/En/How_to_check_the_security_state_of_an_XMLHTTPRequest_over_SSL
+ getSSLCertChain: function(channel) {
+ try {
+ // Do we have a valid channel argument?
+ if (!channel instanceof Ci.nsIChannel) {
+ return null;
+ }
+ var secInfo = channel.securityInfo;
+
+ // Print general connection security state
+ if (secInfo instanceof Ci.nsITransportSecurityInfo) {
+ secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ } else {
+ return null;
+ }
+
+ if (secInfo instanceof Ci.nsISSLStatusProvider) {
+ return secInfo.QueryInterface(Ci.nsISSLStatusProvider).
+ SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+ }
+ return null;
+ } catch(err) {
+ return null;
+ }
+ },
+
+ findSubmissionTarget: function() {
+ // Compute the URL that the Observatory will currently submit to
+ var host = this.prefs.getCharPref("extensions.https_everywhere._observatory.server_host");
+ // Rebuild the regexp iff the host has changed
+ if (host != this.submit_host) {
+ this.submit_host = host;
+ this.submit_url = "https://" + host + "/submit_cert";
+ this.submission_regexp = RegExp("^" + this.regExpEscape(this.submit_url));
+ }
+ },
+
+ regExpEscape: function(s) {
+ // Borrowed from the Closure Library,
+ // https://closure-library.googlecode.com/svn/docs/closure_goog_string_string.js.source.html
+ return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08');
+ },
+
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ this.log(NOTE, "cert warning for " + targetSite);
+ if (targetSite == "observatory.eff.org") {
+ this.log(WARN, "Surpressing observatory warning");
+ return true;
+ }
+ return false;
+ },
+
+ setupASNWatcher: function() {
+ this.getClientASN();
+ this.max_ap = null;
+
+ // we currently do not actually do *any* ASN watching from the client
+ // (in other words, the db will not have ASNs for certs submitted
+ // through Tor, even if the user checks the "send ASN" option)
+ // all of this code for guessing at changes in our public IP via WiFi hints
+ // is therefore disabled
+ /*
+ // Observe network changes to get new ASNs
+ OS.addObserver(this, "network:offline-status-changed", false);
+ var pref_service = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranchInternal);
+ var proxy_branch = pref_service.QueryInterface(Ci.nsIPrefBranchInternal);
+ proxy_branch.addObserver("network.proxy", this, false);
+
+ try {
+ var wifi_service = Cc["@mozilla.org/wifi/monitor;1"].getService(Ci.nsIWifiMonitor);
+ wifi_service.startWatching(this);
+ } catch(e) {
+ this.log(INFO, "Failed to register ASN change monitor: "+e);
+ }*/
+ },
+
+ stopASNWatcher: function() {
+ this.client_asn = ASN_PRIVATE;
+ /*
+ // unhook the observers we registered above
+ OS.removeObserver(this, "network:offline-status-changed");
+ var pref_service = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranchInternal);
+ var proxy_branch = pref_service.QueryInterface(Ci.nsIPrefBranchInternal);
+ proxy_branch.removeObserver(this, "network.proxy");
+ try {
+ var wifi_service = Cc["@mozilla.org/wifi/monitor;1"].getService(Ci.nsIWifiMonitor);
+ wifi_service.stopWatching(this);
+ } catch(e) {
+ this.log(WARN, "Failed to stop wifi state monitor: "+e);
+ }*/
+ },
+
+ getClientASN: function() {
+ // Fetch a new client ASN..
+ if (!this.myGetBoolPref("send_asn")) {
+ this.client_asn = ASN_PRIVATE;
+ return;
+ }
+ else if (!this.torbutton_installed) {
+ this.client_asn = ASN_IMPLICIT;
+ return;
+ }
+ // XXX As a possible base case: the user is running Tor, is not using
+ // bridges, and has send_asn enabled: should we ping an eff.org URL to
+ // learn our ASN?
+ return;
+ },
+
+ /*
+ // Wifi status listener
+ onChange: function(accessPoints) {
+ try {
+ var max_ap = accessPoints[0].mac;
+ } catch(e) {
+ return null; // accessPoints[0] is undefined
+ }
+ var max_signal = accessPoints[0].signal;
+ var old_max_present = false;
+ for (var i=0; i<accessPoints.length; i++) {
+ if (accessPoints[i].mac == this.max_ap) {
+ old_max_present = true;
+ }
+ if (accessPoints[i].signal > max_signal) {
+ max_ap = accessPoints[i].mac;
+ max_signal = accessPoints[i].signal;
+ }
+ }
+ this.max_ap = max_ap;
+ if (!old_max_present) {
+ this.log(INFO, "Old access point is out of range. Getting new ASN");
+ this.getClientASN();
+ } else {
+ this.log(DBUG, "Old access point is still in range.");
+ }
+ },
+
+ // Wifi status listener
+ onError: function(value) {
+ // XXX: Do we care?
+ this.log(NOTE, "ASN change observer got an error: "+value);
+ this.getClientASN();
+ },
+ */
+
+ ourFingerprint: function(cert) {
+ // Calculate our custom fingerprint from an nsIX509Cert
+ return (cert.md5Fingerprint+cert.sha1Fingerprint).replace(":", "", "g");
+ },
+
+ // onSecurity is used to listen for bad cert warnings
+ // There is also onSecurityStateChange, but it does not handle subdocuments. See git
+ // history for an implementation stub.
+ onStateChange: function(aProgress, aRequest, aState, aStatus) {
+ if (!aRequest) return;
+ var chan = null;
+ try {
+ chan = aRequest.QueryInterface(Ci.nsIHttpChannel);
+ } catch(e) {
+ return;
+ }
+ if (chan) {
+ if (!this.observatoryActive(chan)) return;
+ var certchain = this.getSSLCertChain(chan);
+ if (certchain) {
+ this.log(INFO, "Got state cert chain for "
+ + chan.originalURI.spec + "->" + chan.URI.spec + ", state: " + aState);
+ var warning = true;
+ this.submitCertChainForChannel(certchain, chan, warning);
+ }
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == "cookie-changed" && data == "cleared") {
+ this.already_submitted = {};
+ this.delayed_submissions = {};
+ this.log(INFO, "Cookies were cleared. Purging list of pending and already submitted certs");
+ return;
+ }
+
+ if ("http-on-examine-response" == topic) {
+ var channel = subject;
+ if (!this.observatoryActive(channel)) return;
+
+ var certchain = this.getSSLCertChain(subject);
+ var warning = false;
+ this.submitCertChainForChannel(certchain, channel, warning);
+ }
+
+ if (topic == "network:offline-status-changed" && data == "online") {
+ this.log(INFO, "Browser back online. Getting new ASN.");
+ this.getClientASN();
+ return;
+ }
+
+ if (topic == "nsPref:changed") {
+ // If the user toggles the SSL Observatory settings, we need to add or remove
+ // our observers
+ switch (data) {
+ case "network.proxy.ssl":
+ case "network.proxy.ssl_port":
+ case "network.proxy.socks":
+ case "network.proxy.socks_port":
+ // XXX: We somehow need to only call this once. Right now, we'll make
+ // like 3 calls to getClientASN().. The only thing I can think
+ // of is a timer...
+ this.log(INFO, "Proxy settings have changed. Getting new ASN");
+ this.getClientASN();
+ break;
+ case "extensions.https_everywhere._observatory.enabled":
+ if (this.myGetBoolPref("enabled")) {
+ this.pps.registerFilter(this, 0);
+ OS.addObserver(this, "cookie-changed", false);
+ OS.addObserver(this, "http-on-examine-response", false);
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.addProgressListener(this,
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+ this.log(INFO,"SSL Observatory is now enabled via pref change!");
+ } else {
+ try {
+ this.pps.unregisterFilter(this);
+ OS.removeObserver(this, "cookie-changed");
+ OS.removeObserver(this, "http-on-examine-response");
+
+ var dls = CC['@mozilla.org/docloaderservice;1']
+ .getService(CI.nsIWebProgress);
+ dls.removeProgressListener(this);
+ this.log(INFO,"SSL Observatory is now disabled via pref change!");
+ } catch(e) {
+ this.log(WARN, "Removing SSL Observatory observers failed: "+e);
+ }
+ }
+ break;
+ }
+ return;
+ }
+
+ },
+
+ submitCertChainForChannel: function(certchain, channel, warning) {
+ if (!certchain) {
+ return;
+ }
+ var host_ip = "-1";
+ var httpchannelinternal = channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ try {
+ host_ip = httpchannelinternal.remoteAddress;
+ } catch(e) {
+ this.log(INFO, "Could not get server IP address.");
+ }
+
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ var chainEnum = certchain.getChain();
+ var chainArray = [];
+ var chainArrayFpStr = '';
+ var fps = [];
+ for(var i = 0; i < chainEnum.length; i++) {
+ var cert = chainEnum.queryElementAt(i, Ci.nsIX509Cert);
+ chainArray.push(cert);
+ var fp = this.ourFingerprint(cert);
+ fps.push(fp);
+ chainArrayFpStr = chainArrayFpStr + fp;
+ }
+ var chain_hash = sha256_digest(chainArrayFpStr).toUpperCase();
+ this.log(INFO, "SHA-256 hash of cert chain for "+new String(channel.URI.host)+" is "+ chain_hash);
+
+ if(!this.myGetBoolPref("use_whitelist")) {
+ this.log(WARN, "Not using whitelist to filter cert chains.");
+ }
+ else if (this.isChainWhitelisted(chain_hash)) {
+ this.log(INFO, "This cert chain is whitelisted. Not submitting.");
+ return;
+ }
+ else {
+ this.log(INFO, "Cert chain is NOT whitelisted. Proceeding with submission.");
+ }
+
+ if (channel.URI.port == -1) {
+ this.submitChainArray(chainArray, fps, new String(channel.URI.host), channel, host_ip, warning, false);
+ } else {
+ this.submitChainArray(chainArray, fps, channel.URI.host+":"+channel.URI.port, channel, host_ip, warning, false);
+ }
+ },
+
+ observatoryActive: function(channel) {
+
+ if (!this.myGetBoolPref("enabled"))
+ return false;
+
+ if (this.torbutton_installed && this.proxy_test_successful) {
+ // Allow Tor users to choose if they want to submit
+ // during tor and/or non-tor
+ if (this.myGetBoolPref("submit_during_tor") &&
+ this.prefs.getBoolPref("extensions.torbutton.tor_enabled"))
+ return true;
+
+ if (this.myGetBoolPref("submit_during_nontor") &&
+ !this.prefs.getBoolPref("extensions.torbutton.tor_enabled"))
+ return true;
+
+ return false;
+ }
+
+ if (this.proxy_test_successful) {
+ return true;
+ } else if (this.myGetBoolPref("use_custom_proxy")) {
+ // no torbutton; the custom proxy is probably the user opting to
+ // submit certs without strong anonymisation. Because the
+ // anonymisation is weak, we avoid submitting during private browsing
+ // mode.
+ var pbm = this.inPrivateBrowsingMode(channel);
+ this.log(DBUG, "Private browsing mode: " + pbm);
+ return !pbm;
+ }
+ },
+
+ inPrivateBrowsingMode: function(channel) {
+ // In classic firefox fashion, there are multiple versions of this API
+ // https://developer.mozilla.org/EN/docs/Supporting_per-window_private_browsing
+ try {
+ // Firefox 20+, this state is per-window;
+ // should raise an exception on FF < 20
+ CU.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ if (!(channel instanceof CI.nsIHttpChannel)) {
+ this.log(NOTE, "observatoryActive() without a channel");
+ // This is a windowless request. We cannot tell if private browsing
+ // applies. Conservatively, if we have ever seen PBM, it might be
+ // active now
+ return this.everSeenPrivateBrowsing;
+ }
+ var win = this.HTTPSEverywhere.getWindowForChannel(channel);
+ if (!win) return this.everSeenPrivateBrowsing; // windowless request
+
+ if (PrivateBrowsingUtils.isWindowPrivate(win)) {
+ this.everSeenPrivateBrowsing = true;
+ return true;
+ }
+ } catch (e) {
+ // Firefox < 20, this state is global
+ try {
+ var pbs = CC["@mozilla.org/privatebrowsing;1"].getService(CI.nsIPrivateBrowsingService);
+ if (pbs.privateBrowsingEnabled) {
+ this.everSeenPrivateBrowsing = true;
+ return true;
+ }
+ } catch (e) { /* seamonkey or very old firefox */ }
+ }
+ return false;
+ },
+
+ myGetBoolPref: function(prefstring) {
+ // syntactic sugar
+ return this.prefs.getBoolPref ("extensions.https_everywhere._observatory." + prefstring);
+ },
+
+ isChainWhitelisted: function(chainhash) {
+ if (X509ChainWhitelist == null) {
+ this.log(WARN, "Could not find whitelist of popular certificate chains, so ignoring whitelist");
+ return false;
+ }
+ if (X509ChainWhitelist[chainhash] != null) {
+ return true;
+ }
+ return false;
+ },
+
+ findRootInChain: function(certArray) {
+ // Return the position in the chain Array of the/a root CA
+ var rootidx = -1;
+ var nextInChain = certArray[0].issuer;
+ for (var i = 0; i < certArray.length; i++) {
+ // Find the next cert in the valid chain
+ if (certArray[i].equals(nextInChain)) {
+ if (certArray[i].issuerName == certArray[i].subjectName) {
+ // All X509 root certs are self-signed
+ this.log(INFO, "Got root cert at position: "+i);
+ rootidx = i;
+ break;
+ } else {
+ // This is an intermediate CA cert; keep looking for the root
+ nextInChain = certArray[i].issuer;
+ }
+ }
+ }
+ return rootidx;
+ },
+
+ processConvergenceChain: function(chain) {
+ // Make sure the chain we're working with is sane, even if Convergence is
+ // present.
+
+ // Convergence currently performs MITMs against the Firefox in order to
+ // get around https://bugzilla.mozilla.org/show_bug.cgi?id=644640. The
+ // end-entity cert produced by Convergence contains a copy of the real
+ // end-entity cert inside an X509v3 extension. We extract this and send
+ // it rather than the Convergence certs.
+ var convergence = Components.classes['@thoughtcrime.org/convergence;1'];
+ if (!convergence) return null;
+ convergence = convergence.getService().wrappedJSObject;
+ if (!convergence || !convergence.enabled) return null;
+
+ this.log(INFO, "Convergence uses its own internal root certs; not submitting those");
+
+ //this.log(WARN, convergence.certificateStatus.getVerificiationStatus(chain.certArray[0]));
+ try {
+ var certInfo = this.extractRealLeafFromConveregenceLeaf(chain.certArray[0]);
+ var b64Cert = certInfo["certificate"];
+ var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+ chain.leaf = certDB.constructX509FromBase64(b64Cert);
+ chain.certArray = [chain.leaf];
+ chain.fps = [this.ourFingerprint(chain.leaf)];
+ } catch (e) {
+ this.log(WARN, "Failed to extract leaf cert from Convergence cert " + e);
+ chain.certArray = chain.certArray.slice(0,1);
+ chain.fps = chain.fps.slice(0,1);
+ }
+
+ },
+
+ extractRealLeafFromConveregenceLeaf: function(certificate) {
+ // Copied from Convergence's CertificateStatus.getVerificiationStatus
+ var len = {};
+ var derEncoding = certificate.getRawDER(len);
+
+ var derItem = NSS.types.SECItem();
+ derItem.data = NSS.lib.ubuffer(derEncoding);
+ derItem.len = len.value;
+
+ var completeCertificate = NSS.lib.CERT_DecodeDERCertificate(derItem.address(), 1, null);
+
+ var extItem = NSS.types.SECItem();
+ var status = NSS.lib.CERT_FindCertExtension(completeCertificate,
+ NSS.lib.SEC_OID_NS_CERT_EXT_COMMENT,
+ extItem.address());
+ if (status != -1) {
+ var encoded = '';
+ var asArray = ctypes.cast(extItem.data, ctypes.ArrayType(ctypes.unsigned_char, extItem.len).ptr).contents;
+ var marker = false;
+
+ for (var i=0;i<asArray.length;i++) {
+ if (marker) {
+ encoded += String.fromCharCode(asArray[i]);
+ } else if (asArray[i] == 0x00) {
+ marker = true;
+ }
+ }
+
+ return JSON.parse(encoded);
+ }
+ },
+
+ shouldSubmit: function(chain, domain) {
+ // Return true if we should submit this chain to the SSL Observatory
+ var rootidx = this.findRootInChain(chain.certArray);
+ var ss = false; // ss: self-signed
+
+ if (chain.leaf.issuerName == chain.leaf.subjectName)
+ ss = true;
+
+ if (!this.myGetBoolPref("self_signed") && ss) {
+ this.log(INFO, "Not submitting self-signed cert for " + domain);
+ return false;
+ }
+
+ if (!ss && !this.myGetBoolPref("alt_roots")) {
+ if (rootidx == -1) {
+ // A cert with an unknown/absent Issuer. Out of caution, don't submit these
+ this.log(INFO, "Cert for " + domain + " issued by unknown CA " +
+ chain.leaf.issuerName + " (not submitting due to settings)");
+ return false;
+ } else if (!(chain.fps[rootidx] in this.public_roots)) {
+ // A cert with a known but non-public Issuer
+ this.log(INFO, "Got a private root cert. Ignoring domain "
+ +domain+" with root "+chain.fps[rootidx]);
+ return false;
+ }
+ }
+
+ if (chain.fps[0] in this.already_submitted) {
+ this.log(INFO, "Already submitted cert for "+domain+". Ignoring");
+ return false;
+ }
+ return true;
+ },
+
+ submitChainArray: function(certArray, fps, domain, channel, host_ip, warning, resubmitting) {
+ var base64Certs = [];
+ // Put all this chain data in one object so that it can be modified by
+ // subroutines if required
+ var c = {}; c.certArray = certArray; c.fps = fps; c.leaf = certArray[0];
+ this.processConvergenceChain(c);
+ if (!this.shouldSubmit(c,domain)) return;
+
+ // only try to submit now if there aren't too many outstanding requests
+ if (this.current_outstanding_requests > MAX_OUTSTANDING) {
+ this.log(WARN, "Too many outstanding requests ("+this.current_outstanding_requests+"), not submitting");
+
+ // if there are too many current requests but not too many
+ // delayed/pending ones, then delay this one
+ if (Object.keys(this.delayed_submissions).length < MAX_DELAYED)
+ if (!(c.fps[0] in this.delayed_submissions)) {
+ this.log(WARN, "Planning to retry submission...");
+ let retry = function() { this.submitChainArray(certArray, fps, domain, channel, host_ip, warning, true); };
+ this.delayed_submissions[c.fps[0]] = retry;
+ }
+ return;
+ }
+
+ for (var i = 0; i < c.certArray.length; i++) {
+ var len = new Object();
+ var derData = c.certArray[i].getRawDER(len);
+ let result = "";
+ for (let j = 0, dataLength = derData.length; j < dataLength; ++j)
+ result += String.fromCharCode(derData[j]);
+ base64Certs.push(btoa(result));
+ }
+
+ var reqParams = [];
+ reqParams.push("domain="+domain);
+ reqParams.push("server_ip="+host_ip);
+ if (this.myGetBoolPref("testing")) {
+ reqParams.push("testing=1");
+ // The server can compute these, but they're a nice test suite item!
+ reqParams.push("fplist="+this.compatJSON.encode(c.fps));
+ }
+ reqParams.push("certlist="+this.compatJSON.encode(base64Certs));
+
+ if (resubmitting) {
+ reqParams.push("client_asn="+ASN_UNKNOWABLE);
+ } else {
+ reqParams.push("client_asn="+this.client_asn);
+ }
+
+ if (this.myGetBoolPref("priv_dns")) {
+ reqParams.push("private_opt_in=1");
+ } else {
+ reqParams.push("private_opt_in=0");
+ }
+
+ if (warning) {
+ reqParams.push("browser_warning=1");
+ } else {
+ reqParams.push("browser_warning=0");
+ }
+
+ var params = reqParams.join("&") + "&padding=0";
+ var tot_len = BASE_REQ_SIZE;
+
+ this.log(INFO, "Submitting cert for "+domain);
+ this.log(DBUG, "submit_cert params: "+params);
+
+ // Pad to exp scale. This is done because the distribution of cert sizes
+ // is almost certainly pareto, and definitely not uniform.
+ for (tot_len = BASE_REQ_SIZE; tot_len < params.length; tot_len*=2);
+
+ while (params.length != tot_len) {
+ params += "0";
+ }
+
+ var that = this; // We have neither SSLObservatory nor this in scope in the lambda
+
+ var win = channel ? this.HTTPSEverywhere.getWindowForChannel(channel) : null;
+ var req = this.buildRequest(params);
+ req.timeout = TIMEOUT;
+
+ req.onreadystatechange = function(evt) {
+ if (req.readyState == 4) {
+ // pop off one outstanding request
+ that.current_outstanding_requests -= 1;
+ that.log(DBUG, "Popping one off of outstanding requests, current num is: "+that.current_outstanding_requests);
+
+ if (req.status == 200) {
+ that.log(INFO, "Successful cert submission");
+ if (!that.prefs.getBoolPref("extensions.https_everywhere._observatory.cache_submitted"))
+ if (c.fps[0] in that.already_submitted)
+ delete that.already_submitted[c.fps[0]];
+
+ // Retry up to two previously failed submissions
+ let n = 0;
+ for (let fp in that.delayed_submissions) {
+ that.log(WARN, "Retrying a submission...");
+ that.delayed_submissions[fp]();
+ delete that.delayed_submissions[fp];
+ if (++n >= 2) break;
+ }
+ } else if (req.status == 403) {
+ that.log(WARN, "The SSL Observatory has issued a warning about this certificate for " + domain);
+ try {
+ var warningObj = JSON.parse(req.responseText);
+ if (win) that.warnUser(warningObj, win, c.certArray[0]);
+ } catch(e) {
+ that.log(WARN, "Failed to process SSL Observatory cert warnings :( " + e);
+ that.log(WARN, req.responseText);
+ }
+ } else {
+ // Submission failed
+ if (c.fps[0] in that.already_submitted)
+ delete that.already_submitted[c.fps[0]];
+ try {
+ that.log(WARN, "Cert submission failure "+req.status+": "+req.responseText);
+ } catch(e) {
+ that.log(WARN, "Cert submission failure and exception: "+e);
+ }
+ // If we don't have too many delayed submissions, and this isn't
+ // (somehow?) one of them, then plan to retry this submission later
+ if (Object.keys(that.delayed_submissions).length < MAX_DELAYED)
+ if (!(c.fps[0] in that.delayed_submissions)) {
+ that.log(WARN, "Planning to retry submission...");
+ let retry = function() { that.submitChainArray(certArray, fps, domain, channel, host_ip, warning, true); };
+ that.delayed_submissions[c.fps[0]] = retry;
+ }
+
+ }
+ }
+ };
+
+ // Cache this here to prevent multiple submissions for all the content elements.
+ that.already_submitted[c.fps[0]] = true;
+
+ // add one to current outstanding request number
+ that.current_outstanding_requests += 1;
+ that.log(DBUG, "Adding outstanding request, current num is: "+that.current_outstanding_requests);
+ req.send(params);
+ },
+
+ buildRequest: function(params) {
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+
+ // We do this again in case the user altered about:config
+ this.findSubmissionTarget();
+ req.open("POST", this.submit_url+this.csrf_nonce, true);
+
+ // Send the proper header information along with the request
+ // Do not set gzip header.. It will ruin the padding
+ req.setRequestHeader("X-Privacy-Info", "EFF SSL Observatory: https://eff.org/r.22c");
+ req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ req.setRequestHeader("Content-length", params.length);
+ req.setRequestHeader("Connection", "close");
+ // Need to clear useragent and other headers..
+ req.setRequestHeader("User-Agent", "");
+ req.setRequestHeader("Accept", "");
+ req.setRequestHeader("Accept-Language", "");
+ req.setRequestHeader("Accept-Encoding", "");
+ req.setRequestHeader("Accept-Charset", "");
+ return req;
+ },
+
+ warnUser: function(warningObj, win, cert) {
+ var aWin = CC['@mozilla.org/appshell/window-mediator;1']
+ .getService(CI.nsIWindowMediator)
+ .getMostRecentWindow('navigator:browser');
+ aWin.openDialog("chrome://https-everywhere/content/observatory-warning.xul",
+ "","chrome,centerscreen", warningObj, win, cert);
+ },
+
+ registerProxyTestNotification: function(callback_fcn) {
+ if (this.proxy_test_successful != null) {
+ /* Proxy test already ran. Callback immediately. */
+ callback_fcn(this.proxy_test_successful);
+ this.proxy_test_callback = null;
+ return;
+ } else {
+ this.proxy_test_callback = callback_fcn;
+ }
+ },
+
+ testProxySettings: function() {
+ /* Plan:
+ * 1. Launch an async XMLHttpRequest to check.tp.o with magic nonce
+ * 3. Filter the nonce in protocolProxyFilter to use proxy settings
+ * 4. Async result function sets test result status based on check.tp.o
+ */
+ this.proxy_test_successful = null;
+
+ try {
+ var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ var url = this.cto_url + this.csrf_nonce;
+ req.open('GET', url, true);
+ req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ req.overrideMimeType("text/xml");
+ var that = this; // Scope gymnastics for async callback
+ req.onreadystatechange = function (oEvent) {
+ if (req.readyState === 4) {
+ that.proxy_test_successful = false;
+
+ if(req.status == 200) {
+ if(!req.responseXML) {
+ that.log(INFO, "Tor check failed: No XML returned by check service.");
+ that.proxyTestFinished();
+ return;
+ }
+
+ var result = req.responseXML.getElementById('TorCheckResult');
+ if(result===null) {
+ that.log(INFO, "Tor check failed: Non-XML returned by check service.");
+ } else if(typeof(result.target) == 'undefined'
+ || result.target === null) {
+ that.log(INFO, "Tor check failed: Busted XML returned by check service.");
+ } else if(result.target === "success") {
+ that.log(INFO, "Tor check succeeded.");
+ that.proxy_test_successful = true;
+ } else {
+ that.log(INFO, "Tor check failed: "+result.target);
+ }
+ } else {
+ that.log(INFO, "Tor check failed: HTTP Error "+req.status);
+ }
+
+ /* Notify the UI of the test result */
+ if (that.proxy_test_callback) {
+ that.proxy_test_callback(that.proxy_test_successful);
+ that.proxy_test_callback = null;
+ }
+ that.proxyTestFinished();
+ }
+ };
+ req.send(null);
+ } catch(e) {
+ this.proxy_test_successful = false;
+ if(e.result == 0x80004005) { // NS_ERROR_FAILURE
+ this.log(INFO, "Tor check failed: Proxy not running.");
+ }
+ this.log(INFO, "Tor check failed: Internal error: "+e);
+ if (this.proxy_test_callback) {
+ this.proxy_test_callback(this.proxy_test_successful);
+ this.proxy_test_callback = null;
+ }
+ that.proxyTestFinished();
+ }
+ },
+
+ proxyTestFinished: function() {
+ if (!this.myGetBoolPref("enabled")) {
+ this.pps.unregisterFilter(this);
+ }
+ },
+
+ getProxySettings: function(testingForTor) {
+ // This may be called either for an Observatory submission, or during a test to see if Tor is
+ // present. The testingForTor argument is true in the latter case.
+ var proxy_settings = ["direct", "", 0];
+ this.log(INFO,"in getProxySettings()");
+ var custom_proxy_type = this.prefs.getCharPref("extensions.https_everywhere._observatory.proxy_type");
+ if (this.torbutton_installed && this.myGetBoolPref("use_tor_proxy")) {
+ this.log(INFO,"CASE: use_tor_proxy");
+ // extract torbutton proxy settings
+ proxy_settings[0] = "http";
+ proxy_settings[1] = this.prefs.getCharPref("extensions.torbutton.https_proxy");
+ proxy_settings[2] = this.prefs.getIntPref("extensions.torbutton.https_port");
+
+ if (proxy_settings[2] == 0) {
+ proxy_settings[0] = "socks";
+ proxy_settings[1] = this.prefs.getCharPref("extensions.torbutton.socks_host");
+ proxy_settings[2] = this.prefs.getIntPref("extensions.torbutton.socks_port");
+ }
+ /* Regarding the test below:
+ *
+ * custom_proxy_type == "direct" is indicative of the user having selected "submit certs even if
+ * Tor is not available", rather than true custom Tor proxy settings. So in that case, there's
+ * not much point probing to see if the direct proxy is actually a Tor connection, and
+ * localhost:9050 is a better bet. People whose networks send all traffc through Tor can just
+ * tell the Observatory to submit certs without Tor.
+ */
+ } else if (this.myGetBoolPref("use_custom_proxy") && !(testingForTor && custom_proxy_type == "direct")) {
+ this.log(INFO,"CASE: use_custom_proxy");
+ proxy_settings[0] = custom_proxy_type;
+ proxy_settings[1] = this.prefs.getCharPref("extensions.https_everywhere._observatory.proxy_host");
+ proxy_settings[2] = this.prefs.getIntPref("extensions.https_everywhere._observatory.proxy_port");
+ } else {
+ /* Take a guess at default tor proxy settings */
+ this.log(INFO,"CASE: try localhost:9050");
+ proxy_settings[0] = "socks";
+ proxy_settings[1] = "localhost";
+ proxy_settings[2] = 9050;
+ }
+ this.log(INFO, "Using proxy: " + proxy_settings);
+ return proxy_settings;
+ },
+
+ applyFilter: function(aProxyService, inURI, aProxy) {
+
+ try {
+ if (inURI instanceof Ci.nsIURI) {
+ var aURI = inURI.QueryInterface(Ci.nsIURI);
+ if (!aURI) this.log(WARN, "Failed to QI to nsIURI!");
+ } else {
+ this.log(WARN, "applyFilter called without URI");
+ }
+ } catch (e) {
+ this.log(WARN, "EXPLOSION: " + e);
+ }
+
+ var isSubmission = this.submission_regexp.test(aURI.spec);
+ var testingForTor = this.cto_regexp.test(aURI.spec);
+
+ if (isSubmission || testingForTor) {
+ if (aURI.path.search(this.csrf_nonce+"$") != -1) {
+
+ this.log(INFO, "Got observatory url + nonce: "+aURI.spec);
+ var proxy_settings = null;
+ var proxy = null;
+
+ // Send it through tor by creating an nsIProxy instance
+ // for the torbutton proxy settings.
+ try {
+ proxy_settings = this.getProxySettings(testingForTor);
+ proxy = this.pps.newProxyInfo(proxy_settings[0], proxy_settings[1],
+ proxy_settings[2],
+ Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
+ 0xFFFFFFFF, null);
+ } catch(e) {
+ this.log(WARN, "Error specifying proxy for observatory: "+e);
+ }
+
+ this.log(INFO, "Specifying proxy: "+proxy);
+
+ // TODO: Use new identity or socks u/p to ensure we get a unique
+ // tor circuit for this request
+ return proxy;
+ }
+ }
+ return aProxy;
+ },
+
+ // [optional] an array of categories to register this component in.
+ // Hack to cause us to get instantiate early
+ _xpcom_categories: [ { category: "profile-after-change" }, ],
+
+ encString: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+ encStringS: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
+
+ log: function(level, str) {
+ var econsole = CC["@mozilla.org/consoleservice;1"]
+ .getService(CI.nsIConsoleService);
+ try {
+ var threshold = this.prefs.getIntPref(LLVAR);
+ } catch (e) {
+ econsole.logStringMessage( "SSL Observatory: Failed to read about:config LogLevel");
+ threshold = WARN;
+ }
+ if (level >= threshold) {
+ dump("SSL Observatory: "+str+"\n");
+ econsole.logStringMessage("SSL Observatory: " +str);
+ }
+ }
+};
+
+/**
+* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
+* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
+*/
+if (XPCOMUtils.generateNSGetFactory)
+ var NSGetFactory = XPCOMUtils.generateNSGetFactory([SSLObservatory]);
+else
+ var NSGetModule = XPCOMUtils.generateNSGetModule([SSLObservatory]);