@@ -14,18 +14,14 @@ let NOTE=4;
1414let WARN = 5 ;
1515
1616let BASE_REQ_SIZE = 4096 ;
17+ let TIMEOUT = 60000 ;
1718let MAX_OUTSTANDING = 20 ; // Max # submission XHRs in progress
1819let MAX_DELAYED = 32 ; // Max # XHRs are waiting around to be sent or retried
19- let TIMEOUT = 60000 ;
2020
2121let ASN_PRIVATE = - 1 ; // Do not record the ASN this cert was seen on
22- let ASN_IMPLICIT = - 2 ; // ASN can be learned from connecting IP
22+ let ASN_IMPLICIT = - 2 // ASN can be learned from connecting IP
2323let ASN_UNKNOWABLE = - 3 ; // Cert was seen in the absence of [trustworthy] Internet access
2424
25- let HASHLENGTH = 64 ; // hex(sha1 + md5)
26- let MIN_WHITELIST = 1000 ; // do not tolerate whitelists outside these bounds
27- let MAX_WHITELIST = 10000 ;
28-
2925// XXX: We should make the _observatory tree relative.
3026let LLVAR = "extensions.https_everywhere.LogLevel" ;
3127
@@ -59,6 +55,7 @@ const INCLUDE = function(name) {
5955
6056INCLUDE ( 'Root-CAs' ) ;
6157INCLUDE ( 'sha256' ) ;
58+ INCLUDE ( 'X509ChainWhitelist' ) ;
6259INCLUDE ( 'NSS' ) ;
6360
6461function SSLObservatory ( ) {
@@ -117,16 +114,13 @@ function SSLObservatory() {
117114 this . setupASNWatcher ( ) ;
118115
119116 try {
120- NSS . initialize ( ctypes . libraryName ( "nss3" ) ) ;
117+ NSS . initialize ( "" ) ;
121118 } catch ( e ) {
122119 this . log ( WARN , "Failed to initialize NSS component:" + e ) ;
123120 }
124121
125122 this . testProxySettings ( ) ;
126123
127- this . loadCertWhitelist ( ) ;
128- this . maybeUpdateCertWhitelist ( ) ;
129-
130124 this . log ( DBUG , "Loaded observatory component!" ) ;
131125}
132126
@@ -304,122 +298,68 @@ SSLObservatory.prototype = {
304298 return ;
305299 }
306300
307- if ( "http-on-examine-response" == topic ) {
308- var channel = subject ;
309- if ( ! this . observatoryActive ( channel ) ) return ;
310-
311- var certchain = this . getSSLCertChain ( subject ) ;
312- var warning = false ;
313- this . submitCertChainForChannel ( certchain , channel , warning ) ;
314- }
315-
316- if ( topic == "network:offline-status-changed" && data == "online" ) {
317- this . log ( INFO , "Browser back online. Getting new ASN." ) ;
318- this . getClientASN ( ) ;
319- return ;
320- }
321-
322301 if ( topic == "nsPref:changed" ) {
323- // If the user toggles the SSL Observatory settings, we need to add or remove
324- // our observers
325- switch ( data ) {
326- case "network.proxy.ssl" :
327- case "network.proxy.ssl_port" :
328- case "network.proxy.socks" :
329- case "network.proxy.socks_port" :
330- // XXX: We somehow need to only call this once. Right now, we'll make
331- // like 3 calls to getClientASN().. The only thing I can think
332- // of is a timer...
333- this . log ( INFO , "Proxy settings have changed. Getting new ASN" ) ;
334- this . getClientASN ( ) ;
335- break ;
336- case "extensions.https_everywhere._observatory.enabled" :
337- if ( this . myGetBoolPref ( "enabled" ) ) {
338- this . pps . registerFilter ( this , 0 ) ;
339- OS . addObserver ( this , "cookie-changed" , false ) ;
340- OS . addObserver ( this , "http-on-examine-response" , false ) ;
341-
342- var dls = CC [ '@mozilla.org/docloaderservice;1' ]
343- . getService ( CI . nsIWebProgress ) ;
344- dls . addProgressListener ( this ,
345- Ci . nsIWebProgress . NOTIFY_STATE_REQUEST ) ;
346- this . log ( INFO , "SSL Observatory is now enabled via pref change!" ) ;
347- } else {
348- try {
349- this . pps . unregisterFilter ( this ) ;
350- OS . removeObserver ( this , "cookie-changed" ) ;
351- OS . removeObserver ( this , "http-on-examine-response" ) ;
352-
353- var dls = CC [ '@mozilla.org/docloaderservice;1' ]
354- . getService ( CI . nsIWebProgress ) ;
355- dls . removeProgressListener ( this ) ;
356- this . log ( INFO , "SSL Observatory is now disabled via pref change!" ) ;
357- } catch ( e ) {
358- this . log ( WARN , "Removing SSL Observatory observers failed: " + e ) ;
359- }
360- }
361- break ;
302+ // XXX: We somehow need to only call this once. Right now, we'll make
303+ // like 3 calls to getClientASN().. The only thing I can think
304+ // of is a timer...
305+ if ( data == "network.proxy.ssl" || data == "network.proxy.ssl_port" ||
306+ data == "network.proxy.socks" || data == "network.proxy.socks_port" ) {
307+ this . log ( INFO , "Proxy settings have changed. Getting new ASN" ) ;
308+ this . getClientASN ( ) ;
362309 }
363310 return ;
364311 }
365312
366- } ,
367-
368- submitCertChainForChannel : function ( certchain , channel , warning ) {
369- if ( ! certchain ) {
313+ if ( topic == "network:offline-status-changed" && data == "online" ) {
314+ this . log ( INFO , "Browser back online. Getting new ASN." ) ;
315+ this . getClientASN ( ) ;
370316 return ;
371317 }
372318
373- this . maybeUpdateCertWhitelist ( ) ;
374-
375- var host_ip = "-1" ;
376- var httpchannelinternal = channel . QueryInterface ( Ci . nsIHttpChannelInternal ) ;
377- try {
378- host_ip = httpchannelinternal . remoteAddress ;
379- } catch ( e ) {
380- this . log ( INFO , "Could not get server IP address." ) ;
381- }
319+ if ( "http-on-examine-response" == topic ) {
382320
383- if ( ! this . observatoryActive ( ) ) return ;
321+ if ( ! this . observatoryActive ( ) ) return ;
384322
385- var host_ip = "-1" ;
386- var httpchannelinternal = subject . QueryInterface ( Ci . nsIHttpChannelInternal ) ;
387- try {
388- host_ip = httpchannelinternal . remoteAddress ;
389- } catch ( e ) {
390- this . log ( INFO , "Could not get server IP address." ) ;
391- }
392- subject . QueryInterface ( Ci . nsIHttpChannel ) ;
393- var certchain = this . getSSLCert ( subject ) ;
394- if ( certchain ) {
395- var chainEnum = certchain . getChain ( ) ;
396- var chainArray = [ ] ;
397- var chainArrayFpStr = '' ;
398- var fps = [ ] ;
399- for ( var i = 0 ; i < chainEnum . length ; i ++ ) {
400- var cert = chainEnum . queryElementAt ( i , Ci . nsIX509Cert ) ;
401- chainArray . push ( cert ) ;
402- var fp = this . ourFingerprint ( cert ) ;
403- fps . push ( fp ) ;
404- chainArrayFpStr = chainArrayFpStr + fp ;
323+ var host_ip = "-1" ;
324+ var httpchannelinternal = subject . QueryInterface ( Ci . nsIHttpChannelInternal ) ;
325+ try {
326+ host_ip = httpchannelinternal . remoteAddress ;
327+ } catch ( e ) {
328+ this . log ( INFO , "Could not get server IP address." ) ;
405329 }
406- var chain_hash = sha256_digest ( chainArrayFpStr ) . toUpperCase ( ) ;
407- this . log ( INFO , "SHA-256 hash of cert chain for " + new String ( subject . URI . host ) + " is " + chain_hash ) ;
330+ subject . QueryInterface ( Ci . nsIHttpChannel ) ;
331+ var certchain = this . getSSLCert ( subject ) ;
332+ if ( certchain ) {
333+ var chainEnum = certchain . getChain ( ) ;
334+ var chainArray = [ ] ;
335+ var chainArrayFpStr = '' ;
336+ var fps = [ ] ;
337+ for ( var i = 0 ; i < chainEnum . length ; i ++ ) {
338+ var cert = chainEnum . queryElementAt ( i , Ci . nsIX509Cert ) ;
339+ chainArray . push ( cert ) ;
340+ var fp = this . ourFingerprint ( cert ) ;
341+ fps . push ( fp ) ;
342+ chainArrayFpStr = chainArrayFpStr + fp ;
343+ }
344+ var chain_hash = sha256_digest ( chainArrayFpStr ) . toUpperCase ( ) ;
345+ this . log ( INFO , "SHA-256 hash of cert chain for " + new String ( subject . URI . host ) + " is " + chain_hash ) ;
408346
409- if ( ! this . myGetBoolPref ( "use_whitelist" ) ) {
410- this . log ( WARN , "Not using whitelist to filter cert chains." ) ;
411- }
412- else if ( this . isChainWhitelisted ( chain_hash ) ) {
413- this . log ( INFO , "This cert chain is whitelisted. Not submitting." ) ;
414- return ;
415- } else {
416- this . log ( INFO , "Cert chain is NOT whitelisted. Proceeding with submission." ) ;
417- }
347+ if ( ! this . myGetBoolPref ( "use_whitelist" ) ) {
348+ this . log ( WARN , "Not using whitelist to filter cert chains." ) ;
349+ }
350+ else if ( this . isChainWhitelisted ( chain_hash ) ) {
351+ this . log ( INFO , "This cert chain is whitelisted. Not submitting." ) ;
352+ return ;
353+ }
354+ else {
355+ this . log ( INFO , "Cert chain is NOT whitelisted. Proceeding with submission." ) ;
356+ }
418357
419- if ( channel . URI . port == - 1 ) {
420- this . submitChainArray ( chainArray , fps , new String ( channel . URI . host ) , channel , host_ip , warning , false , chain_hash ) ;
421- } else {
422- this . submitChainArray ( chainArray , fps , channel . URI . host + ":" + channel . URI . port , channel , host_ip , warning , false , chain_hash ) ;
358+ if ( subject . URI . port == - 1 ) {
359+ this . submitChain ( chainArray , fps , new String ( subject . URI . host ) , subject , host_ip , false ) ;
360+ } else {
361+ this . submitChain ( chainArray , fps , subject . URI . host + ":" + subject . URI . port , subject , host_ip , false ) ;
362+ }
423363 }
424364 }
425365 } ,
@@ -466,95 +406,12 @@ SSLObservatory.prototype = {
466406 return this . prefs . getBoolPref ( "extensions.https_everywhere._observatory." + prefstring ) ;
467407 } ,
468408
469- loadCertWhitelist : function ( ) {
470- var loc = "chrome://https-everywhere/content/code/X509ChainWhitelist.json" ;
471- var file = CC [ "@mozilla.org/file/local;1" ] . createInstance ( CI . nsILocalFile ) ;
472- file . initWithPath ( this . HTTPSEverywhere . rw . chromeToPath ( loc ) ) ;
473- var data = this . HTTPSEverywhere . rw . read ( file ) ;
474- this . whitelist = JSON . parse ( data ) ;
475- } ,
476-
477- saveCertWhitelist : function ( ) {
478- var loc = "chrome://https-everywhere/content/code/X509ChainWhitelist.json" ;
479- var file = CC [ "@mozilla.org/file/local;1" ] . createInstance ( CI . nsILocalFile ) ;
480- var path = this . HTTPSEverywhere . rw . chromeToPath ( loc ) ;
481- this . log ( NOTE , "SAVING cert whitelist to " + path ) ;
482- file . initWithPath ( path ) ;
483- var store = JSON . stringify ( this . whitelist , null , " " ) ;
484- var data = this . HTTPSEverywhere . rw . write ( file , store ) ;
485- } ,
486-
487- maybeUpdateCertWhitelist : function ( ) {
488- // We aim to update the cert whitelist every 1-3 days
489- var due_pref = "extensions.https_everywhere._observatory.whitelist_update_due" ;
490- var update_due = this . prefs . getIntPref ( due_pref ) ;
491- var now = Date . now ( ) / 1000 ; // Date.now() is milliseconds, but let's be
492- // safe with int pref storage on 32 bit
493- // systems
494- var next = now + ( 1 + 2 * Math . random ( ) ) * 3600 * 24 ; // 1-3 days from now
495- if ( update_due == 0 ) {
496- // first run
497- this . prefs . setIntPref ( due_pref , next ) ;
498- return ;
499- }
500- if ( now < update_due ) return ;
501-
502- // Updating the certlist might yet fail. But that's okay, we can
503- // always live with a slightly older one.
504- this . prefs . setIntPref ( due_pref , next ) ;
505- this . log ( INFO , "Next whitelist update due at " + next ) ;
506-
507- this . updateCertWhitelist ( ) ;
508- } ,
509-
510- updateCertWhitelist : function ( ) {
511- // Fetch a new certificate whitelist by XHR and save it to disk
512- var req = Cc [ "@mozilla.org/xmlextras/xmlhttprequest;1" ]
513- . createInstance ( Ci . nsIXMLHttpRequest ) ;
514-
515- req . open ( "GET" , "https://s.eff.org/files/X509ChainWhitelist.json" , true ) ;
516- req . responseType = "json" ;
517-
518- var that = this ;
519- req . onreadystatechange = function ( ) {
520- if ( req . status == 200 ) {
521- if ( typeof req . response != "object" ) {
522- that . log ( WARN , "INSUFFICIENT WHITELIST OBJECTIVITY" ) ;
523- return false ;
524- }
525- var whitelist = req . response ;
526- var c = 0 ;
527- for ( var hash in whitelist ) {
528- c ++ ;
529- if ( typeof hash != "string" || hash . length != HASHLENGTH ) {
530- that . log ( WARN , "UNACCEPTABLE WHITELIST HASH " + hash ) ;
531- return false ;
532- }
533- }
534- if ( c < MIN_WHITELIST || c > MAX_WHITELIST ) {
535- that . log ( WARN , "Invalid chain whitelist of size " + c ) ;
536- return false ;
537- }
538- that . log ( WARN , "Routine update of SSL Observatory cert whitelist" ) ;
539- that . whitelist = whitelist ;
540- that . log ( NOTE , "Got valid whitelist..." + JSON . stringify ( whitelist ) ) ;
541- that . saveCertWhitelist ( ) ;
542- } else {
543- that . log ( NOTE , "Unexpected response status " + req . status + " fetching chain whitelist" ) ;
544- return false ;
545- }
546- }
547- req . send ( ) ;
548- } ,
549-
550409 isChainWhitelisted : function ( chainhash ) {
551- if ( this . whitelist == null ) {
410+ if ( X509ChainWhitelist == null ) {
552411 this . log ( WARN , "Could not find whitelist of popular certificate chains, so ignoring whitelist" ) ;
553- return null ;
412+ return false ;
554413 }
555-
556- if ( this . whitelist [ chainhash ] != null ) {
557- this . log ( INFO , "whitelist entry for " + chainhash ) ;
414+ if ( X509ChainWhitelist [ chainhash ] != null ) {
558415 return true ;
559416 }
560417 return false ;
@@ -679,7 +536,7 @@ SSLObservatory.prototype = {
679536 return true ;
680537 } ,
681538
682- submitChainArray : function ( certArray , fps , domain , channel , host_ip , warning , resubmitting , chain_hash ) {
539+ submitChain : function ( certArray , fps , domain , channel , host_ip , resubmitting ) {
683540 var base64Certs = [ ] ;
684541 // Put all this chain data in one object so that it can be modified by
685542 // subroutines if required
@@ -696,7 +553,7 @@ SSLObservatory.prototype = {
696553 if ( Object . keys ( this . delayed_submissions ) . length < MAX_DELAYED )
697554 if ( ! ( c . fps [ 0 ] in this . delayed_submissions ) ) {
698555 this . log ( WARN , "Planning to retry submission..." ) ;
699- let retry = function ( ) { this . submitChainArray ( certArray , fps , domain , channel , host_ip , warning , true , chain_hash ) ; } ;
556+ let retry = function ( ) { this . submitChain ( certArray , fps , domain , channel , host_ip , true ) ; } ;
700557 this . delayed_submissions [ c . fps [ 0 ] ] = retry ;
701558 }
702559 return ;
@@ -717,9 +574,9 @@ SSLObservatory.prototype = {
717574 if ( this . myGetBoolPref ( "testing" ) ) {
718575 reqParams . push ( "testing=1" ) ;
719576 // The server can compute these, but they're a nice test suite item!
720- reqParams . push ( "fplist=" + JSON . stringify ( c . fps ) ) ;
577+ reqParams . push ( "fplist=" + this . compatJSON . encode ( c . fps ) ) ;
721578 }
722- reqParams . push ( "certlist=" + JSON . stringify ( base64Certs ) ) ;
579+ reqParams . push ( "certlist=" + this . compatJSON . encode ( base64Certs ) ) ;
723580
724581 if ( resubmitting ) {
725582 reqParams . push ( "client_asn=" + ASN_UNKNOWABLE ) ;
@@ -765,7 +622,7 @@ SSLObservatory.prototype = {
765622 that . log ( DBUG , "Popping one off of outstanding requests, current num is: " + that . current_outstanding_requests ) ;
766623
767624 if ( req . status == 200 ) {
768- that . log ( NOTE , "Successful cert submission for " + domain + " " + chain_hash ) ;
625+ that . log ( INFO , "Successful cert submission" ) ;
769626 if ( ! that . prefs . getBoolPref ( "extensions.https_everywhere._observatory.cache_submitted" ) )
770627 if ( c . fps [ 0 ] in that . already_submitted )
771628 delete that . already_submitted [ c . fps [ 0 ] ] ;
@@ -779,22 +636,20 @@ SSLObservatory.prototype = {
779636 if ( ++ n >= 2 ) break ;
780637 }
781638 } else if ( req . status == 403 ) {
782- that . log ( INFO , "The SSL Observatory has issued a warning about this certificate for " + domain ) ;
783- if ( ! warning && that . prefs . getBoolPref ( "extensions.https_everywhere._observatory.show_cert_warning" ) ) {
784- try {
785- var warningObj = JSON . parse ( req . responseText ) ;
786- if ( win ) that . warnUser ( warningObj , win , c . certArray [ 0 ] ) ;
787- } catch ( e ) {
788- that . log ( WARN , "Failed to process SSL Observatory cert warnings :( " + e ) ;
789- that . log ( WARN , req . responseText ) ;
790- }
639+ that . log ( WARN , "The SSL Observatory has issued a warning about this certificate for " + domain ) ;
640+ try {
641+ var warningObj = JSON . parse ( req . responseText ) ;
642+ if ( win ) that . warnUser ( warningObj , win , c . certArray [ 0 ] ) ;
643+ } catch ( e ) {
644+ that . log ( WARN , "Failed to process SSL Observatory cert warnings :( " + e ) ;
645+ that . log ( WARN , req . responseText ) ;
791646 }
792647 } else {
793648 // Submission failed
794649 if ( c . fps [ 0 ] in that . already_submitted )
795650 delete that . already_submitted [ c . fps [ 0 ] ] ;
796651 try {
797- that . log ( WARN , "Cert submission failure " + req . status + " for " + domain + ": " + req . responseText ) ;
652+ that . log ( WARN , "Cert submission failure " + req . status + ": " + req . responseText ) ;
798653 } catch ( e ) {
799654 that . log ( WARN , "Cert submission failure and exception: " + e ) ;
800655 }
@@ -803,7 +658,7 @@ SSLObservatory.prototype = {
803658 if ( Object . keys ( that . delayed_submissions ) . length < MAX_DELAYED )
804659 if ( ! ( c . fps [ 0 ] in that . delayed_submissions ) ) {
805660 that . log ( WARN , "Planning to retry submission..." ) ;
806- let retry = function ( ) { that . submitChainArray ( certArray , fps , domain , channel , host_ip , warning , true , chain_hash ) ; } ;
661+ let retry = function ( ) { that . submitChain ( certArray , fps , domain , channel , host_ip , true ) ; } ;
807662 that . delayed_submissions [ c . fps [ 0 ] ] = retry ;
808663 }
809664
0 commit comments