草庐IT

javascript - 如何获取 Firefox 附加组件中*当前*页面的 SSL 证书信息

coder 2025-03-28 原文

我正在尝试开发需要访问当前加载页面的 SSL 证书信息的 Firefox 扩展/附加组件。获得此信息后,我计划根据 SSL 信息修改页面内容。不过,在我到达那里之前,我首先需要获取 SSL 信息。

概述的方法 here发出单独的 XMLHTTPRequest 以获取安全证书。如果可以避免,我宁愿不这样做,因为它会带来安全问题。

例如,恶意网站/中间人可以在第一次请求页面时提供一个证书(浏览器会验证),然后为我的扩展程序将发出的 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 时,我有时会获得证书信息,有时则不会。我怀疑这是一个缓存问题,因为刷新页面通常会导致下载/打印证书信息。

对于我的预期应用程序,这种行为是 Not Acceptable 。这是一个研究项目,因此,如果必须的话,我愿意修改 Firefox 源代码,但我更喜欢使用扩展/附加 API 来完成此操作。

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

最佳答案

基于 this回答:

诀窍是注册一个 progress listener并在调用 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 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);

关于javascript - 如何获取 Firefox 附加组件中*当前*页面的 SSL 证书信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6983401/

有关javascript - 如何获取 Firefox 附加组件中*当前*页面的 SSL 证书信息的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

随机推荐