我正在尝试开发一个Firefox扩展/附加组件,需要访问当前加载的页面的SSL证书信息。一旦有了这些信息,我就计划根据SSL信息修改页面的内容。但是,在我到达那里之前,我首先需要获得SSL信息。
这里概述的方法创建一个单独的XMLHTTPRequest来获取安全证书。如果可以避免,我宁愿不这样做,因为这会带来安全问题。
例如,恶意站点/中间人可以在对页面的第一个请求(浏览器将验证)上提供一个证书,然后为我的扩展将生成的XMLHTTPRequest提供另一个证书。这将导致扩展根据不一致的信息修改站点内容。因此,我想获得浏览器在验证站点时使用的SSL证书信息。 考虑到这一点,我将上述方法与在Firefox扩展中修改HTTP响应中概述的方法结合起来,通过添加"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 availablen");
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("securen");
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE)
dump("insecuren");
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN)
dump("unknownn");
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证书信息?
基于这个答案:
诀窍是注册一个进度侦听器,并在调用onSecurityChange
函数时检查aState
。如果设置了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 availablen");
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("securen");
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE) == Ci.nsIWebProgressListener.STATE_IS_INSECURE)
dump("insecuren");
else if ((secInfo.securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) == Ci.nsIWebProgressListener.STATE_IS_BROKEN)
dump("unknownn");
dump("tSecurity description: " + secInfo.shortSecurityDescription + "n");
dump("tSecurity error message: " + secInfo.errorMessage + "n");
}
else {
dump("tNo security info available for this channeln");
}
// 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("runn");
gBrowser.addProgressListener(myListener);
}
};
window.addEventListener("load", function () { test.run(); }, false);
查询通道以获取其安全信息的方式似乎是合理的。我怀疑你的问题实际上是时间——你在错误的时间查询它。如果您只对安全信息感兴趣,那么跟踪所有请求确实是错误的方法。注册一个进度侦听器(有一些示例)并在调用onSecurityChange
时查看通道更有意义。您可能只对aState
包含STATE_IS_SECURE
标志的请求感兴趣。注意,aRequest
参数通常是一个nsIChannel
实例,但也可以是一个普通的nsIRequest
- instanceof
检查是必需的。