2011-08-08 42 views
5

我正在嘗試開發一個需要訪問當前加載的頁面的SSL證書信息的Firefox擴展/附加組件。一旦我掌握了這些信息,我計劃根據SSL信息修改頁面內容。雖然,在我到達那裏之前,我首先需要獲取SSL信息。我如何獲得Firefox當前*頁的SSL證書信息添加開啓

概述的方法here使得單獨的XMLHTTP請求獲得安全證書。如果我能避免它,我寧願不這樣做,因爲它會帶來安全問題。

例如,惡意網站/中間人可以在頁面的第一個請求(瀏覽器將驗證)提供一個證書,然後爲我的擴展將提供的XMLHTTPRequest提供另一個證書。這將導致擴展程序根據不一致的信息修改站點內容。因此,我想獲取瀏覽器在驗證網站時使用的SSL證書信息。

考慮到這一點,我將上述方法與Altering HTTP Responses in Firefox Extension中概述的方法相結合,通過添加「http-on-examine-response」事件的觀察者來攔截所有HTTP響應。我認爲使用這種方法我可以簡單地獲取從站點下載的證書信息。

這裏是我的代碼的肉,其中大部分是從上面的鏈接所(其餘爲Firefox擴展樣板):

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); 

我發現那是什麼這個實現是不一致的。當我在Firefox中加載gmail.com時,我有時會得到證書信息,有時我不會。我懷疑這是一個緩存問題,因爲刷新頁面通常會導致證書信息被下載/打印。

對於我的預期應用,這種行爲是不可接受的。這是一個研究項目,所以如果必須的話,我會願意修改Firefox源代碼,但是我的偏好是使用擴展/附加API來完成。

是否有更好,更一致的方式獲取SSL證書信息?

+1

你不應該在你的'TracingListener'吞下錯誤。我曾經這樣做,並注意到它會導致崩潰,由於狀態不一致。如果原始偵聽器拋出一個錯誤並且您不想保留它(由於錯誤控制檯垃圾郵件),那麼請求需要被取消。像這樣:'catch(e if instanceof Ci.nsIException){request.cancel(e.result);}' –

+0

我根據您的建議對問題進行了一些編輯。這是否處理你描述的情況? –

+0

是的,這種方式應該可以正常工作。 –

回答

3

您查詢頻道以獲取其安全信息的方式看起來很理智。我懷疑你的問題實際上是在計時 - 你在錯誤的時間查詢它。如果安全信息是您感興趣的,那麼跟蹤所有請求實際上是錯誤的方法。註冊進度監聽器(有examples)以及在調用onSecurityChange時查看通道會更有意義。您可能只對aState包含STATE_IS_SECURE flag的請求感興趣。請注意,aRequest參數通常是nsIChannel實例,但也可能是普通的nsIRequest - instanceof檢查是必需的。

+0

注意onSecurityChange現在是一個傳統API,不再適用於當前的Firefox:https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Code_snippets/Progress_Listeners – mikemaccana

+0

@mikemaccana:幾乎所有東西現在都是傳統API,不再有效。任何關於Firefox附加組件的2017年以前的信息都已過時。 –

3

大廈this答案:

關鍵是要註冊一個progress listener和檢查aStateonSecurityChange函數被調用。如果設置了Ci.nsIWebProgressListener.STATE_IS_SECURE標誌,則頁面正在使用SSL連接。但這還不夠,aRequest參數可能不是Ci.nsIChannel的實例,應該先用if (aRequest instanceof Ci.nsIChannel)進行驗證。

這裏是工作代碼:

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);