如何在Firefox Add On中获取*current*页面的SSL证书信息

Tom*_*nen 5 javascript firefox firefox-addon firefox-5

我正在尝试开发一个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 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);
Run Code Online (Sandbox Code Playgroud)

我发现这个实现是不一致的.当我在Firefox中加载gmail.com时,我有时会获得证书信息,有时我不会.我怀疑这是一个缓存问题,因为刷新页面通常会导致下载/打印证书信息.

对于我的预期应用,这种行为是不可接受的.这是一个研究项目,所以,如果必须,我愿意修改Firefox源代码,但我倾向于使用扩展/附加API来做到这一点.

是否有更好,更一致的方式来获取SSL证书信息?

Wla*_*ant 3

您查询频道以获取其安全信息的方式似乎很合理。我怀疑你的问题实际上是时间问题 - 你在错误的时间查询它。如果您只关心安全信息,那么跟踪所有请求实际上是错误的方法。注册进度侦听器(有示例onSecurityChange)并在调用时查看通道更有意义。您可能只对aState包含STATE_IS_SECUREflag 的请求感兴趣。请注意,aRequest参数通常是一个nsIChannel实例,但也可以是一个普通的nsIRequest-instanceof需要检查。