From 7c994c36f077eb818cc912865de37334e19755ab Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Wed, 10 Apr 2013 18:56:57 -0400 Subject: [PATCH] Bug 954534 - Use toolkit untrusted cert dialog for "SSL Handshake failed" errors: chat/ part, r=florian. --- chat/components/public/imIAccount.idl | 5 ++++ chat/modules/jsProtoHelper.jsm | 34 +++++++++++++++++++++++++++ chat/modules/socket.jsm | 28 ++++++++++++++++------ chat/protocols/irc/irc.js | 9 +++---- chat/protocols/xmpp/xmpp-session.jsm | 5 ++-- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/chat/components/public/imIAccount.idl b/chat/components/public/imIAccount.idl index 15d3f0f181..42daa9a12a 100644 --- a/chat/components/public/imIAccount.idl +++ b/chat/components/public/imIAccount.idl @@ -103,6 +103,11 @@ interface prplIAccount: nsISupports { /* When a connection error occurred, this value indicates the type of error */ readonly attribute short connectionErrorReason; + /* When a certificate error occurs, the host/port that caused a + * SSL/certificate error when connecting to it. This is only valid when + * connectionErrorReason is one of ERROR_CERT_*. */ + readonly attribute AUTF8String connectionTarget; + /* Possible connection error reasons: ERROR_NETWORK_ERROR and ERROR_ENCRYPTION_ERROR are not fatal and should enable the automatic reconnection feature. */ diff --git a/chat/modules/jsProtoHelper.jsm b/chat/modules/jsProtoHelper.jsm index d55395859a..a9aa55c06a 100644 --- a/chat/modules/jsProtoHelper.jsm +++ b/chat/modules/jsProtoHelper.jsm @@ -52,6 +52,40 @@ const GenericAccountPrototype = { _connectionErrorReason: Ci.prplIAccount.NO_ERROR, get connectionErrorReason() this._connectionErrorReason, + handleBadCertificate: function(aSocket, aIsSslError) { + this._connectionTarget = aSocket.host + ":" + aSocket.port; + + if (aIsSslError) + return Ci.prplIAccount.ERROR_ENCRYPTION_ERROR; + + let sslStatus = aSocket.sslStatus; + if (!sslStatus) + return Ci.prplIAccount.ERROR_CERT_NOT_PROVIDED; + + if (sslStatus.isUntrusted) { + if (sslStatus.serverCert instanceof Ci.nsIX509Cert3 && + sslStatus.serverCert.isSelfSigned) + return Ci.prplIAccount.ERROR_CERT_SELF_SIGNED; + return Ci.prplIAccount.ERROR_CERT_UNTRUSTED; + } + + if (sslStatus.isNotValidAtThisTime) { + if (sslStatus.serverCert instanceof Ci.nsIX509Cert3 && + sslStatus.serverCert.validity.notBefore < Date.now() * 1000) + return Ci.prplIAccount.ERROR_CERT_NOT_ACTIVATED; + return Ci.prplIAccount.ERROR_CERT_EXPIRED; + } + + if (sslStatus.isDomainMismatch) + return Ci.prplIAccount.ERROR_CERT_HOSTNAME_MISMATCH; + + // XXX ERROR_CERT_FINGERPRINT_MISMATCH + + return Ci.prplIAccount.ERROR_CERT_OTHER_ERROR; + }, + _connectionTarget: "", + get connectionTarget() this._connectionTarget, + reportConnected: function() { this.imAccount.observe(this, "account-connected", null); }, diff --git a/chat/modules/socket.jsm b/chat/modules/socket.jsm index c48ab2faf2..81a6b5c91c 100644 --- a/chat/modules/socket.jsm +++ b/chat/modules/socket.jsm @@ -35,6 +35,7 @@ * connectTimeout (default is no timeout) * readWriteTimeout (default is no timeout) * isConnected + * sslStatus * * Users should "subclass" this object, i.e. set their .__proto__ to be it. And * then implement: @@ -42,7 +43,7 @@ * onConnectionHeard() * onConnectionTimedOut() * onConnectionReset() - * onBadCertificate(AString aNSSErrorMessage) + * onBadCertificate(boolean aIsSslError, AString aNSSErrorMessage) * onConnectionClosed() * onDataReceived(String ) * = onBinaryDataReceived(ArrayBuffer ) @@ -131,6 +132,9 @@ const Socket = { connectTimeout: 0, readWriteTimeout: 0, + // A nsISSLStatus instance giving details about the certificate error. + sslStatus: null, + /* ***************************************************************************** ******************************* Public methods ****************************** @@ -402,12 +406,16 @@ const Socket = { this.onConnectionReset(); else if (aStatus == NS_ERROR_NET_TIMEOUT) this.onConnectionTimedOut(); - else if (aStatus) { + else if (!Components.isSuccessCode(aStatus)) { let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService(Ci.nsINSSErrorsService); - if (aStatus <= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SEC_ERROR_BASE) && - aStatus >= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SEC_ERROR_LIMIT - 1)) { - this.onBadCertificate(nssErrorsService.getErrorMessage(aStatus)); + if ((aStatus <= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SEC_ERROR_BASE) && + aStatus >= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SEC_ERROR_LIMIT - 1)) || + (aStatus <= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SSL_ERROR_BASE) && + aStatus >= nssErrorsService.getXPCOMFromNSSError(nssErrorsService.NSS_SSL_ERROR_LIMIT - 1))) { + this.onBadCertificate(nssErrorsService.getErrorClass(aStatus) == + nssErrorsService.ERROR_CLASS_SSL_PROTOCOL, + nssErrorsService.getErrorMessage(aStatus)); return; } } @@ -419,12 +427,18 @@ const Socket = { */ // Called when there's an error, return true to suppress the modal alert. // Whatever this function returns, NSS will close the connection. - notifyCertProblem: function(aSocketInfo, aStatus, aTargetSite) true, + notifyCertProblem: function(aSocketInfo, aStatus, aTargetSite) { + this.sslStatus = aStatus; + return true; + }, /* * nsISSLErrorListener */ - notifySSLError: function(aSocketInfo, aError, aTargetSite) true, + notifySSLError: function(aSocketInfo, aError, aTargetSite) { + this.sslStatus = null; + return true; + }, /* * nsITransportEventSink methods diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js index d8a6c9197b..f8e974c663 100644 --- a/chat/protocols/irc/irc.js +++ b/chat/protocols/irc/irc.js @@ -674,10 +674,11 @@ ircSocket.prototype = { this._account.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR, _("connection.error.timeOut")); }, - onBadCertificate: function(aNSSErrorMessage) { - this.ERROR("bad certificate: " + aNSSErrorMessage); - this._account.gotDisconnected(Ci.prplIAccount.ERROR_CERT_OTHER_ERROR, - aNSSErrorMessage); + onBadCertificate: function(aIsSslError, aNSSErrorMessage) { + this.ERROR("Bad certificate or SSL connection for " + this._account.name + + ":\n" + aNSSErrorMessage); + let error = this._account.handleBadCertificate(this, aIsSslError); + this._account.gotDisconnected(error, aNSSErrorMessage); }, get DEBUG() this._account.DEBUG, diff --git a/chat/protocols/xmpp/xmpp-session.jsm b/chat/protocols/xmpp/xmpp-session.jsm index a7be373470..96f8cb7826 100644 --- a/chat/protocols/xmpp/xmpp-session.jsm +++ b/chat/protocols/xmpp/xmpp-session.jsm @@ -196,8 +196,9 @@ XMPPSession.prototype = { onConnectionClosed: function() { this._networkError(_("connection.error.serverClosedConnection")); }, - onBadCertificate: function(aNSSErrorMessage) { - this.onError(Ci.prplIAccount.ERROR_CERT_OTHER_ERROR, aNSSErrorMessage); + onBadCertificate: function(aIsSslError, aNSSErrorMessage) { + let error = this._account.handleBadCertificate(this, aIsSslError); + this.onError(error, aNSSErrorMessage); }, onConnectionReset: function() { this._networkError(_("connection.error.resetByPeer"));