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(/([-()\[\]{}+?*.$\^|,:# 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 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]);