I'm trying to develop a Firefox extension/add-on that needs access to the SSL Certificate information of the page that is currently loaded. Once I have this information I plan on modifying the contents of the page based on the SSL information. Though, before I get there I first need to get the SSL info.
The approach outlined here makes a separate XMLHTTPRequest to get the security certificate. I would rather not do that if I could avoid it because it presents a security problem.
For example, a malicious site/man-in-the-middle could provide one certificate on the first request for the page (which the browser would verify) and then provide another certificate for the XMLHTTPRequest that my extension would make. This would result in the extension modifying site contents based on inconsistent information. Hence, I'd like to get the SSL Cert information that the browser itself used when verifying the site.
With that in mind I combined the above approach with the method outlined in Altering HTTP Responses in Firefox Extension to intercept all the HTTP responses by adding an observer of the "http-on-examine-response" event. I thought that with this method I could simply grab the cert info as it was being downloaded from the site.
Here is the meat of my code, much of it taken from the above links (the rest is Firefox extension 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);
What I found is that this implementation is inconsistent. When I load gmail.com in Firefox I'll sometimes get the certificate information and sometimes I won't. I suspect this is a caching issue as refreshing the page will usually result in the certificate information being downloaded/printed.
For my intended application this behavior is not acceptable. This is for a research project so, if I have to, I would be willing to modify the Firefox source code, but my preference would be to do this using the extension/add-on API.
Is there a better, more consistent way to get the SSL Certificate information?
The way you query the channel to get its security info seems sane. I suspect that your problem is actually timing - you query it at the wrong time. Tracing all requests is really the wrong approach if security info is all you are interested in. It makes far more sense to register a progress listener (there are examples) and to look at the channel whenever
onSecurityChange
is being called. You are likely to be only interested in requests whereaState
containsSTATE_IS_SECURE
flag. Note thataRequest
parameter is usually annsIChannel
instance but could also be a plainnsIRequest
-instanceof
check is required.Building on this answer:
The trick is to register a progress listener and check
aState
when theonSecurityChange
function is called. If theCi.nsIWebProgressListener.STATE_IS_SECURE
flag is set then the page is using an SSL connection. That isn't enough however, theaRequest
parameter may not be an instance ofCi.nsIChannel
, that should be verified first withif (aRequest instanceof Ci.nsIChannel)
.Here is the working code: