From e2f2e48f6e01739ccaa763ff7f037306131d4e61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 7 Nov 2010 15:58:23 +0100
Subject: Added security error handling to Swiften.


diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index 4c2be95..7170a20 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -11,6 +11,7 @@
 #include <boost/uuid/uuid_io.hpp>
 #include <boost/uuid/uuid_generators.hpp>
 
+#include "Swiften/TLS/SecurityError.h"
 #include "Swiften/Elements/ProtocolHeader.h"
 #include "Swiften/Elements/StreamFeatures.h"
 #include "Swiften/Elements/StartTLSRequest.h"
@@ -322,8 +323,26 @@ void ClientSession::sendCredentials(const String& password) {
 	stream->writeElement(boost::shared_ptr<AuthRequest>(new AuthRequest(authenticator->getName(), authenticator->getResponse())));
 }
 
+void ClientSession::continueAfterSecurityError() {
+	checkState(WaitingForContinueAfterSecurityError);
+	continueAfterTLSEncrypted();
+}
+
 void ClientSession::handleTLSEncrypted() {
 	checkState(Encrypting);
+
+	Certificate::ref certificate = stream->getPeerCertificate();
+	boost::optional<CertificateVerificationError> verificationError = stream->getPeerCertificateVerificationError();
+	if (verificationError) {
+		state = WaitingForContinueAfterSecurityError;
+		onSecurityError(SecurityError(*verificationError));
+	}
+	else {
+		continueAfterTLSEncrypted();
+	}
+}
+
+void ClientSession::continueAfterTLSEncrypted() {
 	state = WaitingForStreamStart;
 	stream->resetXMPPParser();
 	sendStreamHeader();
diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h
index 83744e0..b14a6ec 100644
--- a/Swiften/Client/ClientSession.h
+++ b/Swiften/Client/ClientSession.h
@@ -20,6 +20,7 @@
 
 namespace Swift {
 	class ClientAuthenticator;
+	class SecurityError;
 
 	class ClientSession : public boost::enable_shared_from_this<ClientSession> {
 		public:
@@ -30,6 +31,7 @@ namespace Swift {
 				Compressing,
 				WaitingForEncrypt,
 				Encrypting,
+				WaitingForContinueAfterSecurityError,
 				WaitingForCredentials,
 				Authenticating,
 				EnablingSessionManagement,
@@ -81,9 +83,11 @@ namespace Swift {
 
 			void sendCredentials(const String& password);
 			void sendStanza(boost::shared_ptr<Stanza>);
+			void continueAfterSecurityError();
 
 		public:
 			boost::signal<void ()> onNeedCredentials;
+			boost::signal<void (const SecurityError&)> onSecurityError;
 			boost::signal<void ()> onInitialized;
 			boost::signal<void (boost::shared_ptr<Swift::Error>)> onFinished;
 			boost::signal<void (boost::shared_ptr<Stanza>)> onStanzaReceived;
@@ -115,6 +119,7 @@ namespace Swift {
 			void requestAck();
 			void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
 			void ack(unsigned int handledStanzasCount);
+			void continueAfterTLSEncrypted();
 
 		private:
 			JID localJID;
diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp
index 214e6b1..4202483 100644
--- a/Swiften/Client/CoreClient.cpp
+++ b/Swiften/Client/CoreClient.cpp
@@ -23,7 +23,7 @@
 
 namespace Swift {
 
-CoreClient::CoreClient(EventLoop* eventLoop, const JID& jid, const String& password) : resolver_(eventLoop), jid_(jid), password_(password), eventLoop(eventLoop), disconnectRequested_(false) {
+CoreClient::CoreClient(EventLoop* eventLoop, const JID& jid, const String& password) : resolver_(eventLoop), jid_(jid), password_(password), eventLoop(eventLoop), disconnectRequested_(false), ignoreSecurityErrors(true) {
 	stanzaChannel_ = new ClientSessionStanzaChannel();
 	stanzaChannel_->onMessageReceived.connect(boost::ref(onMessageReceived));
 	stanzaChannel_->onPresenceReceived.connect(boost::ref(onPresenceReceived));
@@ -93,6 +93,7 @@ void CoreClient::handleConnectorFinished(boost::shared_ptr<Connection> connectio
 		stanzaChannel_->setSession(session_);
 		session_->onFinished.connect(boost::bind(&CoreClient::handleSessionFinished, this, _1));
 		session_->onNeedCredentials.connect(boost::bind(&CoreClient::handleNeedCredentials, this));
+		session_->onSecurityError.connect(boost::bind(&CoreClient::handleSecurityError, this, _1));
 		session_->start();
 	}
 }
@@ -114,6 +115,7 @@ void CoreClient::setCertificate(const String& certificate) {
 }
 
 void CoreClient::handleSessionFinished(boost::shared_ptr<Error> error) {
+	session_->onSecurityError.disconnect(boost::bind(&CoreClient::handleSecurityError, this, _1));
 	session_->onFinished.disconnect(boost::bind(&CoreClient::handleSessionFinished, this, _1));
 	session_->onNeedCredentials.disconnect(boost::bind(&CoreClient::handleNeedCredentials, this));
 	session_.reset();
@@ -214,4 +216,17 @@ bool CoreClient::isActive() const {
 	return session_ || connector_;
 }
 
+void CoreClient::handleSecurityError(const SecurityError& error) {
+	if (ignoreSecurityErrors) {
+		session_->continueAfterSecurityError();
+	}
+	else {
+		onSecurityError(error);
+	}
+}
+
+void CoreClient::continueAfterSecurityError() {
+	session_->continueAfterSecurityError();
+}
+
 }
diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h
index 4170e8d..3176a51 100644
--- a/Swiften/Client/CoreClient.h
+++ b/Swiften/Client/CoreClient.h
@@ -32,6 +32,7 @@ namespace Swift {
 	class ClientSession;
 	class BasicSessionStream;
 	class EventLoop;
+	class SecurityError;
 
 	/** 
 	 * The central class for communicating with an XMPP server.
@@ -71,6 +72,14 @@ namespace Swift {
 			void connect(const String& host);
 			
 			/**
+			 * Instructs the client to continue initializing the session
+			 * after a security error has occurred (and as such ignoring the error)
+			 *
+			 * \see onSecurityError
+			 */
+			void continueAfterSecurityError();
+
+			/**
 			 * Sends a message.
 			 */
 			void sendMessage(Message::ref);
@@ -131,8 +140,29 @@ namespace Swift {
 				return stanzaChannel_;
 			}
 
+			/**
+			 * Sets whether security errors should be ignored or not.
+			 *
+			 * If this is set to 'true', onSecurityError will not be called when a security
+			 * error occurs, and connecting will continue.
+			 *
+			 * Defaults to true.
+			 */
+			void setIgnoreSecurityErrors(bool b) {
+				ignoreSecurityErrors = b;
+			}
+
 		public:
 			/**
+			 * Emitted when an error occurred while establishing a secure connection.
+			 *
+			 * If the error is to be ignored, call continueAfterSecurityError(), otherwise call
+			 * finish().
+			 * This signal is not emitted when setIgnoreSecurityErrors() is set to true.
+			 */
+			boost::signal<void (const SecurityError&)> onSecurityError;
+
+			/**
 			 * Emitted when the client was disconnected from the network.
 			 *
 			 * If the connection was due to a non-recoverable error, the type
@@ -187,6 +217,7 @@ namespace Swift {
 			void handleNeedCredentials();
 			void handleDataRead(const String&);
 			void handleDataWritten(const String&);
+			void handleSecurityError(const SecurityError& securityError);
 
 		private:
 			PlatformDomainNameResolver resolver_;
@@ -206,5 +237,6 @@ namespace Swift {
 			boost::shared_ptr<ClientSession> session_;
 			String certificate_;
 			bool disconnectRequested_;
+			bool ignoreSecurityErrors;
 	};
 }
diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp
index 2cd9fd2..43a8bf3 100644
--- a/Swiften/Client/UnitTest/ClientSessionTest.cpp
+++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp
@@ -283,6 +283,14 @@ class ClientSessionTest : public CppUnit::TestFixture {
 					return tlsEncrypted;
 				}
 
+				virtual Certificate::ref getPeerCertificate() const {
+					return Certificate::ref();
+				}
+
+				virtual boost::optional<CertificateVerificationError> getPeerCertificateVerificationError() const {
+					return boost::optional<CertificateVerificationError>();
+				}
+
 				virtual void addZLibCompression() {
 					compressed = true;
 				}
diff --git a/Swiften/Component/UnitTest/ComponentSessionTest.cpp b/Swiften/Component/UnitTest/ComponentSessionTest.cpp
index d35a664..b6b57dd 100644
--- a/Swiften/Component/UnitTest/ComponentSessionTest.cpp
+++ b/Swiften/Component/UnitTest/ComponentSessionTest.cpp
@@ -123,6 +123,14 @@ class ComponentSessionTest : public CppUnit::TestFixture {
 					return false;
 				}
 
+				virtual Certificate::ref getPeerCertificate() const {
+					return Certificate::ref();
+				}
+
+				virtual boost::optional<CertificateVerificationError> getPeerCertificateVerificationError() const {
+					return boost::optional<CertificateVerificationError>();
+				}
+
 				virtual void addZLibCompression() {
 					assert(false);
 				}
diff --git a/Swiften/Session/BasicSessionStream.cpp b/Swiften/Session/BasicSessionStream.cpp
index a4b1c84..65a241c 100644
--- a/Swiften/Session/BasicSessionStream.cpp
+++ b/Swiften/Session/BasicSessionStream.cpp
@@ -85,6 +85,15 @@ bool BasicSessionStream::isTLSEncrypted() {
 	return tlsLayer;
 }
 
+Certificate::ref BasicSessionStream::getPeerCertificate() const {
+	return tlsLayer->getPeerCertificate();
+}
+
+boost::optional<CertificateVerificationError> BasicSessionStream::getPeerCertificateVerificationError() const {
+	return tlsLayer->getPeerCertificateVerificationError();
+}
+
+
 void BasicSessionStream::addZLibCompression() {
 	boost::shared_ptr<CompressionLayer> compressionLayer(new CompressionLayer());
 	streamStack->addLayer(compressionLayer);
diff --git a/Swiften/Session/BasicSessionStream.h b/Swiften/Session/BasicSessionStream.h
index 22620be..8addeb6 100644
--- a/Swiften/Session/BasicSessionStream.h
+++ b/Swiften/Session/BasicSessionStream.h
@@ -52,6 +52,8 @@ namespace Swift {
 			virtual bool supportsTLSEncryption();
 			virtual void addTLSEncryption();
 			virtual bool isTLSEncrypted();
+			virtual Certificate::ref getPeerCertificate() const;
+			virtual boost::optional<CertificateVerificationError> getPeerCertificateVerificationError() const;
 
 			virtual void setWhitespacePingEnabled(bool);
 
diff --git a/Swiften/Session/SessionStream.h b/Swiften/Session/SessionStream.h
index b2bf9a6..1bf9090 100644
--- a/Swiften/Session/SessionStream.h
+++ b/Swiften/Session/SessionStream.h
@@ -8,11 +8,14 @@
 
 #include "Swiften/Base/boost_bsignals.h"
 #include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
 
 #include "Swiften/Elements/ProtocolHeader.h"
 #include "Swiften/Elements/Element.h"
 #include "Swiften/Base/Error.h"
 #include "Swiften/TLS/PKCS12Certificate.h"
+#include "Swiften/TLS/Certificate.h"
+#include "Swiften/TLS/CertificateVerificationError.h"
 
 namespace Swift {
 	class SessionStream {
@@ -57,6 +60,8 @@ namespace Swift {
 				return !certificate.isNull();
 			}
 
+			virtual Certificate::ref getPeerCertificate() const = 0;
+			virtual boost::optional<CertificateVerificationError> getPeerCertificateVerificationError() const = 0;
 
 			boost::signal<void (const ProtocolHeader&)> onStreamStartReceived;
 			boost::signal<void (boost::shared_ptr<Element>)> onElementReceived;
diff --git a/Swiften/TLS/CertificateVerificationError.h b/Swiften/TLS/CertificateVerificationError.h
index 71895ff..76b4aff 100644
--- a/Swiften/TLS/CertificateVerificationError.h
+++ b/Swiften/TLS/CertificateVerificationError.h
@@ -11,6 +11,15 @@ namespace Swift {
 		public:
 			enum Type {
 				UnknownError,
+				Expired,
+				NotYetValid,
+				SelfSigned,
+				Rejected,
+				Untrusted,
+				InvalidPurpose,
+				PathLengthExceeded,
+				InvalidSignature,
+				InvalidCA,
 			};
 
 			CertificateVerificationError(Type type = UnknownError) : type(type) {}
diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp
index 234c831..c78d5a1 100644
--- a/Swiften/TLS/OpenSSL/OpenSSLContext.cpp
+++ b/Swiften/TLS/OpenSSL/OpenSSLContext.cpp
@@ -221,13 +221,74 @@ Certificate::ref OpenSSLContext::getPeerCertificate() const {
 }
 
 boost::optional<CertificateVerificationError> OpenSSLContext::getPeerCertificateVerificationError() const {
-	long verifyResult = SSL_get_verify_result(handle_);
+	int verifyResult = SSL_get_verify_result(handle_);
 	if (verifyResult != X509_V_OK) {
-		return CertificateVerificationError();
+		return CertificateVerificationError(getVerificationErrorTypeForResult(verifyResult));
 	}
 	else {
 		return boost::optional<CertificateVerificationError>();
 	}
 }
 
+CertificateVerificationError::Type OpenSSLContext::getVerificationErrorTypeForResult(int result) {
+	assert(result != 0);
+	switch (result) {
+		case X509_V_ERR_CERT_NOT_YET_VALID:
+		case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+			return CertificateVerificationError::NotYetValid;
+
+		case X509_V_ERR_CERT_HAS_EXPIRED:
+		case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+			return CertificateVerificationError::Expired;
+
+		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+		case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+			return CertificateVerificationError::SelfSigned;
+
+		case X509_V_ERR_CERT_UNTRUSTED:
+			return CertificateVerificationError::Untrusted;
+
+		case X509_V_ERR_CERT_REJECTED:
+			return CertificateVerificationError::Rejected;
+
+		case X509_V_ERR_INVALID_PURPOSE:
+			return CertificateVerificationError::InvalidPurpose;
+
+		case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+			return CertificateVerificationError::PathLengthExceeded;
+
+		case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+		case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+		case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+			return CertificateVerificationError::InvalidSignature;
+
+		case X509_V_ERR_INVALID_CA:
+		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+		case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+			return CertificateVerificationError::InvalidCA;
+
+		case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+		case X509_V_ERR_AKID_SKID_MISMATCH:
+		case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+		case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+			return CertificateVerificationError::UnknownError;
+
+		// Unused / should not happen
+		case X509_V_ERR_CERT_REVOKED:
+		case X509_V_ERR_OUT_OF_MEM:
+		case X509_V_ERR_UNABLE_TO_GET_CRL:
+		case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+		case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+		case X509_V_ERR_CRL_NOT_YET_VALID:
+		case X509_V_ERR_CRL_HAS_EXPIRED:
+		case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+		case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+		case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+		case X509_V_ERR_APPLICATION_VERIFICATION:
+		default:
+			return CertificateVerificationError::UnknownError;
+	}
+}
+
 }
diff --git a/Swiften/TLS/OpenSSL/OpenSSLContext.h b/Swiften/TLS/OpenSSL/OpenSSLContext.h
index a0e73c4..31141a5 100644
--- a/Swiften/TLS/OpenSSL/OpenSSLContext.h
+++ b/Swiften/TLS/OpenSSL/OpenSSLContext.h
@@ -33,6 +33,8 @@ namespace Swift {
 		private:
 			static void ensureLibraryInitialized();	
 
+			static CertificateVerificationError::Type getVerificationErrorTypeForResult(int);
+
 			void doConnect();
 			void sendPendingDataToNetwork();
 			void sendPendingDataToApplication();
diff --git a/Swiften/TLS/SConscript b/Swiften/TLS/SConscript
index 6a67545..b84dbc0 100644
--- a/Swiften/TLS/SConscript
+++ b/Swiften/TLS/SConscript
@@ -3,6 +3,7 @@ Import("swiften_env")
 objects = swiften_env.StaticObject([
 			"TLSContext.cpp",
 			"TLSContextFactory.cpp",
+			"SecurityError.cpp",
 		])
 		
 if swiften_env.get("HAVE_OPENSSL", 0) :
-- 
cgit v0.10.2-6-g49f6