Как я могу получить информацию о SSL-сертификате для * текущей * страницы в Firefox Add On
Я пытаюсь разработать расширение / надстройку 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 ответа
То, как вы запрашиваете канал, чтобы получить информацию о безопасности, кажется нормальным. Я подозреваю, что ваша проблема на самом деле является временем - вы запрашиваете ее в неправильное время. Отслеживание всех запросов - действительно неправильный подход, если информация о безопасности - это все, что вас интересует. Имеет гораздо больше смысла регистрировать прослушиватель прогресса (есть примеры) и смотреть на канал всякий раз, когда onSecurityChange
называется Вы, вероятно, будете заинтересованы только в запросах, где aState
содержит STATE_IS_SECURE
флаг. Обратите внимание, что aRequest
Параметр обычно nsIChannel
экземпляр, но также может быть простым nsIRequest
- instanceof
проверка обязательна.
Опираясь на этот ответ:
Хитрость заключается в том, чтобы зарегистрировать слушателя прогресса и проверить 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);