2011-08-08 14 views
5

Próbuję utworzyć rozszerzenie/dodatek do przeglądarki Firefox, które potrzebuje dostępu do informacji o certyfikacie SSL strony, która jest aktualnie ładowana . Kiedy mam te informacje, planuję zmodyfikować zawartość strony na podstawie informacji o SSL. Jednak zanim tam dotrę, muszę najpierw uzyskać informacje o SSL.Jak uzyskać informacje o certyfikacie SSL dla strony * bieżącej * w przeglądarce Firefox Add On

Podejście opisane here tworzy osobny certyfikat XMLHTTPRequest w celu uzyskania certyfikatu bezpieczeństwa. Wolałbym tego nie robić, gdybym mógł tego uniknąć, ponieważ stwarza problem bezpieczeństwa.

Na przykład złośliwa witryna/man-in-the-middle może dostarczyć jeden certyfikat na pierwsze żądanie strony (które przeglądarka powinna zweryfikować), a następnie dostarczyć inny certyfikat dla XMLHTTPRequest, który zrobiłby mój rozszerzenie. Doprowadziłoby to do rozszerzenia modyfikującego zawartość witryny na podstawie niespójnych informacji. Dlatego chciałbym uzyskać informacje o certyfikacie SSL, które przeglądała sama przeglądarka.

W związku z tym połączyłem powyższe podejście z metodą opisaną w Altering HTTP Responses in Firefox Extension w celu przechwycenia wszystkich odpowiedzi HTTP przez dodanie obserwatora zdarzenia "http-on-examine-response". Pomyślałem, że dzięki tej metodzie mogłem po prostu pobrać informacje o certyfikacie podczas pobierania z witryny.

Oto mięso z mojego kodu, wiele z nich pochodzi z linków powyżej (reszta jest rozszerzenie Firefoksa boilerplate):

function dumpSecurityInfo(channel) { 

    const Cc = Components.classes 
    const Ci = Components.interfaces; 

    // Do we have a valid channel argument? 
    if (! channel instanceof Ci.nsIChannel) { 
     dump("No channel available\n"); 
     return; 
    } 

    var secInfo = channel.securityInfo; 


    // Print general connection security state 

    if (secInfo instanceof Ci.nsITransportSecurityInfo) { 
     dump("name: " + channel.name + "\n"); 
     secInfo.QueryInterface(Ci.nsITransportSecurityInfo); 

     dump("\tSecurity state: "); 

     // Check security state flags 
     if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_SECURE) == Ci.nsIWebProgressListener.STATE_IS_SECURE) 
      dump("secure\n"); 

     else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE) 
      dump("insecure\n"); 

     else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) 
      dump("unknown\n"); 

     dump("\tSecurity description: " + secInfo.shortSecurityDescription + "\n"); 
     dump("\tSecurity error message: " + secInfo.errorMessage + "\n"); 
    } 

    // Print SSL certificate details 
    if (secInfo instanceof Ci.nsISSLStatusProvider) { 

     var cert = secInfo.QueryInterface(Ci.nsISSLStatusProvider). 
     SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; 

     dump("\nCertificate Status:\n"); 

     var verificationResult = cert.verifyForUsage(Ci.nsIX509Cert.CERT_USAGE_SSLServer); 
     dump("\tVerification: "); 

     switch (verificationResult) { 
      case Ci.nsIX509Cert.VERIFIED_OK: 
       dump("OK"); 
       break; 
      case Ci.nsIX509Cert.NOT_VERIFIED_UNKNOWN: 
       dump("not verfied/unknown"); 
       break; 
      case Ci.nsIX509Cert.CERT_REVOKED: 
       dump("revoked"); 
       break; 
      case Ci.nsIX509Cert.CERT_EXPIRED: 
       dump("expired"); 
       break; 
      case Ci.nsIX509Cert.CERT_NOT_TRUSTED: 
       dump("not trusted"); 
       break; 
      case Ci.nsIX509Cert.ISSUER_NOT_TRUSTED: 
       dump("issuer not trusted"); 
       break; 
      case Ci.nsIX509Cert.ISSUER_UNKNOWN: 
       dump("issuer unknown"); 
       break; 
      case Ci.nsIX509Cert.INVALID_CA: 
       dump("invalid CA"); 
       break; 
      default: 
       dump("unexpected failure"); 
       break; 
     } 
     dump("\n"); 

     dump("\tCommon name (CN) = " + cert.commonName + "\n"); 
     dump("\tOrganisation = " + cert.organization + "\n"); 
     dump("\tIssuer = " + cert.issuerOrganization + "\n"); 
     dump("\tSHA1 fingerprint = " + cert.sha1Fingerprint + "\n"); 

     var validity = cert.validity.QueryInterface(Ci.nsIX509CertValidity); 
     dump("\tValid from " + validity.notBeforeGMT + "\n"); 
     dump("\tValid until " + validity.notAfterGMT + "\n"); 
    } 
} 

function TracingListener() { 
} 

TracingListener.prototype = 
{ 
    originalListener: null, 

    onDataAvailable: function(request, context, inputStream, offset, count) { 
     try 
     { 
      dumpSecurityInfo(request) 
      this.originalListener.onDataAvailable(request, context, inputStream, offset, count); 
     } catch (err) { 
      dump(err); 
      if (err instanceof Ci.nsIException) 
      { 
       request.cancel(e.result); 
      } 
     } 
    }, 

    onStartRequest: function(request, context) { 
     try 
     { 
      dumpSecurityInfo(request) 
      this.originalListener.onStartRequest(request, context); 
     } catch (err) { 
      dump(err); 
      if (err instanceof Ci.nsIException) 
      { 
       request.cancel(e.result); 
      } 
     } 
    }, 

    onStopRequest: function(request, context, statusCode) { 
     this.originalListener.onStopRequest(request, context, statusCode); 
    }, 

    QueryInterface: function (aIID) { 
     const Ci = Components.interfaces; 
     if (iid.equals(Ci.nsIObserver) || 
      iid.equals(Ci.nsISupportsWeakReference)   || 
      iid.equals(Ci.nsISupports)) 
     { 
      return this; 
     } 
     throw Components.results.NS_NOINTERFACE; 
    } 
} 


var httpRequestObserver = 
{ 
    observe: function(aSubject, aTopic, aData) 
    { 
     const Ci = Components.interfaces; 
     if (aTopic == "http-on-examine-response") 
     { 
      var newListener = new TracingListener(); 
      aSubject.QueryInterface(Ci.nsITraceableChannel); 
      newListener.originalListener = aSubject.setNewListener(newListener); 
     } 
    }, 

    QueryInterface : function (aIID) 
    { 
     const Ci = Components.interfaces; 
     if (aIID.equals(Ci.nsIObserver) || 
      aIID.equals(Ci.nsISupports)) 
     { 
      return this; 
     } 

     throw Components.results.NS_NOINTERFACE; 

    } 
}; 

var test = 
{ 
    run: function() { 
     const Ci = Components.interfaces; 
     dump("run"); 
     var observerService = Components.classes["@mozilla.org/observer-service;1"] 
      .getService(Ci.nsIObserverService);  
     observerService.addObserver(httpRequestObserver, 
      "http-on-examine-response", false); 
    } 
}; 

window.addEventListener("load", function() { test.run(); }, false); 

Co znalazłem to, że ta realizacja jest niespójne. Kiedy ładuję gmail.com w Firefoksie, czasami otrzymuję informacje o certyfikacie, a czasami nie. Podejrzewam, że jest to problem z pamięcią podręczną, ponieważ odświeżenie strony zazwyczaj powoduje pobranie/wydrukowanie informacji o certyfikacie.

Dla mojego zamierzonego zastosowania to zachowanie jest nie do przyjęcia. To jest dla projektu badawczego, więc jeśli będę musiał, będę skłonny zmodyfikować kod źródłowy Firefoxa, ale moje preferencje byłoby to zrobić za pomocą interfejsu API rozszerzenia/dodatku.

Czy istnieje lepszy, bardziej spójny sposób uzyskania informacji o certyfikacie SSL?

+1

Nie należy połknąć błędów w 'TracingListener'. Kiedyś to robiłem i zauważyłem, że powoduje awarie spowodowane niespójnym stanem. Jeśli oryginalny odbiornik zgłosił błąd i nie chcesz go przechowywać (z powodu spamu konsoli błędów), żądanie musi zostać anulowane. W ten sposób: 'catch (e jeśli e instancja Ci.nsIException) {request.cancel (e.result);}' –

+0

Wprowadziłem kilka zmian do pytania zgodnie z Twoją sugestią. Czy to załatwia sprawę, którą opisujesz? –

+0

Tak, w ten sposób powinien działać poprawnie. –

Odpowiedz

3

Sposób wysyłania zapytań o kanał w celu uzyskania informacji o zabezpieczeniach wydaje się rozsądny. Podejrzewam, że twój problem jest w rzeczywistości - zapytanie to w nieodpowiednim momencie. Śledzenie wszystkich żądań jest naprawdę błędnym podejściem, jeśli informacje o zabezpieczeniach są wszystkim, czym jesteś zainteresowany. Sensownym jest rejestrowanie odbiornika postępu (są examples) i patrzenie na kanał, gdy jest wywoływany onSecurityChange. Prawdopodobnie interesują Cię tylko żądania, w których aState zawiera STATE_IS_SECURE flag. Zauważ, że parametr aRequest jest zwykle instancją nsIChannel, ale może również wymagać zwykłego sprawdzenia.

+0

Uwaga "onSecurityChange" jest już starszym API i nie działa już w aktualnym Firefoxie: https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Code_snippets/Progress_Listeners – mikemaccana

+0

@mikemaccana: Prawie wszystko jest już starszym API i już nie działa. Wszelkie informacje dotyczące dodatków do Firefoksa sprzed 2017 roku są przestarzałe. –

3

Opierając się na this odpowiedź:

Sztuką jest, aby zarejestrować progress listener i sprawdzić aState gdy funkcja onSecurityChange nazywa. Jeśli ustawiona jest flaga Ci.nsIWebProgressListener.STATE_IS_SECURE, strona korzysta z połączenia SSL. To jednak nie wystarcza, parametr aRequest może nie być instancją Ci.nsIChannel, którą należy najpierw zweryfikować przy pomocy if (aRequest instanceof Ci.nsIChannel).

Oto kod działa:

function dumpSecurityInfo(channel) { 

    const Cc = Components.classes 
    const Ci = Components.interfaces; 

    // Do we have a valid channel argument? 
    if (! channel instanceof Ci.nsIChannel) { 
     dump("No channel available\n"); 
     return; 
    } 

    var secInfo = channel.securityInfo; 

    // Print general connection security state 
    if (secInfo instanceof Ci.nsITransportSecurityInfo) { 
     dump("name: " + channel.name + "\n"); 
     secInfo.QueryInterface(Ci.nsITransportSecurityInfo); 

     dump("\tSecurity state: "); 

     // Check security state flags 
     if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_SECURE) == Ci.nsIWebProgressListener.STATE_IS_SECURE) 
      dump("secure\n"); 

     else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE) 
      dump("insecure\n"); 

     else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN) 
      dump("unknown\n"); 

     dump("\tSecurity description: " + secInfo.shortSecurityDescription + "\n"); 
     dump("\tSecurity error message: " + secInfo.errorMessage + "\n"); 
    } 
    else { 

     dump("\tNo security info available for this channel\n"); 
    } 

    // Print SSL certificate details 
    if (secInfo instanceof Ci.nsISSLStatusProvider) { 

     var cert = secInfo.QueryInterface(Ci.nsISSLStatusProvider). 
     SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; 

     dump("\nCertificate Status:\n"); 

     var verificationResult = cert.verifyForUsage(Ci.nsIX509Cert.CERT_USAGE_SSLServer); 
     dump("\tVerification: "); 

     switch (verificationResult) { 
      case Ci.nsIX509Cert.VERIFIED_OK: 
       dump("OK"); 
       break; 
      case Ci.nsIX509Cert.NOT_VERIFIED_UNKNOWN: 
       dump("not verfied/unknown"); 
       break; 
      case Ci.nsIX509Cert.CERT_REVOKED: 
       dump("revoked"); 
       break; 
      case Ci.nsIX509Cert.CERT_EXPIRED: 
       dump("expired"); 
       break; 
      case Ci.nsIX509Cert.CERT_NOT_TRUSTED: 
       dump("not trusted"); 
       break; 
      case Ci.nsIX509Cert.ISSUER_NOT_TRUSTED: 
       dump("issuer not trusted"); 
       break; 
      case Ci.nsIX509Cert.ISSUER_UNKNOWN: 
       dump("issuer unknown"); 
       break; 
      case Ci.nsIX509Cert.INVALID_CA: 
       dump("invalid CA"); 
       break; 
      default: 
       dump("unexpected failure"); 
       break; 
     } 
     dump("\n"); 

     dump("\tCommon name (CN) = " + cert.commonName + "\n"); 
     dump("\tOrganisation = " + cert.organization + "\n"); 
     dump("\tIssuer = " + cert.issuerOrganization + "\n"); 
     dump("\tSHA1 fingerprint = " + cert.sha1Fingerprint + "\n"); 

     var validity = cert.validity.QueryInterface(Ci.nsIX509CertValidity); 
     dump("\tValid from " + validity.notBeforeGMT + "\n"); 
     dump("\tValid until " + validity.notAfterGMT + "\n"); 
    } 
} 

var myListener = 
{ 
    QueryInterface: function(aIID) 
    { 
     if (aIID.equals(Components.interfaces.nsIWebProgressListener) || 
      aIID.equals(Components.interfaces.nsISupportsWeakReference) || 
      aIID.equals(Components.interfaces.nsISupports)) 
      return this; 
     throw Components.results.NS_NOINTERFACE; 
    }, 

    onStateChange: function(aWebProgress, aRequest, aFlag, aStatus) { }, 

    onLocationChange: function(aProgress, aRequest, aURI) { }, 

    onProgressChange: function(aWebProgress, aRequest, curSelf, maxSelf, curTot, maxTot) { }, 
    onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) { }, 
    onSecurityChange: function(aWebProgress, aRequest, aState) 
    { 
     // check if the state is secure or not 
     if(aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) 
     { 
      // this is a secure page, check if aRequest is a channel, 
      // since only channels have security information 
      if (aRequest instanceof Ci.nsIChannel) 
      { 
       dumpSecurityInfo(aRequest); 
      } 
     }  
    } 
} 

var test = 
{ 
    run: function() { 
     dump("run\n"); 
     gBrowser.addProgressListener(myListener); 
    } 
}; 

window.addEventListener("load", function() { test.run(); }, false); 
Powiązane problemy