summaryrefslogtreecommitdiff
path: root/data/extensions/https-everywhere@eff.org/components
diff options
context:
space:
mode:
authorRuben Rodriguez <ruben@gnu.org>2015-10-10 11:25:26 -0500
committerRuben Rodriguez <ruben@gnu.org>2015-10-12 23:20:54 -0500
commit02da35db344679e666459b1e84946c7c9e5ba464 (patch)
tree6019eec51cd9b95256d9d66eb02dee9829f1624f /data/extensions/https-everywhere@eff.org/components
parent50557a44e8257abc03913ed5b0828e9fa509dc85 (diff)
HTTPS-Everywhere updated to 5.1.1
Diffstat (limited to 'data/extensions/https-everywhere@eff.org/components')
-rw-r--r--data/extensions/https-everywhere@eff.org/components/https-everywhere.js895
-rw-r--r--data/extensions/https-everywhere@eff.org/components/ssl-observatory.js943
2 files changed, 0 insertions, 1838 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
deleted file mode 100644
index 81f6ad6..0000000
--- a/data/extensions/https-everywhere@eff.org/components/https-everywhere.js
+++ /dev/null
@@ -1,895 +0,0 @@
-// LOG LEVELS
-let VERB=1;
-let DBUG=2;
-let INFO=3;
-let NOTE=4;
-let WARN=5;
-
-// PREFERENCE BRANCHES
-let PREFBRANCH_ROOT=0;
-let PREFBRANCH_RULE_TOGGLE=1;
-let PREFBRANCH_NONE=2;
-
-// maps domain patterns (with at most one wildcard) to RuleSets
-let https_domains = {};
-// URLs we've given up on rewriting because of redirection loops
-let https_everywhere_blacklist = {};
-// domains for which there is at least one blacklisted URL
-let https_blacklist_domains = {};
-
-const CI = Components.interfaces;
-const CC = Components.classes;
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-
-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_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");
-
-function HTTPSEverywhere() {
-
- // Set up logging in each component:
- HTTPS.log = HTTPSRules.log = RuleWriter.log = this.log = https_everywhereLog;
-
- this.expandoMap = new WeakMap();
-
- this.log = https_everywhereLog;
- this.wrappedJSObject = this;
- this.https_rules = HTTPSRules;
- this.rw = RuleWriter; // currently used for some file IO helpers, though that
- // should probably be refactored
- 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);
-
- this.httpNowhereEnabled = this.prefs.getBoolPref("http_nowhere.enabled");
- this.isMobile = this.doMobileCheck();
-
- // 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);
- } else {
- // Need this to initialize FF for Android UI even when HTTPS-E is off
- if (this.isMobile) {
- this.obsService.addObserver(this, "sessionstore-windows-restored", 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;
-}
-
-
-/*
-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
-
-1. HTTPSEverywhere.shouldIgnoreURI() checks for very quick reasons to ignore a
-request, such as redirection loops, non-HTTP[S] URIs, and OCSP
-
- 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() 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.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(browser, key) {
- let obj = this.expandoMap.get(browser);
- if (!obj) {
- if (browser.currentURI) {
- this.log(NOTE, "No expando for " + browser.currentURI.spec);
- }
- return null;
- }
- return obj[key];
- },
-
- setExpando: function(browser, key, value) {
- if (!this.expandoMap.has(browser)) {
- this.expandoMap.set(browser, {});
- }
- let obj = this.expandoMap.get(browser);
- obj[key] = value;
- },
-
- // We use resetApplicableList 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.
- resetApplicableList: function(browser) {
- if (!this.prefs.getBoolPref("globalEnabled")) {
- return;
- }
- try {
- this.newApplicableListForBrowser(browser);
- } catch (e) {
- this.log(WARN, "Couldn't make applicable list"+e);
- }
- },
-
- // Given an nsIChannel (essentially, a container for an HTTP or similar
- // resource request), try to find the relevant tab if there is one.
- // Specifically, find the XUL <browser> element for that tab. Note
- // there are multiple meanings for the word 'browser' in Firefox, described at:
- // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser
- // We're looking for this one:
- // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/browser
- // Also note some requests, like Safe Browsing requests, will have no
- // associated tab.
- getBrowserForChannel: function(channel) {
- let loadContext, topFrameElement, associatedWindow;
- let spec = channel.URI.spec;
- try {
- loadContext = channel.notificationCallbacks.getInterface(CI.nsILoadContext);
- } catch(e) {
- }
-
- if (!loadContext) {
- try {
- loadContext = channel.loadGroup.notificationCallbacks
- .getInterface(CI.nsILoadContext);
- } catch(e) {
- // Lots of requests have no notificationCallbacks, mostly background
- // ones like OCSP checks or smart browsing fetches.
- this.log(DBUG, "getBrowserForChannel: no loadContext for " + spec);
- return null;
- }
- }
-
- if (loadContext) {
- topFrameElement = loadContext.topFrameElement;
- try {
- // If loadContext is an nsDocShell, associatedWindow is present.
- // Otherwise, if it's just a LoadContext, accessing it will throw
- // NS_ERROR_UNEXPECTED.
- associatedWindow = loadContext.associatedWindow;
- } catch (e) {
- }
- }
-
- // On e10s (multiprocess, aka electrolysis) Firefox,
- // loadContext.topFrameElement gives us a reference to the XUL <browser>
- // element we need. However, on non-e10s Firefox, topFrameElement is null.
- if (topFrameElement) {
- return topFrameElement;
- } else if (associatedWindow) {
- // For non-e10s Firefox, get the XUL <browser> element using this rather
- // magical / opaque code cribbed from
- // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser#Getting_the_browser_that_fires_the_http-on-modify-request_notification_(example_code_updated_for_loadContext)
-
- // this is the HTML DOM window of the page that just loaded
- var contentWindow = loadContext.associatedWindow;
- // aDOMWindow this is the firefox window holding the tab
- var aDOMWindow = contentWindow.top.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShellTreeItem)
- .rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow);
- // this is the gBrowser object of the firefox window this tab is in
- var gBrowser = aDOMWindow.gBrowser;
- if (gBrowser && gBrowser._getTabForContentWindow) {
- var aTab = gBrowser._getTabForContentWindow(contentWindow.top);
- // this is the clickable tab xul element, the one found in the tab strip
- // of the firefox window, aTab.linkedBrowser is same as browser var above
- // this is the browser within the tab
- if (aTab) {
- return aTab.linkedBrowser;
- } else {
- this.log(NOTE, "getBrowserForChannel: aTab was null for " + spec);
- return null;
- }
- } else if (aDOMWindow.BrowserApp) {
- // gBrowser is unavailable in Firefox for Android, and in some desktop
- // contexts, like the fetches for new tab tiles (which have an
- // associatedWindow, but no gBrowser)?
- // If available, try using the BrowserApp API:
- // https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/BrowserApp
- // TODO: We don't get the toolbar icon on android. Probably need to fix
- // the gBrowser reference in toolbar_button.js.
- // Also TODO: Where are these log messages going? They don't show up in
- // remote debug console.
- var mTab = aDOMWindow.BrowserApp.getTabForWindow(contentWindow.top);
- if (mTab) {
- return mTab.browser;
- } else {
- this.log(WARN, "getBrowserForChannel: mTab was null for " + spec);
- return null;
- }
- } else {
- this.log(INFO, "getBrowserForChannel: No gBrowser and no BrowserApp for " + spec);
- return null;
- }
- } else {
- this.log(NOTE, "getBrowserForChannel: No loadContext for " + spec);
- return null;
- }
- },
-
- // 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 browser = this.getBrowserForChannel(channel);
- return this.getApplicableListForBrowser(browser);
- },
-
- newApplicableListForBrowser: function(browser) {
- if (!browser) {
- this.log(WARN, "Get alist without browser");
- return null;
- }
- var alist = new ApplicableList(this.log, browser.currentURI);
- this.setExpando(browser,"applicable_rules",alist);
- return alist;
- },
-
- getApplicableListForBrowser: function(browser) {
- if (!browser) {
- //this.log(WARN, "Get alist without browser");
- return null;
- }
- var alist= this.getExpando(browser,"applicable_rules");
- if (alist) {
- return alist;
- } else {
- alist = new ApplicableList(this.log, browser.currentURI);
- this.setExpando(browser,"applicable_rules",alist);
- }
- return alist;
- },
-
- // These are the highest level heuristics for figuring out whether
- // we should consider rewriting a URI. Everything here should be simple
- // and avoid dependence on the ruleset library
- shouldIgnoreURI: function(channel, alist) {
- var uri = channel.URI;
- // Ignore all non-http(s) requests?
- if (!(uri.schemeIs("http") || uri.schemeIs("https"))) { return true; }
-
- // If HTTP Nowhere is enabled, skip the rest of the shouldIgnoreURI checks
- if (this.httpNowhereEnabled) {
- return false;
- }
-
- // These are URIs we've seen redirecting back in loops after we redirect them
- if (uri.spec in https_everywhere_blacklist) {
- this.log(DBUG, "Avoiding blacklisted " + uri.spec);
- if (alist) {
- alist.breaking_rule(https_everywhere_blacklist[uri.spec]);
- } else {
- this.log(NOTE,"Failed to indicate breakage in content menu");
- }
- return true;
- }
-
- // OCSP (currently) needs to be HTTP to avoid cert validation loops
- // though someone should rev the spec to allow opportunistic encryption
- if ("allowSTS" in channel) {
- // Firefox 32+ lets us infer whether this is an OCSP request
- if (!channel.allowSTS) {
- this.log(INFO, "Channel with HTTPS rewrites forbidden, deeming OCSP, for " + channel.URI.spec);
- return true;
- }
- } else {
- // Firefox <32 requires a more hacky estimate
- // load the list opportunistically to speed startup & FF 32+
- if (this.ocspList == undefined) { this.loadOCSPList(); }
- if (this.ocspList.indexOf(uri.spec.replace(/\/$/,'')) !== -1) {
- this.log(INFO, "Known ocsp request "+uri.spec);
- return true;
- }
- }
-
- return false;
- },
-
- loadOCSPList: function() {
- try {
- var loc = "chrome://https-everywhere/content/code/commonOCSP.json";
- var file = CC["@mozilla.org/file/local;1"].createInstance(CI.nsILocalFile);
- file.initWithPath(this.rw.chromeToPath(loc));
- var data = this.rw.read(file);
- this.ocspList = JSON.parse(data);
- } catch(e) {
- this.log(WARN, "Failed to load OCSP list: " + e);
- this.ocspList = [];
- }
- },
-
- 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);
- // lst is null if no window is associated (ex: some XHR)
- var lst = this.getApplicableListForChannel(channel);
- if (this.shouldIgnoreURI(channel, lst)) return;
- HTTPS.replaceChannel(lst, channel, this.httpNowhereEnabled);
- } 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);
-
- 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");
- if (!this.isMobile) {
- this.maybeShowObservatoryPopup();
- } else {
- this.log(WARN, "Initializing Firefox for Android UI");
- Cu.import("chrome://https-everywhere/content/code/AndroidUI.jsm");
- AndroidUI.init();
- }
- 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, this.httpNowhereEnabled);
- },
-
- 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 browser = this.getBrowserForChannel(oldChannel);
- var old_alist = null;
- if (browser)
- old_alist = this.getExpando(browser,"applicable_rules");
- browser = this.getBrowserForChannel(newChannel);
- if (!browser) return null;
- var new_alist = this.getExpando(browser,"applicable_rules");
- if (old_alist && !new_alist) {
- new_alist = old_alist;
- this.setExpando(browser,"applicable_rules",new_alist);
- } else if (!new_alist) {
- new_alist = new ApplicableList(this.log, browser.currentURI);
- this.setExpando(browser,"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 if (prefBranch === PREFBRANCH_NONE)
- branch_name = "";
- 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;
- },
-
- // Are we on Firefox for Android?
- doMobileCheck: function() {
- let appInfo = CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo);
- let ANDROID_ID = "{aa3c5121-dab2-40e2-81ca-7ea25febc110}";
- return (appInfo.ID === ANDROID_ID);
- },
-
- 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 = CC["@mozilla.org/categorymanager;1"]
- .getService(CI.nsICategoryManager);
- catman.deleteCategoryEntry("net-channel-event-sinks",
- SERVICE_CTRID, true);
-
- 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);
-
- this.log(INFO,
- "ChannelReplacement.supported = "+ChannelReplacement.supported);
-
- if (!Thread.hostRunning) {
- Thread.hostRunning = true;
- }
-
- var catman = CC["@mozilla.org/categorymanager;1"]
- .getService(CI.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);
- }
- }
- },
-
- toggleHttpNowhere: function() {
- let prefService = Services.prefs;
- let thisBranch =
- prefService.getBranch("extensions.https_everywhere.http_nowhere.");
- let securityBranch = prefService.getBranch("security.");
-
- // Whether cert is treated as invalid when OCSP connection fails
- let OCSP_REQUIRED = "OCSP.require";
-
- // Branch to save original settings
- let ORIG_OCSP_REQUIRED = "orig.ocsp.required";
-
-
- if (thisBranch.getBoolPref("enabled")) {
- // Restore original OCSP settings. TODO: What if user manually edits
- // these while HTTP Nowhere is enabled?
- let origOcspRequired = thisBranch.getBoolPref(ORIG_OCSP_REQUIRED);
- securityBranch.setBoolPref(OCSP_REQUIRED, origOcspRequired);
-
- thisBranch.setBoolPref("enabled", false);
- this.httpNowhereEnabled = false;
- } else {
- // Save original OCSP settings in HTTP Nowhere preferences branch.
- let origOcspRequired = securityBranch.getBoolPref(OCSP_REQUIRED);
- thisBranch.setBoolPref(ORIG_OCSP_REQUIRED, origOcspRequired);
-
- // Disable OCSP enforcement
- securityBranch.setBoolPref(OCSP_REQUIRED, false);
-
- thisBranch.setBoolPref("enabled", true);
- this.httpNowhereEnabled = true;
- }
- }
-};
-
-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) {
- var levelName = ["", "VERB", "DBUG", "INFO", "NOTE", "WARN"][level];
- var prefix = "HTTPS Everywhere " + levelName + ": ";
- // dump() prints to browser stdout. That's sometimes undesireable,
- // so only do it when a pref is set (running from test.sh enables
- // this pref).
- if (prefs.getBoolPref("log_to_stdout")) {
- dump(prefix + str + "\n");
- }
- econsole.logStringMessage(prefix + 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
deleted file mode 100644
index abcb175..0000000
--- a/data/extensions/https-everywhere@eff.org/components/ssl-observatory.js
+++ /dev/null
@@ -1,943 +0,0 @@
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cr = Components.results;
-
-const CI = Components.interfaces;
-const CC = Components.classes;
-const CR = Components.results;
-
-// Log levels
-let VERB=1;
-let DBUG=2;
-let INFO=3;
-let NOTE=4;
-let WARN=5;
-
-let BASE_REQ_SIZE=4096;
-let TIMEOUT = 60000;
-let MAX_OUTSTANDING = 20; // Max # submission XHRs in progress
-let MAX_DELAYED = 32; // Max # XHRs are waiting around to be sent or retried
-
-let ASN_PRIVATE = -1; // Do not record the ASN this cert was seen on
-let ASN_IMPLICIT = -2; // ASN can be learned from connecting IP
-let ASN_UNKNOWABLE = -3; // Cert was seen in the absence of [trustworthy] Internet access
-
-// XXX: We should make the _observatory tree relative.
-let 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
- var tor_logger_component = CC["@torproject.org/torbutton-logger;1"];
- if (tor_logger_component) {
- this.tor_logger =
- tor_logger_component.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 = {};
- OS.addObserver(this, "cookie-changed", false);
-
- // 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;
-
- // 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);
-
- // Register observer
- OS.addObserver(this, "http-on-examine-response", false);
-
- // 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.nsIBadCertListener2]),
-
- 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
- getSSLCert: 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();
- },
- */
-
- // Calculate the MD5 fingerprint for a cert. This is the fingerprint of the
- // DER-encoded form, same as the result of
- // openssl x509 -md5 -fingerprint -noout
- // We use this because the SSL Observatory depends in many places on a special
- // fingerprint which is the concatenation of MD5+SHA1, and the MD5 fingerprint
- // is no longer available on the cert object.
- // Implementation cribbed from
- // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICryptoHash
- md5Fingerprint: function(cert) {
- var len = new Object();
- var derData = cert.getRawDER(len);
- var ch = CC["@mozilla.org/security/hash;1"].createInstance(CI.nsICryptoHash);
- ch.init(ch.MD5);
- ch.update(derData,derData.length);
- var h = ch.finish(false);
-
- function toHexString(charCode) {
- return ("0" + charCode.toString(16)).slice(-2);
- }
- return [toHexString(h.charCodeAt(i)) for (i in h)].join("").toUpperCase();
- },
-
- ourFingerprint: function(cert) {
- // Calculate our custom fingerprint from an nsIX509Cert
- return (this.md5Fingerprint(cert)+cert.sha1Fingerprint).replace(":", "", "g");
- },
-
- 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 (topic == "nsPref:changed") {
- // 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...
- if (data == "network.proxy.ssl" || data == "network.proxy.ssl_port" ||
- data == "network.proxy.socks" || data == "network.proxy.socks_port") {
- this.log(INFO, "Proxy settings have changed. Getting new ASN");
- this.getClientASN();
- }
- return;
- }
-
- if (topic == "network:offline-status-changed" && data == "online") {
- this.log(INFO, "Browser back online. Getting new ASN.");
- this.getClientASN();
- return;
- }
-
- if ("http-on-examine-response" == topic) {
-
- if (!this.observatoryActive()) return;
-
- var host_ip = "-1";
- var httpchannelinternal = subject.QueryInterface(Ci.nsIHttpChannelInternal);
- try {
- host_ip = httpchannelinternal.remoteAddress;
- } catch(e) {
- this.log(INFO, "Could not get server IP address.");
- }
- subject.QueryInterface(Ci.nsIHttpChannel);
- var certchain = this.getSSLCert(subject);
- if (certchain) {
- 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(subject.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 (subject.URI.port == -1) {
- this.submitChain(chainArray, fps, new String(subject.URI.host), subject, host_ip, false);
- } else {
- this.submitChain(chainArray, fps, subject.URI.host+":"+subject.URI.port, subject, host_ip, false);
- }
- }
- }
- },
-
- observatoryActive: function() {
-
- 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.
- try {
- var pbs = CC["@mozilla.org/privatebrowsing;1"].getService(CI.nsIPrivateBrowsingService);
- if (pbs.privateBrowsingEnabled) return false;
- } catch (e) { /* seamonkey or old firefox */ }
-
- return true;
- }
-
- 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;
-
- 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;
- },
-
- submitChain: function(certArray, fps, domain, channel, host_ip, 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.submitChain(certArray, fps, domain, channel, host_ip, 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");
- }
-
- 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 HTTPSEverywhere = CC["@eff.org/https-everywhere;1"]
- .getService(Components.interfaces.nsISupports)
- .wrappedJSObject;
- var win = null;
- if (channel) {
- var browser = this.HTTPSEverywhere.getBrowserForChannel(channel);
- if (browser) {
- var win = browser.contentWindow;
- }
- }
- 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") &&
- 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 &&
- c.fps[0] in that.delayed_submissions) {
- that.log(WARN, "Planning to retry submission...");
- let retry = function() { that.submitChain(certArray, fps, domain, channel, host_ip, 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://www.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.");
- 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;
- }
- }
- };
- 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;
- }
- }
- },
-
- 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) {
- var levelName = ["", "VERB", "DBUG", "INFO", "NOTE", "WARN"][level];
- var prefix = "SSL Observatory " + levelName + ": ";
- // dump() prints to browser stdout. That's sometimes undesireable,
- // so only do it when a pref is set (running from test.sh enables
- // this pref).
- if (this.prefs.getBoolPref("extensions.https_everywhere.log_to_stdout")) {
- dump(prefix + str + "\n");
- }
- econsole.logStringMessage(prefix + 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]);