From 91b828a6e94f15c675e03baff4d45a7feb939eb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Wed, 10 Nov 2010 22:02:12 +0100
Subject: Added server identity check.


diff --git a/.project b/.project
index 8ad2b97..17363c7 100644
--- a/.project
+++ b/.project
@@ -18,7 +18,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
-					<value>dist=1 Swift</value>
+					<value>check=1 QA</value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.buildArguments</key>
@@ -54,7 +54,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
-					<value>dist=1 Swift</value>
+					<value>check=1 QA</value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.stopOnError</key>
diff --git a/Swiften/Client/ClientSession.cpp b/Swiften/Client/ClientSession.cpp
index a199a84..9e6db5d 100644
--- a/Swiften/Client/ClientSession.cpp
+++ b/Swiften/Client/ClientSession.cpp
@@ -37,6 +37,7 @@
 #include "Swiften/SASL/DIGESTMD5ClientAuthenticator.h"
 #include "Swiften/Session/SessionStream.h"
 #include "Swiften/TLS/CertificateTrustChecker.h"
+#include "Swiften/TLS/ServerIdentityVerifier.h"
 
 namespace Swift {
 
@@ -330,16 +331,27 @@ void ClientSession::handleTLSEncrypted() {
 	Certificate::ref certificate = stream->getPeerCertificate();
 	boost::shared_ptr<CertificateVerificationError> verificationError = stream->getPeerCertificateVerificationError();
 	if (verificationError) {
-		if (certificateTrustChecker && certificateTrustChecker->isCertificateTrusted(certificate, localJID.getDomain())) {
+		checkTrustOrFinish(certificate, verificationError);
+	}
+	else {
+		ServerIdentityVerifier identityVerifier(localJID);
+		if (identityVerifier.certificateVerifies(certificate)) {
 			continueAfterTLSEncrypted();
 		}
 		else {
-			finishSession(verificationError);
+			boost::shared_ptr<CertificateVerificationError> identityError(new CertificateVerificationError(CertificateVerificationError::InvalidServerIdentity));
+			checkTrustOrFinish(certificate, identityError);
 		}
 	}
-	else {
+}
+
+void ClientSession::checkTrustOrFinish(Certificate::ref certificate, boost::shared_ptr<CertificateVerificationError> error) {
+	if (certificateTrustChecker && certificateTrustChecker->isCertificateTrusted(certificate, localJID.getDomain())) {
 		continueAfterTLSEncrypted();
 	}
+	else {
+		finishSession(error);
+	}
 }
 
 void ClientSession::continueAfterTLSEncrypted() {
diff --git a/Swiften/Client/ClientSession.h b/Swiften/Client/ClientSession.h
index 6acd9a3..20573a0 100644
--- a/Swiften/Client/ClientSession.h
+++ b/Swiften/Client/ClientSession.h
@@ -121,6 +121,7 @@ namespace Swift {
 			void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
 			void ack(unsigned int handledStanzasCount);
 			void continueAfterTLSEncrypted();
+			void checkTrustOrFinish(Certificate::ref certificate, boost::shared_ptr<CertificateVerificationError> error);
 
 		private:
 			JID localJID;
diff --git a/Swiften/Client/UnitTest/ClientSessionTest.cpp b/Swiften/Client/UnitTest/ClientSessionTest.cpp
index 11e4992..74f3376 100644
--- a/Swiften/Client/UnitTest/ClientSessionTest.cpp
+++ b/Swiften/Client/UnitTest/ClientSessionTest.cpp
@@ -24,6 +24,8 @@
 #include "Swiften/Elements/EnableStreamManagement.h"
 #include "Swiften/Elements/IQ.h"
 #include "Swiften/Elements/ResourceBind.h"
+#include "Swiften/TLS/SimpleCertificate.h"
+#include "Swiften/TLS/BlindCertificateTrustChecker.h"
 
 using namespace Swift;
 
@@ -33,6 +35,7 @@ class ClientSessionTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testStartTLS);
 		CPPUNIT_TEST(testStartTLS_ServerError);
 		CPPUNIT_TEST(testStartTLS_ConnectError);
+		CPPUNIT_TEST(testStartTLS_InvalidIdentity);
 		CPPUNIT_TEST(testAuthenticate);
 		CPPUNIT_TEST(testAuthenticate_Unauthorized);
 		CPPUNIT_TEST(testAuthenticate_NoValidAuthMechanisms);
@@ -57,6 +60,11 @@ class ClientSessionTest : public CppUnit::TestFixture {
 			server = boost::shared_ptr<MockSessionStream>(new MockSessionStream());
 			sessionFinishedReceived = false;
 			needCredentials = false;
+			blindCertificateTrustChecker = new BlindCertificateTrustChecker();
+		}
+
+		void tearDown() {
+			delete blindCertificateTrustChecker;
 		}
 
 		void testStart_Error() {
@@ -71,6 +79,7 @@ class ClientSessionTest : public CppUnit::TestFixture {
 
 		void testStartTLS() {
 			boost::shared_ptr<ClientSession> session(createSession());
+			session->setCertificateTrustChecker(blindCertificateTrustChecker);
 			session->start();
 			server->receiveStreamStart();
 			server->sendStreamStart();
@@ -116,6 +125,24 @@ class ClientSessionTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT(sessionFinishedError);
 		}
 
+		void testStartTLS_InvalidIdentity() {
+			boost::shared_ptr<ClientSession> session(createSession());
+			session->start();
+			server->receiveStreamStart();
+			server->sendStreamStart();
+			server->sendStreamFeaturesWithStartTLS();
+			server->receiveStartTLS();
+			CPPUNIT_ASSERT(!server->tlsEncrypted);
+			server->sendTLSProceed();
+			CPPUNIT_ASSERT(server->tlsEncrypted);
+			server->onTLSEncrypted();
+
+			CPPUNIT_ASSERT_EQUAL(ClientSession::Finished, session->getState());
+			CPPUNIT_ASSERT(sessionFinishedReceived);
+			CPPUNIT_ASSERT(sessionFinishedError);
+			CPPUNIT_ASSERT_EQUAL(CertificateVerificationError::InvalidServerIdentity, boost::dynamic_pointer_cast<CertificateVerificationError>(sessionFinishedError)->getType());
+		}
+
 		void testAuthenticate() {
 			boost::shared_ptr<ClientSession> session(createSession());
 			session->start();
@@ -284,7 +311,7 @@ class ClientSessionTest : public CppUnit::TestFixture {
 				}
 
 				virtual Certificate::ref getPeerCertificate() const {
-					return Certificate::ref();
+					return Certificate::ref(new SimpleCertificate());
 				}
 
 				virtual boost::shared_ptr<CertificateVerificationError> getPeerCertificateVerificationError() const {
@@ -429,6 +456,7 @@ class ClientSessionTest : public CppUnit::TestFixture {
 		bool sessionFinishedReceived;
 		bool needCredentials;
 		boost::shared_ptr<Error> sessionFinishedError;
+		BlindCertificateTrustChecker* blindCertificateTrustChecker;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(ClientSessionTest);
diff --git a/Swiften/IDN/IDNA.cpp b/Swiften/IDN/IDNA.cpp
new file mode 100644
index 0000000..a21ca63
--- /dev/null
+++ b/Swiften/IDN/IDNA.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/IDN/IDNA.h"
+
+#include <stringprep.h>
+#include <vector>
+#include <idna.h>
+
+namespace Swift {
+
+String IDNA::getEncoded(const String& domain) {
+	char* output;
+	if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) {
+		String result(output);
+		free(output);
+		return result;
+	}
+	else {
+		return domain;
+	}
+}
+
+}
diff --git a/Swiften/IDN/IDNA.h b/Swiften/IDN/IDNA.h
new file mode 100644
index 0000000..cc4144b
--- /dev/null
+++ b/Swiften/IDN/IDNA.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Base/String.h"
+
+namespace Swift {
+	class IDNA {
+		public:
+			static String getEncoded(const String& s);
+	};
+}
diff --git a/Swiften/IDN/SConscript b/Swiften/IDN/SConscript
new file mode 100644
index 0000000..bfdb42c
--- /dev/null
+++ b/Swiften/IDN/SConscript
@@ -0,0 +1,10 @@
+Import("swiften_env")
+
+myenv = swiften_env.Clone()
+myenv.MergeFlags(swiften_env["LIBIDN_FLAGS"])
+
+objects = myenv.StaticObject([
+			"StringPrep.cpp",
+			"IDNA.cpp",
+		])
+swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/IDN/StringPrep.cpp b/Swiften/IDN/StringPrep.cpp
new file mode 100644
index 0000000..d9e061e
--- /dev/null
+++ b/Swiften/IDN/StringPrep.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/IDN/StringPrep.h"
+
+#include <stringprep.h>
+#include <vector>
+
+namespace Swift {
+
+static const int MAX_STRINGPREP_SIZE = 1024;
+
+const Stringprep_profile* getLibIDNProfile(StringPrep::Profile profile) {
+	switch(profile) {
+		case StringPrep::NamePrep: return stringprep_nameprep; break;
+		case StringPrep::XMPPNodePrep: return stringprep_xmpp_nodeprep; break;
+		case StringPrep::XMPPResourcePrep: return stringprep_xmpp_resourceprep; break;
+		case StringPrep::SASLPrep: return stringprep_saslprep; break;
+	}
+	assert(false);
+	return 0;
+}
+
+String StringPrep::getPrepared(const String& s, Profile profile) {
+	
+	std::vector<char> input(s.getUTF8String().begin(), s.getUTF8String().end());
+	input.resize(MAX_STRINGPREP_SIZE);
+	if (stringprep(&input[0], MAX_STRINGPREP_SIZE, static_cast<Stringprep_profile_flags>(0), getLibIDNProfile(profile)) == 0) {
+		return String(&input[0]);
+	}
+	else {
+		return "";
+	}
+}
+
+}
diff --git a/Swiften/IDN/StringPrep.h b/Swiften/IDN/StringPrep.h
new file mode 100644
index 0000000..3b27efa
--- /dev/null
+++ b/Swiften/IDN/StringPrep.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Base/String.h"
+
+namespace Swift {
+	class StringPrep {
+		public:
+			enum Profile {
+				NamePrep,
+				XMPPNodePrep,
+				XMPPResourcePrep,
+				SASLPrep,
+			};
+
+			static String getPrepared(const String& s, Profile profile);
+	};
+}
diff --git a/Swiften/JID/JID.cpp b/Swiften/JID/JID.cpp
index f89ba29..cf5317e 100644
--- a/Swiften/JID/JID.cpp
+++ b/Swiften/JID/JID.cpp
@@ -9,7 +9,7 @@
 #include <iostream>
 
 #include "Swiften/JID/JID.h"
-#include "Swiften/StringPrep/StringPrep.h"
+#include "Swiften/IDN/StringPrep.h"
 
 namespace Swift {
 
diff --git a/Swiften/Network/DomainNameResolver.cpp b/Swiften/Network/DomainNameResolver.cpp
index 854b97f..96c5165 100644
--- a/Swiften/Network/DomainNameResolver.cpp
+++ b/Swiften/Network/DomainNameResolver.cpp
@@ -6,23 +6,9 @@
 
 #include "Swiften/Network/DomainNameResolver.h"
 
-#include <idna.h>
-
 namespace Swift {
 
 DomainNameResolver::~DomainNameResolver() {
 }
 
-String DomainNameResolver::getNormalized(const String& domain) {
-	char* output;
-	if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) {
-		String result(output);
-		free(output);
-		return result;
-	}
-	else {
-		return domain;
-	}
-}
-
 }
diff --git a/Swiften/Network/DomainNameResolver.h b/Swiften/Network/DomainNameResolver.h
index 4f8829c..cf9d521 100644
--- a/Swiften/Network/DomainNameResolver.h
+++ b/Swiften/Network/DomainNameResolver.h
@@ -21,8 +21,5 @@ namespace Swift {
 
 			virtual boost::shared_ptr<DomainNameServiceQuery> createServiceQuery(const String& name) = 0;
 			virtual boost::shared_ptr<DomainNameAddressQuery> createAddressQuery(const String& name) = 0;
-
-		protected:
-			static String getNormalized(const String& domain);
 	};
 }
diff --git a/Swiften/Network/PlatformDomainNameResolver.cpp b/Swiften/Network/PlatformDomainNameResolver.cpp
index 44c87e0..3f72466 100644
--- a/Swiften/Network/PlatformDomainNameResolver.cpp
+++ b/Swiften/Network/PlatformDomainNameResolver.cpp
@@ -18,6 +18,7 @@
 #include <algorithm>
 
 #include "Swiften/Base/String.h"
+#include "Swiften/IDN/IDNA.h"
 #include "Swiften/Network/HostAddress.h"
 #include "Swiften/EventLoop/EventLoop.h"
 #include "Swiften/Network/HostAddressPort.h"
@@ -95,11 +96,11 @@ PlatformDomainNameResolver::PlatformDomainNameResolver(EventLoop* eventLoop) : e
 }
 
 boost::shared_ptr<DomainNameServiceQuery> PlatformDomainNameResolver::createServiceQuery(const String& name) {
-	return boost::shared_ptr<DomainNameServiceQuery>(new PlatformDomainNameServiceQuery(getNormalized(name), eventLoop));
+	return boost::shared_ptr<DomainNameServiceQuery>(new PlatformDomainNameServiceQuery(IDNA::getEncoded(name), eventLoop));
 }
 
 boost::shared_ptr<DomainNameAddressQuery> PlatformDomainNameResolver::createAddressQuery(const String& name) {
-	return boost::shared_ptr<DomainNameAddressQuery>(new AddressQuery(getNormalized(name), eventLoop));
+	return boost::shared_ptr<DomainNameAddressQuery>(new AddressQuery(IDNA::getEncoded(name), eventLoop));
 }
 
 }
diff --git a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
index 5d0ee9a..551afd5 100644
--- a/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
+++ b/Swiften/SASL/SCRAMSHA1ClientAuthenticator.cpp
@@ -14,7 +14,7 @@
 #include "Swiften/StringCodecs/Base64.h"
 #include "Swiften/StringCodecs/HMACSHA1.h"
 #include "Swiften/StringCodecs/PBKDF2.h"
-#include "Swiften/StringPrep/StringPrep.h"
+#include "Swiften/IDN/StringPrep.h"
 
 namespace Swift {
 
diff --git a/Swiften/SConscript b/Swiften/SConscript
index df34e8b..1f762cb 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -125,7 +125,7 @@ if env["SCONS_STAGE"] == "build" :
 	SConscript(dirs = [
 			"Avatars",
 			"Base",
-			"StringPrep",
+			"IDN",
 			"SASL",
 			"TLS",
 			"EventLoop",
@@ -266,6 +266,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("StringCodecs/UnitTest/HexifyTest.cpp"),
 			File("StringCodecs/UnitTest/HMACSHA1Test.cpp"),
 			File("StringCodecs/UnitTest/PBKDF2Test.cpp"),
+			File("TLS/UnitTest/ServerIdentityVerifierTest.cpp"),
 			File("VCards/UnitTest/VCardManagerTest.cpp"),
 		])
 	
diff --git a/Swiften/StringPrep/SConscript b/Swiften/StringPrep/SConscript
deleted file mode 100644
index 480d81a..0000000
--- a/Swiften/StringPrep/SConscript
+++ /dev/null
@@ -1,9 +0,0 @@
-Import("swiften_env")
-
-myenv = swiften_env.Clone()
-myenv.MergeFlags(swiften_env["LIBIDN_FLAGS"])
-
-objects = myenv.StaticObject([
-			"StringPrep.cpp"
-		])
-swiften_env.Append(SWIFTEN_OBJECTS = [objects])
diff --git a/Swiften/StringPrep/StringPrep.cpp b/Swiften/StringPrep/StringPrep.cpp
deleted file mode 100644
index ea084a5..0000000
--- a/Swiften/StringPrep/StringPrep.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include "Swiften/StringPrep/StringPrep.h"
-
-#include <stringprep.h>
-#include <vector>
-
-namespace Swift {
-
-static const int MAX_STRINGPREP_SIZE = 1024;
-
-const Stringprep_profile* getLibIDNProfile(StringPrep::Profile profile) {
-	switch(profile) {
-		case StringPrep::NamePrep: return stringprep_nameprep; break;
-		case StringPrep::XMPPNodePrep: return stringprep_xmpp_nodeprep; break;
-		case StringPrep::XMPPResourcePrep: return stringprep_xmpp_resourceprep; break;
-		case StringPrep::SASLPrep: return stringprep_saslprep; break;
-	}
-	assert(false);
-	return 0;
-}
-
-String StringPrep::getPrepared(const String& s, Profile profile) {
-	
-	std::vector<char> input(s.getUTF8String().begin(), s.getUTF8String().end());
-	input.resize(MAX_STRINGPREP_SIZE);
-	if (stringprep(&input[0], MAX_STRINGPREP_SIZE, static_cast<Stringprep_profile_flags>(0), getLibIDNProfile(profile)) == 0) {
-		return String(&input[0]);
-	}
-	else {
-		return "";
-	}
-}
-
-}
diff --git a/Swiften/StringPrep/StringPrep.h b/Swiften/StringPrep/StringPrep.h
deleted file mode 100644
index 3b27efa..0000000
--- a/Swiften/StringPrep/StringPrep.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include "Swiften/Base/String.h"
-
-namespace Swift {
-	class StringPrep {
-		public:
-			enum Profile {
-				NamePrep,
-				XMPPNodePrep,
-				XMPPResourcePrep,
-				SASLPrep,
-			};
-
-			static String getPrepared(const String& s, Profile profile);
-	};
-}
diff --git a/Swiften/TLS/CertificateVerificationError.h b/Swiften/TLS/CertificateVerificationError.h
index f1bd091..807df03 100644
--- a/Swiften/TLS/CertificateVerificationError.h
+++ b/Swiften/TLS/CertificateVerificationError.h
@@ -22,6 +22,7 @@ namespace Swift {
 				PathLengthExceeded,
 				InvalidSignature,
 				InvalidCA,
+				InvalidServerIdentity,
 			};
 
 			CertificateVerificationError(Type type = UnknownError) : type(type) {}
diff --git a/Swiften/TLS/SConscript b/Swiften/TLS/SConscript
index f83e383..43f7db6 100644
--- a/Swiften/TLS/SConscript
+++ b/Swiften/TLS/SConscript
@@ -4,6 +4,7 @@ objects = swiften_env.StaticObject([
 			"Certificate.cpp",
 			"CertificateFactory.cpp",
 			"CertificateTrustChecker.cpp",
+			"ServerIdentityVerifier.cpp",
 			"TLSContext.cpp",
 			"TLSContextFactory.cpp",
 		])
diff --git a/Swiften/TLS/ServerIdentityVerifier.cpp b/Swiften/TLS/ServerIdentityVerifier.cpp
new file mode 100644
index 0000000..05efd31
--- /dev/null
+++ b/Swiften/TLS/ServerIdentityVerifier.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/TLS/ServerIdentityVerifier.h"
+
+#include "Swiften/Base/foreach.h"
+#include "Swiften/IDN/IDNA.h"
+
+namespace Swift {
+
+ServerIdentityVerifier::ServerIdentityVerifier(const JID& jid) {
+	domain = jid.getDomain();
+	encodedDomain = IDNA::getEncoded(domain);
+}
+
+bool ServerIdentityVerifier::certificateVerifies(Certificate::ref certificate) {
+	bool hasSAN = false;
+
+	// DNS names
+	std::vector<String> dnsNames = certificate->getDNSNames();
+	foreach (const String& dnsName, dnsNames) {
+		if (matchesDomain(dnsName)) {
+			return true;
+		}
+	}
+	hasSAN |= !dnsNames.empty();
+
+	// SRV names
+	std::vector<String> srvNames = certificate->getSRVNames();
+	foreach (const String& srvName, srvNames) {
+		// Only match SRV names that begin with the service; this isn't required per
+		// spec, but we're being purist about this.
+		if (srvName.beginsWith("_xmpp-client.") && matchesDomain(srvName.getSubstring(String("_xmpp-client.").getUTF8Size(), srvName.npos()))) {
+			return true;
+		}
+	}
+	hasSAN |= !srvNames.empty();
+
+	// XmppAddr
+	std::vector<String> xmppAddresses = certificate->getXMPPAddresses();
+	foreach (const String& xmppAddress, xmppAddresses) {
+		if (matchesAddress(xmppAddress)) {
+			return true;
+		}
+	}
+	hasSAN |= !xmppAddresses.empty();
+
+	// CommonNames. Only check this if there was no SAN (according to spec).
+	if (!hasSAN) {
+		std::vector<String> commonNames = certificate->getCommonNames();
+		foreach (const String& commonName, commonNames) {
+			if (matchesDomain(commonName)) {
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+bool ServerIdentityVerifier::matchesDomain(const String& s) {
+	if (s.beginsWith("*.")) {
+		String matchString(s.getSubstring(2, s.npos()));
+		String matchDomain = encodedDomain;
+		int dotIndex = matchDomain.find('.');
+		if (dotIndex >= 0) {
+			matchDomain = matchDomain.getSubstring(dotIndex + 1, matchDomain.npos());
+		}
+		return matchString == matchDomain;
+	}
+	else {
+		return s == encodedDomain;
+	}
+}
+
+bool ServerIdentityVerifier::matchesAddress(const String& s) {
+	return s == domain;
+}
+
+}
diff --git a/Swiften/TLS/ServerIdentityVerifier.h b/Swiften/TLS/ServerIdentityVerifier.h
new file mode 100644
index 0000000..a001a5e
--- /dev/null
+++ b/Swiften/TLS/ServerIdentityVerifier.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/JID/JID.h"
+#include "Swiften/TLS/Certificate.h"
+
+namespace Swift {
+	class ServerIdentityVerifier {
+		public:
+			ServerIdentityVerifier(const JID& jid);
+
+			bool certificateVerifies(Certificate::ref);
+
+		private:
+			bool matchesDomain(const String&);
+			bool matchesAddress(const String&);
+
+		private:
+			String domain;
+			String encodedDomain;
+	};
+}
diff --git a/Swiften/TLS/SimpleCertificate.h b/Swiften/TLS/SimpleCertificate.h
new file mode 100644
index 0000000..2db8291
--- /dev/null
+++ b/Swiften/TLS/SimpleCertificate.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Base/String.h"
+#include "Swiften/TLS/Certificate.h"
+
+namespace Swift {
+	class SimpleCertificate : public Certificate {
+		public:
+			typedef boost::shared_ptr<SimpleCertificate> ref;
+
+			void setSubjectName(const String& name) {
+				subjectName = name;
+			}
+
+			String getSubjectName() const {
+				return subjectName;
+			}
+
+			std::vector<String> getCommonNames() const {
+				return commonNames;
+			}
+
+			void addCommonName(const String& name) {
+				commonNames.push_back(name);
+			}
+
+			void addSRVName(const String& name) {
+				srvNames.push_back(name);
+			}
+
+			void addDNSName(const String& name) {
+				dnsNames.push_back(name);
+			}
+
+			void addXMPPAddress(const String& addr) {
+				xmppAddresses.push_back(addr);
+			}
+
+			std::vector<String> getSRVNames() const {
+				return srvNames;
+			}
+
+			std::vector<String> getDNSNames() const {
+				return dnsNames;
+			}
+
+			std::vector<String> getXMPPAddresses() const {
+				return xmppAddresses;
+			}
+
+			ByteArray toDER() const {
+				return ByteArray();
+			}
+
+		private:
+			void parse();
+
+		private:
+			String subjectName;
+			std::vector<String> commonNames;
+			std::vector<String> dnsNames;
+			std::vector<String> xmppAddresses;
+			std::vector<String> srvNames;
+	};
+}
diff --git a/Swiften/TLS/UnitTest/ServerIdentityVerifierTest.cpp b/Swiften/TLS/UnitTest/ServerIdentityVerifierTest.cpp
new file mode 100644
index 0000000..a7fdbad
--- /dev/null
+++ b/Swiften/TLS/UnitTest/ServerIdentityVerifierTest.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Base/ByteArray.h"
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <vector>
+
+#include "Swiften/TLS/ServerIdentityVerifier.h"
+#include "Swiften/TLS/SimpleCertificate.h"
+
+using namespace Swift;
+
+class ServerIdentityVerifierTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(ServerIdentityVerifierTest);
+		CPPUNIT_TEST(testCertificateVerifies_WithoutMatchingDNSName);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingDNSName);
+		CPPUNIT_TEST(testCertificateVerifies_WithSecondMatchingDNSName);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingInternationalDNSName);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingDNSNameWithWildcard);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingDNSNameWithWildcardMatchingNoComponents);
+		CPPUNIT_TEST(testCertificateVerifies_WithDNSNameWithWildcardMatchingTwoComponents);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingSRVNameWithoutService);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingSRVNameWithService);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingSRVNameWithServiceAndWildcard);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingSRVNameWithDifferentService);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingXmppAddr);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingXmppAddrWithWildcard);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingInternationalXmppAddr);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingCNWithoutSAN);
+		CPPUNIT_TEST(testCertificateVerifies_WithMatchingCNWithMatchingSAN);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testCertificateVerifies_WithoutMatchingDNSName() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("foo.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingDNSName() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithSecondMatchingDNSName() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("foo.com");
+			certificate->addDNSName("bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingInternationalDNSName() {
+			ServerIdentityVerifier testling(JID("foo@tron\xc3\xa7on.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("xn--tronon-zua.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingDNSNameWithWildcard() {
+			ServerIdentityVerifier testling(JID("foo@im.bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("*.bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingDNSNameWithWildcardMatchingNoComponents() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("*.bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithDNSNameWithWildcardMatchingTwoComponents() {
+			ServerIdentityVerifier testling(JID("foo@xmpp.im.bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addDNSName("*.bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingSRVNameWithoutService() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addSRVName("bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingSRVNameWithService() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addSRVName("_xmpp-client.bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingSRVNameWithServiceAndWildcard() {
+			ServerIdentityVerifier testling(JID("foo@im.bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addSRVName("_xmpp-client.*.bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingSRVNameWithDifferentService() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addSRVName("_xmpp-server.bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingXmppAddr() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addXMPPAddress("bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingXmppAddrWithWildcard() {
+			ServerIdentityVerifier testling(JID("foo@im.bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addXMPPAddress("*.bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingInternationalXmppAddr() {
+			ServerIdentityVerifier testling(JID("foo@tron\xc3\xa7.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addXMPPAddress("tron\xc3\xa7.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingCNWithoutSAN() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addCommonName("bar.com");
+
+			CPPUNIT_ASSERT(testling.certificateVerifies(certificate));
+		}
+
+		void testCertificateVerifies_WithMatchingCNWithMatchingSAN() {
+			ServerIdentityVerifier testling(JID("foo@bar.com/baz"));
+			SimpleCertificate::ref certificate(new SimpleCertificate());
+			certificate->addSRVName("foo.com");
+			certificate->addCommonName("bar.com");
+
+			CPPUNIT_ASSERT(!testling.certificateVerifies(certificate));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ServerIdentityVerifierTest);
-- 
cgit v0.10.2-6-g49f6