Как я могу получить информацию о сертификате SSL для * текущей * страницы в Firefox Add On - PullRequest
5 голосов
/ 08 августа 2011

Я пытаюсь разработать расширение / надстройку Firefox, для которого требуется доступ к информации о сертификате SSL на странице, которая загружена в данный момент.Как только я получу эту информацию, я планирую изменить содержимое страницы на основе информации SSL.Однако прежде чем попасть туда, мне сначала нужно получить информацию о SSL.

Подход, изложенный здесь , создает отдельный запрос XMLHTTPRequest для получения сертификата безопасности.Я бы предпочел не делать этого, если бы мог избежать этого, потому что это представляет проблему безопасности.

Например, злонамеренный сайт / человек-посредник может предоставить один сертификат при первом запросе страницы (который будет проверять браузер), а затем предоставить другой сертификат для XMLHTTPRequest, который мое расширение будетделать.Это приведет к тому, что расширение изменяет содержимое сайта на основании противоречивой информации.Поэтому я хотел бы получить информацию SSL-сертификата, которую сам браузер использовал при проверке сайта.

Имея это в виду, я объединил описанный выше подход с методом, описанным в Изменение HTTP-ответов в Firefox.Расширение для перехвата всех ответов HTTP путем добавления наблюдателя события «http-on-exam-response».Я подумал, что с помощью этого метода я мог бы просто получить информацию сертификата, когда она загружалась с сайта.

Вот основная часть моего кода, большая часть которого взята из приведенных выше ссылок (остальное расширение 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);

Я обнаружил, что эта реализация несовместима.Когда я загружаю gmail.com в Firefox, я иногда получаю информацию о сертификате, а иногда нет.Я подозреваю, что это проблема кэширования, поскольку обновление страницы обычно приводит к загрузке / печати информации о сертификате.

Для моего предполагаемого приложения это поведение неприемлемо.Это для исследовательского проекта, поэтому, если потребуется, я бы хотел изменить исходный код Firefox, но я бы предпочел сделать это с помощью API расширения / дополнения.

Есть лилучший, более последовательный способ получить информацию о сертификате SSL?

Ответы [ 2 ]

3 голосов
/ 10 августа 2011

Опираясь на этот ответ:

Хитрость заключается в том, чтобы зарегистрировать обработчик прогресса и проверить aState при вызове функции onSecurityChange.Если установлен флаг 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);
3 голосов
/ 09 августа 2011

То, как вы запрашиваете канал для получения информации о безопасности, кажется нормальным. Я подозреваю, что ваша проблема на самом деле является временем - вы запрашиваете ее в неправильное время. Отслеживание всех запросов - действительно неправильный подход, если информация о безопасности - это все, что вас интересует. Имеет гораздо больше смысла регистрировать обработчик прогресса (есть примеров ) и просматривать канал всякий раз, когда onSecurityChange будучи призванным Вероятно, вас заинтересуют только те запросы, в которых aState содержит STATE_IS_SECURE flag . Обратите внимание, что параметр aRequest обычно является экземпляром nsIChannel, но также может быть простой проверкой nsIRequest - instanceof.

...