diff --git a/VERSION b/VERSION index 359a5b9..867bf6b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0 \ No newline at end of file +1.0.0-rc1 diff --git a/include/iec104.h b/include/iec104.h index 9aa3730..091b00e 100644 --- a/include/iec104.h +++ b/include/iec104.h @@ -129,6 +129,7 @@ class IEC104Server static void connectionEventHandler(void* parameter, IMasterConnection con, CS104_PeerConnectionEvent event); CS104_Slave m_slave{}; + TLSConfiguration m_tlsConfig = nullptr; CS101_AppLayerParameters alParams; std::string m_name; Logger* m_log; @@ -142,6 +143,8 @@ class IEC104Server bool m_started = false; std::thread* m_monitoringThread = nullptr; void _monitoringThread(); + + bool createTLSConfiguration(); }; #endif \ No newline at end of file diff --git a/include/iec104_config.hpp b/include/iec104_config.hpp index 9665cf8..bc0c3c0 100644 --- a/include/iec104_config.hpp +++ b/include/iec104_config.hpp @@ -21,6 +21,7 @@ class IEC104Config void importProtocolConfig(const string& protocolConfig); void importExchangeConfig(const string& exchangeConfig); + void importTlsConfig(const string& tlsConfig); std::map>* getExchangeDefinitions() {return m_exchangeDefinitions;}; @@ -55,6 +56,11 @@ class IEC104Config string& CmdDest() {return m_cmdDest;}; + std::string& GetPrivateKey() {return m_privateKey;}; + std::string& GetOwnCertificate() {return m_ownCertificate;}; + std::vector& GetRemoteCertificates() {return m_remoteCertificates;}; + std::vector& GetCaCertificates() {return m_caCertificates;}; + private: static bool isValidIPAddress(const string& addrStr); @@ -96,6 +102,11 @@ class IEC104Config std::map>* m_exchangeDefinitions = nullptr; std::map m_allowedOriginators; + + std::string m_privateKey; + std::string m_ownCertificate; + std::vector m_remoteCertificates; + std::vector m_caCertificates; }; #endif /* IEC104_CONFIG_H */ \ No newline at end of file diff --git a/src/iec104.cpp b/src/iec104.cpp index 9fff5c3..c8b2bc4 100644 --- a/src/iec104.cpp +++ b/src/iec104.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace std; @@ -57,17 +58,136 @@ IEC104Server::m_getDataPoint(int ca, int ioa, int typeId) return dp; } +bool +IEC104Server::createTLSConfiguration() +{ + TLSConfiguration tlsConfig = TLSConfiguration_create(); + + if (tlsConfig) + { + bool tlsConfigOk = true; + + string certificateStore = getDataDir() + string("/etc/certs/"); + + if (m_config->GetOwnCertificate().length() == 0 || m_config->GetPrivateKey().length() == 0) { + Logger::getLogger()->error("No private key and/or certificate configured for client"); + tlsConfigOk = false; + } + + if (m_config->GetOwnCertificate().empty() == false) + { + string ownCertFile = certificateStore + m_config->GetOwnCertificate(); + + if (access(ownCertFile.c_str(), R_OK) == 0) { + + if (TLSConfiguration_setOwnCertificateFromFile(tlsConfig, ownCertFile.c_str()) == false) { + Logger::getLogger()->error("Failed to load own certificate from file: %s", ownCertFile.c_str()); + tlsConfigOk = false; + } + + } + else { + Logger::getLogger()->error("Failed to access own certificate file: %s", ownCertFile.c_str()); + tlsConfigOk = false; + } + } + + if (m_config->GetPrivateKey().empty() == false) + { + string privateKeyFile = certificateStore + m_config->GetPrivateKey(); + + if (access(privateKeyFile.c_str(), R_OK) == 0) { + + if (TLSConfiguration_setOwnKeyFromFile(tlsConfig, privateKeyFile.c_str(), NULL) == false) { + Logger::getLogger()->error("Failed to load private key from file: %s", privateKeyFile.c_str()); + tlsConfigOk = false; + } + + } + else { + Logger::getLogger()->error("Failed to access private key file: %s", privateKeyFile.c_str()); + tlsConfigOk = false; + } + } + + if (m_config->GetRemoteCertificates().size() > 0) { + TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true); + + for (std::string& remoteCert : m_config->GetRemoteCertificates()) + { + string remoteCertFile = certificateStore + remoteCert; + + if (access(remoteCertFile.c_str(), R_OK) == 0) { + if (TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, remoteCertFile.c_str()) == false) { + Logger::getLogger()->warn("Failed to load remote certificate file: %s -> ignore certificate", remoteCertFile.c_str()); + } + } + else { + Logger::getLogger()->warn("Failed to access remote certificate file: %s -> ignore certificate", remoteCertFile.c_str()); + } + + } + } + else { + TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, false); + } + + if (m_config->GetCaCertificates().size() > 0) { + TLSConfiguration_setChainValidation(tlsConfig, true); + + for (std::string& caCert : m_config->GetCaCertificates()) + { + string caCertFile = certificateStore + caCert; + + if (access(caCertFile.c_str(), R_OK) == 0) { + if (TLSConfiguration_addCACertificateFromFile(tlsConfig, caCertFile.c_str()) == false) { + Logger::getLogger()->warn("Failed to load CA certificate file: %s -> ignore certificate", caCertFile.c_str()); + } + } + else { + Logger::getLogger()->warn("Failed to access CA certificate file: %s -> ignore certificate", caCertFile.c_str()); + } + + } + } + else { + TLSConfiguration_setChainValidation(tlsConfig, false); + } + + if (tlsConfigOk) { + m_tlsConfig = tlsConfig; + } + else { + TLSConfiguration_destroy(tlsConfig); + m_tlsConfig = nullptr; + } + + return tlsConfigOk; + } + else { + return false; + } +} + void IEC104Server::setJsonConfig(const std::string& stackConfig, - const std::string& dataExchangeConfig, + const std::string& dataExchangeConfig, const std::string& tlsConfig) { m_config->importExchangeConfig(dataExchangeConfig); m_config->importProtocolConfig(stackConfig); + m_config->importTlsConfig(tlsConfig); m_exchangeDefinitions = *m_config->getExchangeDefinitions(); - m_slave = CS104_Slave_create(m_config->AsduQueueSize(), 100); + if (m_config->UseTLS()) { + if (createTLSConfiguration()) { + m_slave = CS104_Slave_createSecure(m_config->AsduQueueSize(), 100, m_tlsConfig); + } + } + else { + m_slave = CS104_Slave_create(m_config->AsduQueueSize(), 100); + } if (m_slave) { @@ -180,7 +300,14 @@ IEC104Server::configure(const ConfigCategory* config) const std::string dataExchange = config->getValue("exchanged_data"); - const std::string tlsConfig = std::string(""); + std::string tlsConfig = ""; + + if (config->itemExists("tls_conf") == false) { + m_log->error("Missing TLS configuration"); + } + else { + tlsConfig = config->getValue("tls_conf"); + } setJsonConfig(protocolStack, dataExchange, tlsConfig); } @@ -1025,7 +1152,10 @@ IEC104Server::send(const vector& readings) // update internal value m_updateDataPoint(dp, (IEC60870_5_TypeID)type, value, ts, qd); - if (cot == CS101_COT_PERIODIC || cot == CS101_COT_SPONTANEOUS) { + if (cot == CS101_COT_PERIODIC || cot == CS101_COT_SPONTANEOUS || + cot == CS101_COT_RETURN_INFO_REMOTE || cot == CS101_COT_RETURN_INFO_LOCAL || + cot == CS101_COT_BACKGROUND_SCAN) + { m_enqueueSpontDatapoint(dp, cot, (IEC60870_5_TypeID)type); } } @@ -1597,6 +1727,12 @@ IEC104Server::stop() m_slave = nullptr; } + if (m_tlsConfig) + { + TLSConfiguration_destroy(m_tlsConfig); + m_tlsConfig = nullptr; + } + if (m_started == true) { m_started = false; @@ -1607,4 +1743,4 @@ IEC104Server::stop() m_monitoringThread = nullptr; } } -} \ No newline at end of file +} diff --git a/src/iec104_config.cpp b/src/iec104_config.cpp index a3cd260..96b5f8b 100644 --- a/src/iec104_config.cpp +++ b/src/iec104_config.cpp @@ -568,6 +568,65 @@ IEC104Config::importExchangeConfig(const string& exchangeConfig) m_exchangeConfigComplete = true; } +void +IEC104Config::importTlsConfig(const string& tlsConfig) +{ + Document document; + + if (document.Parse(const_cast(tlsConfig.c_str())).HasParseError()) { + Logger::getLogger()->fatal("Parsing error in TLS configuration"); + + return; + } + + if (!document.IsObject()) + return; + + if (!document.HasMember("tls_conf") || !document["tls_conf"].IsObject()) { + return; + } + + const Value& tlsConf = document["tls_conf"]; + + if (tlsConf.HasMember("private_key") && tlsConf["private_key"].IsString()) { + m_privateKey = tlsConf["private_key"].GetString(); + } + + if (tlsConf.HasMember("own_cert") && tlsConf["own_cert"].IsString()) { + m_ownCertificate = tlsConf["own_cert"].GetString(); + } + + if (tlsConf.HasMember("ca_certs") && tlsConf["ca_certs"].IsArray()) { + + const Value& caCerts = tlsConf["ca_certs"]; + + for (const Value& caCert : caCerts.GetArray()) { + if (caCert.HasMember("cert_file")) { + if (caCert["cert_file"].IsString()) { + string certFileName = caCert["cert_file"].GetString(); + + m_caCertificates.push_back(certFileName); + } + } + } + } + + if (tlsConf.HasMember("remote_certs") && tlsConf["remote_certs"].IsArray()) { + + const Value& remoteCerts = tlsConf["remote_certs"]; + + for (const Value& remoteCert : remoteCerts.GetArray()) { + if (remoteCert.HasMember("cert_file")) { + if (remoteCert["cert_file"].IsString()) { + string certFileName = remoteCert["cert_file"].GetString(); + + m_remoteCertificates.push_back(certFileName); + } + } + } + } +} + int IEC104Config::TcpPort() { if (m_tcpPort == -1) { diff --git a/src/plugin.cpp b/src/plugin.cpp index 158ecae..35a06f6 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -175,17 +175,29 @@ static const char* default_config = QUOTE({ } }) }, - "tls" : { + "tls_conf" : { "description" : "tls parameters", "type" : "JSON", "displayName" : "TLS parameters", "order" : "4", "default" : QUOTE({ - "tls_conf:" : { - "private_key" : "server-key.pem", - "server_cert" : "server.cer", - "ca_cert" : "root.cer" - } + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer", + "ca_certs" : [ + { + "cert_file": "iec104_ca.cer" + }, + { + "cert_file": "iec104_ca2.cer" + } + ], + "remote_certs" : [ + { + "cert_file": "iec104_client.cer" + } + ] + } }) } }); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 465ac64..a7cecb5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,6 +78,9 @@ include_directories(${FLEDGE_INCLUDE_DIRS}) # Add Fledge lib path link_directories(${FLEDGE_LIB_DIRS}) +# copy certificates to output +file(COPY data DESTINATION ${CMAKE_BINARY_DIR}/tests) + # Link runTests with what we want to test and the GTest and pthread library add_executable(RunTests ${unittests} ${SOURCES} version.h) diff --git a/tests/data/etc/certs/iec104_ca.cer b/tests/data/etc/certs/iec104_ca.cer new file mode 100644 index 0000000..8768344 Binary files /dev/null and b/tests/data/etc/certs/iec104_ca.cer differ diff --git a/tests/data/etc/certs/iec104_client.cer b/tests/data/etc/certs/iec104_client.cer new file mode 100644 index 0000000..a5aa8db Binary files /dev/null and b/tests/data/etc/certs/iec104_client.cer differ diff --git a/tests/data/etc/certs/iec104_client.key b/tests/data/etc/certs/iec104_client.key new file mode 100644 index 0000000..c39425c --- /dev/null +++ b/tests/data/etc/certs/iec104_client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAxAMUXdnem99n5J8Z8Wa0fdPtoMCTdkQJrOO6WJ4XePrpQgHU +HDziSmdIZDDkpJ3Ey0Byy+b+iiRDmOuIZGSCsI0ehggWaia12h2osUK0BLyThuZ/ +RQw54K0dy61eviNaYsftiBcxHYKyKmWch/wLLYxdd1qLzd0reAbSUaHDkDzrj9hO +8qr7DhKpqx7PoVh1gFhAKPKuY6b+4xqv5eZvt8QflQTWYGxaxmUIEinqCnzh1l5d +tp0rhnBsz9Y4y8dXjh0m7pbXmRNY6opMxXatqgYEqsntLy1N6x7DvWLBqtVvEmox +Tc5bbAoRW3eEToClDdFQBzLsMVcSEX8vwttk3QIDAQABAoIBABHr1ijeiqPlwTH9 ++flAUrBOeCOCd/kQL3JHP/pqOestxbXrROFwD6CN4OiIL999LUkIE3bhH9SxjByn +LElBh1FtFaVbh/EcqPPQUmQinSLxuutSl8BQZdpM+bRtnYP054awkN8of60bDf8i +WzVzrfH0K3eGJ9Iirp7CwOgFykOdpQyxsI+HG8grcwA87x1ZsAIfHhiKmQByliNl +BkbJmYBOtfVgXje5QdxTptlTNljFSbZcaCXv1P3aOqctcgJMQjg0T+E37Y8Cav80 +6SuXbpv/cdacG695MAT7Vtywue0Axh59DvxAzc+deyQT70Hzw+Mo6pgi0clFnwzU +Y5ViDWECgYEAxxhRKzpz7klnmGob5CZvrbqDfC3JUEOxKH0e342S/HmT05bTI21w +N8A0KStNjQXS1mmkAkY/OO1Zutmf6yjqsxAIEO5UMTCSEP7YLRB7qBdN7dOt3JaK +4wxErMCljdT68Vj5Qj8YzIXJkWPk871oFTvVNe2qxgrCUigE5ai2I8cCgYEA/Akv +E0L+2uXayEucEamzO3n9xVziNanjyHilnJJvduvO9gd+crBbxSKqaXSdfPnp2mSa ++e3N7elxP2b/kPrGkzZekSaMh1nPH4Upu+ISK117r1x+vmnxZHRpehrVh1QzOQ5p +Ljt+GaXa3ur3P/6uW5KMbtGGW6MEgDwLMLvpqjsCgYA5pnfyfYWOTWEbCDa1VM/n +zWc/cP6nKELHR5vF/fe+9fFxRm4zBwCElDpGZYyaNkJ75bEhG3g5Irll2phs/rcf +TJgZVvm4GKljFHhCbFByNvVQ1Ye1pT3oSugj4dDOhgp4Elxy61Rh/KeGWxez4Heg +FmhBqmVV3U2xfncUjUrYhwKBgQCKtPM3gpOIHSA/Q31tKxv9C7JiQDAuoIU/+0YJ +2X2G0VhhhtZMgErBP8bRquBRu6i8DMpN6lZ/LQ6qeiEExT8sHawF7lVA2GhpTHwf +btfZDeXYKOuIF/5F7ttt2/7QL8LRD+FLFGrd6q1+KYpRqfSDaS/ofV+YZys+98yg +0YpTqQKBgQCWJpV2ySgXcKJzAUh14VNpLTRzJOMSpsU576nwz0TLUREVjiH6rvKr +gxllDEe1bVNMEekaZ+dOqiJX+4aTnEbIrEXki5Dvz0vq8biImW9RRdPEHgYVTv/d +qBOPHiIq2JiY6abD9XNPM3VQ/z8em6/4mkC8COCJRd2mA89FOYRxOQ== +-----END RSA PRIVATE KEY----- diff --git a/tests/data/etc/certs/iec104_server.cer b/tests/data/etc/certs/iec104_server.cer new file mode 100644 index 0000000..957bdc3 Binary files /dev/null and b/tests/data/etc/certs/iec104_server.cer differ diff --git a/tests/data/etc/certs/iec104_server.key b/tests/data/etc/certs/iec104_server.key new file mode 100644 index 0000000..6fe3529 --- /dev/null +++ b/tests/data/etc/certs/iec104_server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAu3Fjxb904UdyV/dDfvs8SFi6zJeNoMYjmpXm/LBcFSH3zGoK +wdcMovrUTED3Cc6Ww84AYpJ5MRMPTct7DfKJkWfSnkzOPmLldTSTv3RvzGVb4NzK +QqA5aSVDqAzPiP5RnFT6Q4KWRe69TMFxpw7zMXCJx9jDggqN1oojGGkmSgYGXnFd +Nc20Mujejh5pihgwnN4Y/bPFyxJwvIMj+D8qr9klhEmXKPTiX9UFd8oLkn9JCB6+ +SiHhNyFIo+Llossn1Q2hxCGty36fAhEWzpfBTaY510VLjie9y4q9GPTwxITDqSQd +xcX8IuvrbxX0DyEK507SMmTJmB9448eF9ZCWFQIDAQABAoIBAC80BuQtqslwrKLq +adz4d93gOmp7X/c07pJnXZwU7ZuEylp3+e2Gsm/4qq3pTkzx8ZWtsvsf19U774av +z3VbtrkfZDLpNKcRUKeLbgmw0NawT8r4zxaoMsz/zWHsl/bv1K2B2ORXZnCGBrXl +oTFo2mWA6bGiLNn6vm1grCXhlPreywyG/kFK3pi2VvkpvG3XZSI7mmZ0Dq/MD3nO +03oOZBqwwnMObfQQdhKE7646/+KgeuF/JsXaUH4bkHmtzYWyocWYMqpC0hjpNWlQ +cKuQ7t1kfmpsGD9aNW4+ND2ok9BdxIiC+rPXS9NDqZxoWLp+a8seU++uqk1l8RPq +tPE3LqECgYEAz1NmemNLiUsKvyemUvjp8+dJupzWtdV7fsnCbYhj/5gDA2UhFKCf +dP9xiHCdNe0797oAqHY7c3JhS4ug8haDy9aDIu5GG2DNYzjX/oYm4ywbCdRx+uEN +RcTw69FjSYVGkObmxWYszwsFybRasV6PYamg65qYR3FlvW2Td4Fndy8CgYEA53L/ +zHtBRQiNGJU9jfMHeX0bTtXIAt622Qn78jw0it/rhXWi2RwG2Cw5Q2aPRJ6uMt9F +yk1+GAPZcwYqwjq/nKRrl71Tn+KDWIk5rz1fNYRkaXtnMLs2MOogqoDTBshW0QBq +tnPrFNsaLKX6V92Az69wHjd2uwvLQLTvS/EuNfsCgYEAr3to/uhytAd3VirKRep3 +o0E+D5zWw1upxrwhPDK4aUuSKVp8sIfvz8iyoQiomE9vdZPTIMPKOEI1BgtuM9pI +vcyYfIVvg5bg4T3o3H9SBPB9BknyG6ZHZKl4PjGht0X+X4GBDM4Z2Tj8Mijcpsph +1AkOsrzMbZQWyEoqCnnWSHMCgYAFEHUcak4BTrCXqxxPsNOnCt/AF9lqhqkFkrxa +joqvxzqGDw7jJUPZEw6ltObJn5c8Mbp7NLrfl6X4aFgjK9npeYeJKHFd/DzXgRks +BnHA4Aa6cCLP5CjJZTYVxP/ZFCUiKZosJ9kq+ahW9cLGjWg2IyaW4qvMZ/OolMzv +onVaZQKBgQCir8u1vDsyA4JQXMytPHBJe27XaLRGULvteNydVB59Vt21a99o5gt1 +5B9gwWArZdZby3/KZiliNmzp8lMCrLJYjTL5WK6dbWdq92X5hCOofKPIjEcgHjhk +mvnAos3HeC83bJQtADXhw9jR7Vr6GJLM9HDcIgeIMzX7+BuqlMgaHA== +-----END RSA PRIVATE KEY----- diff --git a/tests/test_connectionHandler.cpp b/tests/test_connectionHandler.cpp index 33525ef..a78c17f 100644 --- a/tests/test_connectionHandler.cpp +++ b/tests/test_connectionHandler.cpp @@ -78,11 +78,124 @@ static string protocol_stack = QUOTE({ } }); +static string protocol_stack_2 = QUOTE({ + "protocol_stack" : { + "name" : "iec104server", + "version" : "1.0", + "transport_layer" : { + "redundancy_groups":[ + { + "connections":[ + { + "clt_ip":"192.168.2.244" + }, + { + "clt_ip":"192.168.0.11" + } + ], + "rg_name":"red-group-1" + }, + { + "connections":[ + { + "clt_ip":"192.168.2.224" + }, + { + "clt_ip":"192.168.0.11" + }, + { + "clt_ip":"192.168.0.12" + } + ], + "rg_name":"red-group-2" + }, + { + "rg_name":"catch-all" + } + ], + "bind_on_ip":false, + "srv_ip":"0.0.0.0", + "port":2404, + "tls":true, + "k_value":12, + "w_value":8, + "t0_timeout":10, + "t1_timeout":15, + "t2_timeout":10, + "t3_timeout":20 + }, + "application_layer" : { + "ca_asdu_size":2, + "ioaddr_size":3, + "asdu_size":0, + "time_sync":false, + "cmd_exec_timeout":20, + "cmd_recv_timeout":60, + "accept_cmd_with_time":2, + "filter_orig":false, + "filter_list":[ + { + "orig_addr":0 + }, + { + "orig_addr":1 + }, + { + "orig_addr":2 + } + ] + } + } + }); + static string tls = QUOTE({ - "tls_conf:" : { - "private_key" : "server-key.pem", - "server_cert" : "server.cer", - "ca_cert" : "root.cer" + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer", + "ca_certs" : [ + { + "cert_file": "iec104_ca.cer" + }, + { + "cert_file": "iec104_ca2.cer" + } + ], + "remote_certs" : [ + { + "cert_file": "iec104_client.cer" + } + ] + } + }); + +static string tls_2 = QUOTE({ + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer", + "remote_certs" : [ + { + "cert_file": "iec104_client.cer" + } + ] + } + }); + +static string tls_3 = QUOTE({ + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer", + "ca_certs" : [ + { + "cert_file": "iec104_ca.cer" + } + ] + } + }); + +static string tls_4 = QUOTE({ + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer" } }); @@ -147,27 +260,101 @@ class ConnectionHandlerTest : public testing::Test { // Init iec104server object iec104Server = new IEC104Server(); - const char* ip = "127.0.0.1"; - uint16_t port = IEC_60870_5_104_DEFAULT_PORT; - - // Create connection - connection = CS104_Connection_create(ip, port); } // TearDown is ran for every tests, so each variable are destroyed again void TearDown() override { - CS104_Connection_destroy(connection); iec104Server->stop(); delete iec104Server; } }; -TEST_F(ConnectionHandlerTest, ConnectionHandler) +TEST_F(ConnectionHandlerTest, NormalConnection) { iec104Server->setJsonConfig(protocol_stack, exchanged_data, tls); + // Create connection + connection = CS104_Connection_create("127.0.0.1", IEC_60870_5_104_DEFAULT_PORT); + + bool result = CS104_Connection_connect(connection); + ASSERT_TRUE(result); + + CS104_Connection_destroy(connection); +} + +TEST_F(ConnectionHandlerTest, TLSConnection) +{ + setenv("FLEDGE_DATA", "./tests/data", 1); + + TLSConfiguration tlsConfig = TLSConfiguration_create(); + + TLSConfiguration_addCACertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_ca.cer"); + TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.cer"); + TLSConfiguration_setOwnKeyFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.key", NULL); + TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_server.cer"); + TLSConfiguration_setChainValidation(tlsConfig, true); + TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true); + + // Create connection + connection = CS104_Connection_createSecure("127.0.0.1", IEC_60870_5_104_DEFAULT_PORT, tlsConfig); + + iec104Server->setJsonConfig(protocol_stack_2, exchanged_data, tls); + bool result = CS104_Connection_connect(connection); ASSERT_TRUE(result); + + CS104_Connection_destroy(connection); + TLSConfiguration_destroy(tlsConfig); } + +TEST_F(ConnectionHandlerTest, TLSConnectionNoCaCertificate) +{ + setenv("FLEDGE_DATA", "./tests/data", 1); + + TLSConfiguration tlsConfig = TLSConfiguration_create(); + + TLSConfiguration_addCACertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_ca.cer"); + TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.cer"); + TLSConfiguration_setOwnKeyFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.key", NULL); + TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_server.cer"); + TLSConfiguration_setChainValidation(tlsConfig, true); + TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true); + + // Create connection + connection = CS104_Connection_createSecure("127.0.0.1", IEC_60870_5_104_DEFAULT_PORT, tlsConfig); + + iec104Server->setJsonConfig(protocol_stack_2, exchanged_data, tls_2); + + bool result = CS104_Connection_connect(connection); + ASSERT_TRUE(result); + + CS104_Connection_destroy(connection); + TLSConfiguration_destroy(tlsConfig); +} + +TEST_F(ConnectionHandlerTest, TLSConnectionNoRemoteOrCaCertificate) +{ + setenv("FLEDGE_DATA", "./tests/data", 1); + + TLSConfiguration tlsConfig = TLSConfiguration_create(); + + TLSConfiguration_addCACertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_ca.cer"); + TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.cer"); + TLSConfiguration_setOwnKeyFromFile(tlsConfig, "tests/data/etc/certs/iec104_client.key", NULL); + TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "tests/data/etc/certs/iec104_server.cer"); + TLSConfiguration_setChainValidation(tlsConfig, true); + TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true); + + // Create connection + connection = CS104_Connection_createSecure("127.0.0.1", IEC_60870_5_104_DEFAULT_PORT, tlsConfig); + + iec104Server->setJsonConfig(protocol_stack_2, exchanged_data, tls_4); + + bool result = CS104_Connection_connect(connection); + ASSERT_FALSE(result); + + CS104_Connection_destroy(connection); + TLSConfiguration_destroy(tlsConfig); +} \ No newline at end of file diff --git a/tests/test_control.cpp b/tests/test_control.cpp index d287b4b..b20cd99 100644 --- a/tests/test_control.cpp +++ b/tests/test_control.cpp @@ -60,7 +60,7 @@ static string protocol_stack = QUOTE({ "ioaddr_size":3, "asdu_size":0, "time_sync":false, - "cmd_exec_timeout":5, + "cmd_exec_timeout":1, "cmd_recv_timeout":1, "accept_cmd_with_time":2, "filter_orig":false, @@ -80,10 +80,14 @@ static string protocol_stack = QUOTE({ }); static string tls = QUOTE({ - "tls_conf:" : { - "private_key" : "server-key.pem", - "server_cert" : "server.cer", - "ca_cert" : "root.cer" + "tls_conf" : { + "private_key" : "iec104_server.key", + "own_cert" : "iec104_server.cer", + "ca_certs" : [ + { + "cert_file": "iec104_ca.cer" + } + ] } }); @@ -137,6 +141,67 @@ static string exchanged_data = QUOTE({ } }); +static string exchanged_data_2 = QUOTE({ + "exchanged_data" : { + "name" : "iec104server", + "version" : "1.0", + "datapoints":[ + { + "label":"TS1", + "protocols":[ + { + "name":"iec104", + "address":"45-672", + "typeid":"M_SP_NA_1" + }, + { + "name":"tase2", + "address":"S_114562", + "typeid":"Data_StateQTimeTagExtended" + } + ] + }, + { + "label":"TM1", + "protocols":[ + { + "name":"iec104", + "address":"45-984", + "typeid":"M_ME_NA_1" + }, + { + "name":"tase2", + "address":"S_114562", + "typeid":"Data_RealQ" + } + ] + }, + { + "label":"CM1", + "protocols":[ + { + "name":"iec104", + "address":"45-10005", + "typeid":"C_SC_NA_1", + "termination_timeout": 3000 + } + ] + }, + { + "label":"CM2", + "protocols":[ + { + "name":"iec104", + "address":"45-10010", + "typeid":"C_SE_TC_1", + "termination_timeout": 1 + } + ] + } + ] + } + }); + // Class to be called in each test, contains fixture to be used in class ControlTest : public testing::Test { @@ -328,6 +393,34 @@ TEST_F(ControlTest, ReceiveSinglePointCommand) ASSERT_EQ(1, operateHandlerCalled); } +TEST_F(ControlTest, ReceiveSetpointCommandShortWithTimestamp) +{ + iec104Server->setJsonConfig(protocol_stack, exchanged_data_2, tls); + + iec104Server->registerControl(operateHandler); + + iec104Server->ActConTimeout(200); + iec104Server->ActTermTimeout(200); + + ASSERT_TRUE(CS104_Connection_connect(connection)); + + CS104_Connection_sendStartDT(connection); + + CP56Time2a timestamp = CP56Time2a_createFromMsTimestamp(NULL, Hal_getTimeInMs()); + + InformationObject sc = (InformationObject)SetpointCommandShortWithCP56Time2a_create(NULL, 10010, 1.5f, false, 0, timestamp); + + CS104_Connection_sendProcessCommandEx(connection, CS101_COT_ACTIVATION, 45, sc); + + InformationObject_destroy(sc); + + free(timestamp); + + Thread_sleep(1500); + + ASSERT_EQ(1, operateHandlerCalled); +} + TEST_F(ControlTest, SinglePointCommandUnknownCA) { iec104Server->setJsonConfig(protocol_stack, exchanged_data, tls); diff --git a/tests/test_sendSpontData.cpp b/tests/test_sendSpontData.cpp index 73ebc89..92cf698 100644 --- a/tests/test_sendSpontData.cpp +++ b/tests/test_sendSpontData.cpp @@ -977,3 +977,40 @@ TEST_F(SendSpontDataTest, CreateReading_M_ME_TF_1) delete dataobjects; } + +TEST_F(SendSpontDataTest, CreateReading_differentSpontaneousCOTs) +{ + iec104Server->setJsonConfig(protocol_stack, exchanged_data, tls); + + CS104_Connection_setASDUReceivedHandler(connection, test1_ASDUReceivedHandler, this); + + bool result = CS104_Connection_connect(connection); + ASSERT_TRUE(result); + + CS104_Connection_sendStartDT(connection); + + auto* dataobjects = new vector; + + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_SPONTANEOUS, (float)0.1f, false, false, false, false, false, NULL)); + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_BACKGROUND_SCAN, (float)1.0f, false, false, true, false, false, NULL)); + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_PERIODIC, (float)1.0f, false, false, true, false, false, NULL)); + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_RETURN_INFO_LOCAL, (float)1.0f, false, false, true, false, false, NULL)); + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_RETURN_INFO_REMOTE, (float)1.0f, false, false, true, false, false, NULL)); + dataobjects->push_back(createDataObject("M_ME_NA_1", 45, 984, CS101_COT_INTERROGATED_BY_STATION, (float)1.0f, false, false, true, false, false, NULL)); + + Reading* reading = new Reading(std::string("TM1"), *dataobjects); + + vector readings; + + readings.push_back(reading); + + iec104Server->send(readings); + + Thread_sleep(500); + + ASSERT_EQ(5, receivedAsdu.size()); + + delete reading; + + delete dataobjects; +} \ No newline at end of file