Browse Source

implement very basic ssl, todo allow server to run without ssl and get ssl fully running (broken right now, complains about no shared cipher)

Sander, Paul 4 years ago
parent
commit
e2ab8c089f

+ 3 - 2
cli/CMakeLists.txt

@@ -13,9 +13,10 @@ pkg_check_modules(READLINE REQUIRED readline)
 pkg_check_modules(JSONCPP REQUIRED jsoncpp)
 
 find_package(Threads)
-find_package(Boost 1.67 REQUIRED COMPONENTS system program_options)
+find_package(OpenSSL REQUIRED)
+find_package(Boost 1.70 REQUIRED COMPONENTS system program_options)
 
 
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} include)
 
-target_link_libraries(ccats-cli PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${READLINE_LIBRARIES} ${JSONCPP_LIBRARIES})
+target_link_libraries(ccats-cli PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${READLINE_LIBRARIES} ${JSONCPP_LIBRARIES})

+ 5 - 0
cli/include/ioman.h

@@ -5,6 +5,7 @@
 #include "fileman.h"
 
 #include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
 #include <condition_variable>
 #include <json/json.h>
 #include <mutex>
@@ -57,6 +58,8 @@ private:
 	boost::asio::streambuf recvbuf;
 	boost::system::error_code errcode;
 	tcp::socket *tcpsock;
+	boost::asio::ssl::stream<tcp::socket&> *sslsock;
+	boost::asio::ssl::context *sslctx;
 	/**
 	 * The IP and port to connect to
 	 * Flag telling wether one is connected
@@ -64,6 +67,7 @@ private:
 	std::string ipstring;
 	unsigned short port;
 	bool connected;
+	bool usessl;
 	/**
 	 * Instances of CmdMan and FileMan to process user input and handle File I/O
 	 */
@@ -132,6 +136,7 @@ public:
 	 * Constructor and destructor
 	 */
 	IoMan(char *ipcstring);
+	IoMan(char *ipcstring, bool enablessl);
 	virtual ~IoMan();
 
 	/**

+ 1 - 0
cli/include/userioman.h

@@ -60,6 +60,7 @@ public:
 	 * Constructor and destructor
 	 */
 	UserIoMan(char *ipcstring);
+	UserIoMan(char *ipcstring, bool usessl);
 	~UserIoMan();
 
 protected:

+ 40 - 10
cli/src/ioman.cpp

@@ -25,11 +25,23 @@ extern IoMan *gIOMAN;
 
 void ioman_externalDebugPrint(string msg) { gIOMAN->printMessage(msg, gIOMAN->OutMsgType::debug); }
 
+IoMan::IoMan(char *ipcstring, bool enablessl) : IoMan(ipcstring) {
+	usessl = enablessl;
+	if(usessl) {
+		sslctx = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
+		sslctx->set_verify_mode(boost::asio::ssl::verify_peer);
+		sslctx->set_options(boost::asio::ssl::context::no_sslv2);
+		sslctx->load_verify_file("rootca.crt");
+		sslsock = new boost::asio::ssl::stream<tcp::socket&>(*tcpsock, *sslctx);
+	}
+}
+
 IoMan::IoMan(char *ipcstring) : cmdman(fileman, &ioman_externalDebugPrint) {
 	ipstring = std::string(ipcstring);
 	port = 1234;
 	tcpsock = new tcp::socket(ios);
 	connected = false;
+	usessl = false;
 
 	/* to be put elsewhere */
 
@@ -46,6 +58,11 @@ IoMan::IoMan(char *ipcstring) : cmdman(fileman, &ioman_externalDebugPrint) {
 
 IoMan::~IoMan() {
 	initcv.notify_all();
+
+	if (connected) {
+		disconnect();
+	}
+	
 	if (runnetwork) {
 		networkmutex.lock();
 		runnetwork = false;
@@ -67,11 +84,9 @@ IoMan::~IoMan() {
 		tresponse.join();
 	}
 
-	if (connected) {
-		disconnect();
-	}
-
+	if(usessl) delete sslsock;
 	delete tcpsock;
+	delete sslctx;
 	delete reader;
 }
 
@@ -145,7 +160,7 @@ bool IoMan::connect() {
 		connected = false;
 	} else {
 		// establish connection
-		printMessage("IoMan::connect() connecting to " + ipstring, debug);
+		printMessage(string(__PRETTY_FUNCTION__) + string(" connecting to ") + ipstring, debug);
 		ep = new tcp::endpoint(addr, port);
 		tcpsock->connect(*ep, errcode);
 		if (errcode) {
@@ -157,6 +172,18 @@ bool IoMan::connect() {
 		}
 		delete ep;
 	}
+	if(usessl) {
+		// try to do ssl handshake
+		printMessage(string(__PRETTY_FUNCTION__) + string(" doing ssl handshake with ") + ipstring, debug);
+		sslsock->handshake(boost::asio::ssl::stream_base::client, errcode);
+		if (errcode) {
+			root["error"] = errcode.message();
+			connected = false;
+		} else {
+			connected = true;
+			root["error"] = "";
+		}
+	}
 	root["accept"] = connected;
 	printMessage(Json::writeString(wbuilder, root), normal);
 	return connected;
@@ -239,7 +266,8 @@ void IoMan::networkMain() {
 		*/
 
 		// read from network
-		readsize = boost::asio::read_until(*tcpsock, recvbuf, '\n', errcode);
+		if(usessl) readsize = boost::asio::read_until(*sslsock, recvbuf, '\n', errcode);
+		else readsize = boost::asio::read_until(*tcpsock, recvbuf, '\n', errcode);
 		printMessage(string(__PRETTY_FUNCTION__) + string(" asio::read() ok ") + std::to_string(readsize), debug);
 		// printMessage(string("have ") + std::to_string(toprocess.size()) +
 		// string(" commands"), debug);
@@ -247,8 +275,9 @@ void IoMan::networkMain() {
 			printMessage("IoMan::networkMain() couldnt read json data\n" + errcode.message(), error);
 			continue;
 		}
-		if (!readsize)
+		if (readsize < 1) {
 			break;
+		}
 		recvjson = (char *)(boost::asio::buffer_cast<const char *>(recvbuf.data()));
 		recvjson[readsize] = 0;
 		while (strchr(recvjson, '\n')) {
@@ -260,6 +289,7 @@ void IoMan::networkMain() {
 			if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
 				printMessage("IoMan::networkMain() couldnt parse json data: " + jsonerror, error);
 				recvbuf.consume(jsonsize);
+				recvjson += jsonsize;
 				continue;
 			}
 			recvbuf.consume(jsonsize);
@@ -267,8 +297,7 @@ void IoMan::networkMain() {
 
 			printMessage(string(__PRETTY_FUNCTION__) + string(" remaining recvbuf ") + string(boost::asio::buffer_cast<const char *>(recvbuf.data())), debug);
 
-			for (int i = 0; i < jsonsize; i++)
-				recvjson++;
+			recvjson += jsonsize;
 			// store locally
 			toput.push_back(root);
 		}
@@ -343,7 +372,8 @@ void IoMan::inputMain() {
 			}
 			if (cmdret.type & CmdMan::rettype::send) {
 				printMessage("IoMan::inputMain() sending json \"" + Json::writeString(wbuilder, cmdret.msg) + "\"", debug);
-				boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
+				if(usessl) boost::asio::write(*sslsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
+				else boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, cmdret.msg) + "\n"), errcode);
 				if (errcode) {
 					printMessage("IoMan::inputMain() couldnt send json data\n" + errcode.message() + "\n", error);
 					continue;

+ 9 - 6
cli/src/main.cpp

@@ -19,10 +19,10 @@ void show_help(char *exec) {
 int main(int argc, char **argv) {
 	bpo::options_description desc{"Options"};
 	desc.add_options()("help", "show this help")("machine", "switch to machine mode for I/O (not designed for user interaction)")(
-	    "batch", bpo::value<std::string>(), "run operations from arg as batch file");
+	    "batch", bpo::value<std::string>(), "run operations from arg as batch file")("usessl", "enable ssl for connection to server");
 
 	bpo::variables_map vm;
-	unsigned int machine = 0;
+	bool machine = false, usessl = false;
 	const char *file = NULL;
 	IoMan *ioman;
 
@@ -43,21 +43,24 @@ int main(int argc, char **argv) {
 		}
 		if (vm.count("machine")) {
 			// enable machine/gui mode
-			machine = 1;
+			machine = true;
 		}
 		if (vm.count("batch")) {
 			// handle batch file mode
 			file = vm["batch"].as<std::string>().c_str();
 		}
+		if (vm.count("usessl")) {
+			usessl = true;
+		}
 	} catch (const bpo::error &ex) {
 		std::fprintf(stderr, "%s\n", ex.what());
 	}
-	std::printf("ip %s machine mode is %d file is %s\n", argv[1], machine, file ? file : "");
+	std::printf("ip %s machine mode is %d file is %s enablessl is %d\n", argv[1], machine, file ? file : "", usessl);
 
 	if (machine) {
-		ioman = new MachineIoMan(argv[1]);
+		ioman = new MachineIoMan(argv[1], usessl);
 	} else {
-		ioman = new UserIoMan(argv[1]);
+		ioman = new UserIoMan(argv[1], usessl);
 	}
 	gIOMAN = ioman;
 	if (ioman->init()) {

+ 3 - 1
cli/src/userioman.cpp

@@ -10,7 +10,9 @@
 
 using boost::asio::buffer;
 
-UserIoMan::UserIoMan(char *ipcstring) : IoMan(ipcstring) {
+UserIoMan::UserIoMan(char *ipcstring) : IoMan(ipcstring) {};
+
+UserIoMan::UserIoMan(char *ipcstring, bool usessl) : IoMan(ipcstring, usessl) {
 
 	/* setup json stuff */
 	Json::CharReaderBuilder rbuilder;

+ 18 - 0
daemon/Daemon-Config-Reference.md

@@ -7,6 +7,20 @@ This file must be in the same directory as the binary.
 `port` : The port where the server listens. Must be a valid port.
 `interface` : The sniffer interface you want to use.
 `userdatabase` : The file where userdata is stored in format: user;password
+`filedirectory` : The directory where files from the clients will be stored and read from
+`SSLenabled` : When set to true, the server will only use and accept SSL connections from clients. Set to false to disable this
+`SSLcertificate` : The certificate file to use for SSL connections
+`SSLprivatekey` : The private key file to use for SSL connections
+`SSLdhparams` : The diffie-hellman file to use for SSL connections
+
+### Notes about SSL
+To use SSL, certificates, keys and diffie-hellman parameters are required. To generate these, a convenience script `createsslfiles.sh` is provided.
+The names of the output files are controlled with variables at the top of the script, modify these if desired.
+Assuming default names, place the `user.crt`, `user.key` and `dh2048.pem` files somewhere convenient and configure the server accordingly.
+Place the `rootca.crt` certificate in the directory you intend to run the client from.
+
+If you get an error about SSL related files not being found despite them existing, shorten the names of the files.
+If you cannot connect and the server prints a error related to TLSv1, ensure your version of boost and OpenSSL are up to date.
 
 ### Example for config.txt
 ```
@@ -14,4 +28,8 @@ port=1234
 interface=lo
 userdatabase=user Storage.txt
 filedirectory=./files/
+SSLenabled=true
+SSLcertificate=user.crt
+SSLprivatekey=user.key
+SSLdhparams=dh2048.pem
 ```

+ 26 - 0
daemon/createsslfiles.sh

@@ -0,0 +1,26 @@
+#!/bin/bash
+ROOTNAME=rootca
+USERNAME=user
+DIFFIENAME=dh2048
+echo "Creating ROOTCA key"
+openssl genrsa -out $ROOTNAME.key 2048
+echo "Creating ROOTCA cert"
+openssl req -x509 -new -nodes -key $ROOTNAME.key -days 20000 -out $ROOTNAME.crt
+echo "Creating USER key"
+openssl genrsa -out $USERNAME.key 2048
+echo "Creating USER base cert"
+openssl req -new -key $USERNAME.key -out $USERNAME.csr
+echo "Signing USER base cert using ROOT key and cert"
+openssl x509 -req -in $USERNAME.csr -CA $ROOTNAME.crt -CAkey $ROOTNAME.key -CAcreateserial -out $USERNAME.crt -days 20000
+echo "Creating DIFFIE params. This might take a while"
+openssl dhparam -out $DIFFIENAME.pem 2048
+
+# should OK
+echo "This should OK"
+openssl verify -CAfile $ROOTNAME.crt $ROOTNAME.crt
+# should OK
+echo "This should OK"
+openssl verify -CAfile $ROOTNAME.crt $USERNAME.crt
+# should FAIL
+echo "This should FAIL"
+openssl verify -CAfile $USERNAME.crt $USERNAME.crt

+ 31 - 2
daemon/include/Server.h

@@ -2,6 +2,7 @@
 #define SERVER_H
 
 #include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
 #include <boost/bind.hpp>
 #include <boost/enable_shared_from_this.hpp>
 
@@ -21,8 +22,35 @@ using ip::tcp;
  */
 class con_handler : public boost::enable_shared_from_this<con_handler> {
 private:
+	/**
+	 * boost tcp socket
+	 */
 	tcp::socket sock;
 
+	/**
+	 * ssl stream socket
+	 */
+	ssl::stream<tcp::socket &> sslsock;
+
+	/**
+	 * ssl enable state
+	 *
+	 * true - ssl is enabled | false ssl is disabled
+	 */
+	const bool usessl;
+
+	/**
+	 * Performs SSL handshake.
+	 *
+	 * @return true - success | false - failure
+	 */
+	bool handshake();
+
+	/**
+	 * Closes socket
+	 */
+	void close_sock();
+
 	/**
 	 * max buffer length
 	 */
@@ -87,7 +115,7 @@ public:
 	 *
 	 * @param io_service connection info
 	 */
-	con_handler(basic_socket_acceptor<ip::tcp>::executor_type &io_service);
+	con_handler(basic_socket_acceptor<ip::tcp>::executor_type &io_service, boost::asio::ssl::context &context);
 
 	/**
 	 * con_handler destructor.
@@ -103,7 +131,7 @@ public:
 	 *
 	 * @return con_handler pointer
 	 */
-	static pointer create(basic_socket_acceptor<ip::tcp>::executor_type &io_service);
+	static pointer create(basic_socket_acceptor<ip::tcp>::executor_type &io_service, boost::asio::ssl::context &context);
 
 	/**
 	 * socket getter
@@ -172,6 +200,7 @@ public:
 class Server {
 private:
 	tcp::acceptor acceptor_;
+	boost::asio::ssl::context context_;
 
 	/**
 	 * Accepts traffic

+ 2 - 1
daemon/src/CMakeLists.txt

@@ -6,6 +6,7 @@ add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp
 
 # dependencies used by server only
 find_package(libtins 4.2 REQUIRED)
+find_package(OpenSSL REQUIRED)
 
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDE_DIRS})
-target_link_libraries(ccats PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${LIBTINS_LIBRARIES} ${JSONCPP_LIBRARIES})
+target_link_libraries(ccats PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${LIBTINS_LIBRARIES} ${JSONCPP_LIBRARIES})

+ 58 - 25
daemon/src/Server.cpp

@@ -11,7 +11,8 @@ using ip::tcp;
  * con_handler *
  ***************/
 
-con_handler::con_handler(basic_socket_acceptor<ip::tcp>::executor_type &io_service) : sock(io_service), buf(max_length), jsonCommander(fileManager) {
+con_handler::con_handler(basic_socket_acceptor<ip::tcp>::executor_type &io_service, boost::asio::ssl::context &context)
+    : sock(io_service), sslsock(sock, context), usessl(Config::getValue("SSLenabled") == "true"), buf(max_length), jsonCommander(fileManager) {
 	// disable indentation for json
 	this->jsonStringBuilder.settings_["indentation"] = "";
 
@@ -22,11 +23,32 @@ con_handler::con_handler(basic_socket_acceptor<ip::tcp>::executor_type &io_servi
 
 con_handler::~con_handler() {}
 
-con_handler::pointer con_handler::create(basic_socket_acceptor<ip::tcp>::executor_type &io_service) { return pointer(new con_handler(io_service)); }
+con_handler::pointer con_handler::create(basic_socket_acceptor<ip::tcp>::executor_type &io_service, boost::asio::ssl::context &context) {
+	return pointer(new con_handler(io_service, context));
+}
 
 tcp::socket &con_handler::socket() { return sock; }
 
-void con_handler::start() { read(&con_handler::handle_read_version); }
+bool con_handler::handshake() {
+	boost::system::error_code err;
+	sslsock.handshake(boost::asio::ssl::stream_base::server, err);
+	if (err) {
+		std::cerr << "SSL handshake failed: " << err.message() << std::endl;
+		close_sock();
+		return false;
+	}
+	return true;
+}
+
+void con_handler::close_sock() { sock.close(); }
+
+void con_handler::start() {
+	if (usessl) {
+		if (handshake())
+			read(&con_handler::handle_read_version);
+	} else
+		read(&con_handler::handle_read_version);
+}
 
 void con_handler::handle_read_version(const boost::system::error_code &err, size_t bytes_transferred) {
 	if (!err) {
@@ -43,12 +65,12 @@ void con_handler::handle_read_version(const boost::system::error_code &err, size
 		case JsonCommander::Action::closeAndSend:
 			sendJson(response.json);
 		default:
-			sock.close();
+			close_sock();
 		}
 
 	} else {
-		std::cerr << "error: " << err.message() << std::endl;
-		sock.close();
+		std::cerr << __PRETTY_FUNCTION__ << " error: " << err.message() << std::endl;
+		close_sock();
 	}
 }
 
@@ -67,12 +89,12 @@ void con_handler::handle_read_login(const boost::system::error_code &err, size_t
 		case JsonCommander::Action::closeAndSend:
 			sendJson(response.json);
 		default:
-			sock.close();
+			close_sock();
 		}
 
 	} else {
-		std::cerr << "error: " << err.message() << std::endl;
-		sock.close();
+		std::cerr << __PRETTY_FUNCTION__ << " error: " << err.message() << std::endl;
+		close_sock();
 	}
 }
 
@@ -91,12 +113,12 @@ void con_handler::handle_read_command(const boost::system::error_code &err, size
 		case JsonCommander::Action::closeAndSend:
 			sendJson(response.json);
 		default:
-			sock.close();
+			close_sock();
 		}
 
 	} else {
-		std::cerr << "error: " << err.message() << std::endl;
-		sock.close();
+		std::cerr << __PRETTY_FUNCTION__ << " error: " << err.message() << std::endl;
+		close_sock();
 	}
 }
 
@@ -104,26 +126,28 @@ void con_handler::handle_write(const boost::system::error_code &err, size_t byte
 	if (!err) {
 		std::cout << "Hello World!" << std::endl;
 	} else {
-		std::cerr << "error: " << err.message() << std::endl;
-		sock.close();
+		std::cerr << __PRETTY_FUNCTION__ << " error: " << err.message() << std::endl;
+		close_sock();
 	}
 }
 
 void con_handler::read(void (con_handler::*handler)(const boost::system::error_code &err, size_t bytes_transferred)) {
-	/*sock.async_read_some(buffer(data, max_length),
-	                     boost::bind(handler, shared_from_this(),
-	                                 placeholders::error,
-	                                 placeholders::bytes_transferred));*/
-
-	async_read_until(sock, buf, '\n', bind(handler, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
+	if (usessl)
+		async_read_until(sslsock, buf, '\n', bind(handler, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
+	else
+		async_read_until(sock, buf, '\n', bind(handler, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
 }
 
 void con_handler::sendJson(const Json::Value &json) {
 	std::string jsonString = Json::writeString(jsonStringBuilder, json);
 	jsonString.append("\n");
 
-	sock.async_write_some(buffer(jsonString, max_length),
-	                      boost::bind(&con_handler::handle_write, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
+	if (usessl)
+		sslsock.async_write_some(buffer(jsonString, max_length),
+		                         boost::bind(&con_handler::handle_write, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
+	else
+		sock.async_write_some(buffer(jsonString, max_length),
+		                      boost::bind(&con_handler::handle_write, shared_from_this(), placeholders::error, placeholders::bytes_transferred));
 }
 
 Json::Value con_handler::parseMessage() {
@@ -138,7 +162,7 @@ Json::Value con_handler::parseMessage() {
 	// parse data
 	if (!this->jsonReader->parse(data, data + lineEnd, &root, &err)) {
 		std::cerr << "Json error: " << err << std::endl << "data: " << data;
-		sock.close();
+		close_sock();
 	}
 
 	buf.consume(lineEnd + 1);
@@ -152,11 +176,20 @@ Json::Value con_handler::parseMessage() {
 
 void Server::start_accept() {
 	auto executor = acceptor_.get_executor();
-	con_handler::pointer connection = con_handler::create(executor);
+	con_handler::pointer connection = con_handler::create(executor, context_);
 	acceptor_.async_accept(connection->socket(), boost::bind(&Server::handle_accept, this, connection, placeholders::error));
 }
 
-Server::Server(io_service &io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), std::stoi(Config::getValue("port")))) { start_accept(); }
+Server::Server(io_service &io_service)
+    : acceptor_(io_service, tcp::endpoint(tcp::v4(), std::stoi(Config::getValue("port")))), context_(boost::asio::ssl::context::sslv23) {
+	if (Config::getValue("SSLenabled") == "true") {
+		context_.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
+		context_.use_certificate_chain_file(Config::getValue("SSLcertificate"));
+		context_.use_private_key_file(Config::getValue("SSLprivatekey"), boost::asio::ssl::context::pem);
+		context_.use_tmp_dh_file(Config::getValue("SSLdhparams"));
+	}
+	start_accept();
+}
 
 Server::~Server() {}
 

+ 8 - 0
daemon/test/test-dh2048.pem

@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAkn1yM22zNYKpIdmWyhpyrs6lMNQyRk+BM7AzS/cgu5jr790SOeG9
++MSDGWlUUcC2uAAFFevg+XDD6Bk5qAGjJsbWKQyUBeTQdKURhdQh4cNfSPtKIh93
+ub1ViSQEwbekqQYpKWDGsogHj5MvKBN9y4Ywv5cfV0snaZIR278yDt7h0MuFNRUj
+BZpolcwLHNUjuSm3gBpNBEA3GUvWkGm9QATPVyNsmBGUW2+pVcdiRNV+zVbDceqR
+Jgsfz3Y8tO9yuXghUfmI6STOLERSWrzge161MH1QhRfWWOMGjKIrurVSUzheFT7d
+jTzCW014/7P0e85N5fwtWoQuGr0G0WficwIBAg==
+-----END DH PARAMETERS-----