diff --git a/SConstruct b/SConstruct index ef7d5bf..ae3df10 100644 --- a/SConstruct +++ b/SConstruct @@ -234,10 +234,14 @@ echo_server = SConscript('#/examples/echo_server/SConscript',variant_dir = build # echo_client echo_client = SConscript('#/examples/echo_client/SConscript',variant_dir = builddir + 'echo_client',duplicate = 0) +# print_client +print_client = SConscript('#/examples/print_client/SConscript',variant_dir = builddir + 'print_client',duplicate = 0) + # echo_server_tls if tls_build: echo_server_tls = SConscript('#/examples/echo_server_tls/SConscript',variant_dir = builddir + 'echo_server_tls',duplicate = 0) echo_server_both = SConscript('#/examples/echo_server_both/SConscript',variant_dir = builddir + 'echo_server_both',duplicate = 0) + print_client_tls = SConscript('#/examples/print_client_tls/SConscript',variant_dir = builddir + 'print_client_tls',duplicate = 0) # broadcast_server broadcast_server = SConscript('#/examples/broadcast_server/SConscript',variant_dir = builddir + 'broadcast_server',duplicate = 0) diff --git a/changelog.md b/changelog.md index 9d7fb5e..ba8284f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,9 @@ HEAD +- Examples: Add print_client example. This demonstrates a minimal non-TLS + client that connects to a server and prints out the messages it receives. +- Examples: Add print_client_tls example. This demonstrates a minimal TLS + client, including basic support via Asio+OpenSSL for certificate chain + and hostname verification. - Improvement: Move the socket_init_handler to execute as a part of init_asio rather than connection pre_init. This allows setting of socket options prior to the bind/listen/accept system calls. Thank you ChristianRobl3D for diff --git a/examples/echo_server_tls/server.pem b/examples/echo_server_tls/server.pem index 5196dcf..da233f5 100644 --- a/examples/echo_server_tls/server.pem +++ b/examples/echo_server_tls/server.pem @@ -1,55 +1,57 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA77v5VbFaq+geVd/RCLXoKTWj/LgXGjiOXBpJZca3o5edi7N1 -Ej41/13jfZ4OxgFIRFAALd+fU4NsObXzskth3rJLmJjUtGajRIPqF2IUVjFMmybQ -9dUDSqPzd/bG+ul0DP7DNuiBcLC8XNC3/kchPUzVq9gNDk56jXn87kMoIkVXBWQS -Rwk0yCtELSfwxxRCrGKgQDfXnRdwmkPe42DmKtdGksXJlUhP1UVTAXaSw1Nz64LV -9bJje/eoDsSe4OxifVVToE6ZxIW+r1jVgrcupeYXQrSHIztl2U/rx2JkGMN0eS+P -qrsJmkmRBN1cpgmvSV7WoM3hj6Eq71z4Dfv6EwIDAQABAoIBAEXAL19bZsI1mv3p -TOx34MB8tuXEuhQK+ICbtVdDZhLW/iOzZxCTwSo3qwTVg/7gSKJ3lFXEhprJ1idE -ZU8u157vyUbw0JJceoxoxSdghgI9/cf2lz2vaHHDGgeBaYt/eSB+z1WeeGrNQUDQ -CXiWQXmQbWq+Ra4v70BSieDY8UhmzCTRhvfZV80vaY/4SnHxJ9C8Nu8/An7U3pwq -ccfTaWMp3Q5ztmEnExF6/b1SUsnI8rzFovw/4C50AU09N/Z6zZVFsXgJAT2Kd1Tx -HfP1vUkWZ/TJ6kcmVIV9U3uMcedBD8Rb/3M0Qdp+eymQ+1oYQ3K3/a2RlUQIeXzf -hRbE4wECgYEA/WyULrwBsvSMPqK0K9pZpv921Yl6d4bGnpxKze7ltc9o5/LsU35q -8u29Es7cPyhyUDOsPW8v2CNjw54t+eFGsu59bBvXZNz5gB7hTUEyLntB24cO5PmN -kVExcMDFC6NgPaauBtUhODievKJ2C8P7sRJDAVKE9KkavDddU7nSEjMCgYEA8ivG -AqwNXYsiIFjAo4uj3kuBFt8LU/q4m6GXI+9qyxfiJjuF1dROx5qaQGiE85VaBFUu -gpbnjZH7s+BBgoaQhrB4i6mztljdN1KKuAlnjhB+Fywg8m8N9lAi3wIaxnIEqDkU -vFiIFPTBCk0U7IZ/OyCrKPQTE9ApWHfBwA/vWKECgYBQixrJg61SkBCms5Vpvprx -zY2aLniC1o33yRqpOr09PG9OENH1c19QWCjYenBbjmJOhS2/1L+zBQRnHrXkDion -Ik8wdeTORMDzbF0U7ZyiU0BKIjGkqn/I6LI68LlvinxC+9+hgkltkek5cLTt5lrv -Gyu6ltx02e4KVdpOiuduKwKBgQC3U0PWigCkK8ttyUIrjG5Evcu/UKH2tPpDdpQ/ -8+JYVIAyiSTLtqjcmcDjuTvMWeeHGCTZXvtzRGvSw5VUBiIqlDTtJU6SX7s3QhkZ -MKVf+kQ5roJShJeBOzDquWEjkPTNlEiKPErn8lCgR7HrS/XNAPIRUpOOkCp8ekwF -5Qo/gQKBgAue3TeCeIzxh4PxSAk39Uaj+AaLvlYg8C+ucEodCNQZnJ7jOO0hHkfT -NvZBes0VukCGlBLOGRQtC3kgtyrerscnDiZOhdnJrhPIdNPC0k/p/uAfh7+C5Sxm -rCEL0S7kORdEjlIGd5j6ZEN/HZe8FyOYSjbFAoJSlAId9WMSxycQ ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDMYjHlTUeUGSys +Fz5PZcvgS3gojBlEAOu2gXFQDcJ7kq6dJ4jKsPaH1Q5jAtEDkU/el8otzfacOgyp +2ZxioRimpmcORWGU0bKJcenh4ZQ1oK1CQObjeYk1YgE7H8/sBetSdtL4n1rB8LIz +AV/k6kwSZFu3/lSmc6g09H4efSKGKVBcVOjBamcvFGVH4KhM2NyL+ffeV5H2Ucxk +ipyhpr4uxEoC3EV60sQxloqZb+upBM0LS4kVvaYMcn39XkUx3Z8FwN5+hFGwsWda +tU8zDxEuRMrZxG7mwDXLBGehtQvoJIVMQbOuwBQcgAbsVyy1dxV3aczbLX0iGEuG +eBhdFE+BAgMBAAECggEAQaPn0nUXYAEVz89HO8i9ybNzS9Jy6txA18SK1+MTawyY +9/AShsZ+5vEORc5JwpOQyzSEwmE7qsEaABLbnvGOMTeQMY0m4dzXMj1bmCgSqYaJ +HpYpkTUfU/2913dIF81u3nU7HI5RX6gmEyuF2MdG10FUE6ujFDJg+2DqgHA//kYD +hkXFinVS2PuZs8d5xdzpF0aCIWTuOc+Fgsyhdm/lZRIzFdID45YUVuPIN2uh+GkM +ENp/r1x7dPlDRqiL1ufP0mTQGs26S5kQSF8W0BClkOIOgmrhSON4+Vqhqx+ki/7w +RY+7mmgdvt0uzYT+Lk2cDw4f89Rsh7rR1EieBpQ2YQKBgQDq6zAHWfweJmkugT0w +HzI0UKfcOdzlJBwMu6tSgSHU99dnXlTwQY8sG7vtfRekokoo7XY4JsSk1n6E9OVy +4UKuEvU1+llDGxtvHxEEGOAgwB8wxMuY4uNYgDVhTlUzr2ERcet7FOIGzxEWzSsg +5vgnTQfyMzAh5/6k8CsHVI4u2wKBgQDeuYVCgg555lcc5rvTFxfU15d3fweSd78+ +akgIBaXAlFbxI+5znGPmKG/ii4N2XObC8B568fA2nIxw6M1xgbKyvvmN3ECYiqWv +bx8x6Vg5Slg0vJr+DrPgvIKbOWEEKF/cfpTeeVLP0gUBT63mA3qezuRx1r0JJr7A +k9a4Td9j0wKBgDmRQMfMaVgKGaRnz1LHkkn3qerx0wvj+Wu1YZpqQpwp0ANovm/R +4P/yG+9qxCx4CKxW5K2F8pJibcavLLsmMGzwAF8l5lHnhqWIe2cBoYrlCb+tuibR +Et1RLcOWqpJr2+GmhQo4Z9s7SvjHdlYtw4n9+oCDwrvMWj6ZDDJTqjQZAoGAEhRt +RODZ2/texvHT/Wa6gISfvwuIydL+q0plXoFW2zMve5O3H5tqYJyXuIQqv8j60og7 +cS+CmGxM2j2Lr9MfdnMaPvHKLJfUq1ER7zNJ/hyS3HUS/9yhrXSgBYm63mOIpJWB +8C1ZE5Ww4lJdg3Z01b9lu/f6kGucwHU/0OZBZBECgYAQ+dl2kKKd+lQ9O/LVz7oD +goQMPYF+QZcEhY4vlYKkWVtR2A0CiY6XeTi6vO/qVUt/ht+UO3XIJFOjGV1VyORQ +Bhibfstxl5s59jGlns5y5QqcRKzCiX74BKG0xQUtHgga7Od6L+GJKbJAPBfncYwW +U7Tfwwi0WbbgQoy5Xr/5gg== +-----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIEpzCCA4+gAwIBAgIJAMlCHvwnJ+F/MA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD -VQQGEwJVUzELMAkGA1UECBMCSUwxEDAOBgNVBAcTB0NoaWNhZ28xGTAXBgNVBAoT -EFdlYlNvY2tldCsrIERlbW8xIjAgBgNVBAMTGXV0aWxpdGllcy53ZWJzb2NrZXRw -cC5vcmcxJjAkBgkqhkiG9w0BCQEWF3dlYnNvY2tldHBwQHphcGhveWQuY29tMB4X -DTE1MDQyNTE1NTk1M1oXDTE4MDMyODE1NTk1M1owgZMxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEZMBcGA1UEChMQV2ViU29ja2V0 -KysgRGVtbzEiMCAGA1UEAxMZdXRpbGl0aWVzLndlYnNvY2tldHBwLm9yZzEmMCQG -CSqGSIb3DQEJARYXd2Vic29ja2V0cHBAemFwaG95ZC5jb20wggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDvu/lVsVqr6B5V39EItegpNaP8uBcaOI5cGkll -xrejl52Ls3USPjX/XeN9ng7GAUhEUAAt359Tg2w5tfOyS2HeskuYmNS0ZqNEg+oX -YhRWMUybJtD11QNKo/N39sb66XQM/sM26IFwsLxc0Lf+RyE9TNWr2A0OTnqNefzu -QygiRVcFZBJHCTTIK0QtJ/DHFEKsYqBAN9edF3CaQ97jYOYq10aSxcmVSE/VRVMB -dpLDU3PrgtX1smN796gOxJ7g7GJ9VVOgTpnEhb6vWNWCty6l5hdCtIcjO2XZT+vH -YmQYw3R5L4+quwmaSZEE3VymCa9JXtagzeGPoSrvXPgN+/oTAgMBAAGjgfswgfgw -HQYDVR0OBBYEFAWNBET93ZzSukXGkuGb93CfmUkDMIHIBgNVHSMEgcAwgb2AFAWN -BET93ZzSukXGkuGb93CfmUkDoYGZpIGWMIGTMQswCQYDVQQGEwJVUzELMAkGA1UE -CBMCSUwxEDAOBgNVBAcTB0NoaWNhZ28xGTAXBgNVBAoTEFdlYlNvY2tldCsrIERl -bW8xIjAgBgNVBAMTGXV0aWxpdGllcy53ZWJzb2NrZXRwcC5vcmcxJjAkBgkqhkiG -9w0BCQEWF3dlYnNvY2tldHBwQHphcGhveWQuY29tggkAyUIe/Ccn4X8wDAYDVR0T -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 -----END CERTIFICATE----- - diff --git a/examples/print_client/CMakeLists.txt b/examples/print_client/CMakeLists.txt new file mode 100644 index 0000000..98b975a --- /dev/null +++ b/examples/print_client/CMakeLists.txt @@ -0,0 +1,12 @@ + +file (GLOB SOURCE_FILES *.cpp) +file (GLOB HEADER_FILES *.hpp) + +init_target (print_client) + +build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES}) + +link_boost () +final_target () + +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "examples") diff --git a/examples/print_client/SConscript b/examples/print_client/SConscript new file mode 100644 index 0000000..8da4ce5 --- /dev/null +++ b/examples/print_client/SConscript @@ -0,0 +1,23 @@ +## Print client example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is available build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('print_client', ["print_client.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('print_client', ["print_client.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/print_client/print_client.cpp b/examples/print_client/print_client.cpp new file mode 100644 index 0000000..7ba6e5f --- /dev/null +++ b/examples/print_client/print_client.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include + +#include + +typedef websocketpp::client client; + +void on_message(websocketpp::connection_hdl, client::message_ptr msg) { + std::cout << msg->get_payload() << std::endl; +} + +int main(int argc, char* argv[]) { + client c; + + std::string uri = "ws://localhost:9002"; + + if (argc == 2) { + uri = argv[1]; + } + + try { + // Set logging to be pretty verbose (everything except message payloads) + c.set_access_channels(websocketpp::log::alevel::all); + c.clear_access_channels(websocketpp::log::alevel::frame_payload); + c.set_error_channels(websocketpp::log::elevel::all); + + // Initialize ASIO + c.init_asio(); + + // Register our message handler + c.set_message_handler(&on_message); + + websocketpp::lib::error_code ec; + client::connection_ptr con = c.get_connection(uri, ec); + if (ec) { + std::cout << "could not create connection because: " << ec.message() << std::endl; + return 0; + } + + // Note that connect here only requests a connection. No network messages are + // exchanged until the event loop starts running in the next line. + c.connect(con); + + // Start the ASIO io_service run loop + // this will cause a single connection to be made to the server. c.run() + // will exit when this connection is closed. + c.run(); + } catch (websocketpp::exception const & e) { + std::cout << e.what() << std::endl; + } +} diff --git a/examples/print_client_tls/CMakeLists.txt b/examples/print_client_tls/CMakeLists.txt new file mode 100644 index 0000000..18ea753 --- /dev/null +++ b/examples/print_client_tls/CMakeLists.txt @@ -0,0 +1,17 @@ + +file (GLOB SOURCE_FILES *.cpp) +file (GLOB HEADER_FILES *.hpp) + +if (OPENSSL_FOUND) + +init_target (print_client_tls) + +build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES}) + +link_boost () +link_openssl() +final_target () + +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "examples") + +endif() \ No newline at end of file diff --git a/examples/print_client_tls/SConscript b/examples/print_client_tls/SConscript new file mode 100644 index 0000000..01b0114 --- /dev/null +++ b/examples/print_client_tls/SConscript @@ -0,0 +1,24 @@ +## Print client tls example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') +Import('tls_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is available build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] + prgs += env_cpp11.Program('print_client_tls', ["print_client_tls.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system'],env) + [platform_libs] + [polyfill_libs] + [tls_libs] + prgs += env.Program('print_client_tls', ["print_client_tls.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/print_client_tls/ca-chain.cert.pem b/examples/print_client_tls/ca-chain.cert.pem new file mode 100644 index 0000000..12e233b --- /dev/null +++ b/examples/print_client_tls/ca-chain.cert.pem @@ -0,0 +1,66 @@ +-----BEGIN CERTIFICATE----- +MIIFxTCCA62gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwYDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAklMMRAwDgYDVQQHDAdDaGljYWdvMRQwEgYDVQQKDAtXZWJTb2Nr +ZXQrKzEcMBoGA1UEAwwTV2ViU29ja2V0KysgUm9vdCBDQTAeFw0xNjA1MjUxMzU4 +MjdaFw0yNjA1MjMxMzU4MjdaMIGCMQswCQYDVQQGEwJVUzELMAkGA1UECAwCSUwx +FDASBgNVBAoMC1dlYlNvY2tldCsrMSowKAYDVQQLDCFXZWJTb2NrZXQrKyBDZXJ0 +aWZpY2F0ZSBBdXRob3JpdHkxJDAiBgNVBAMMG1dlYlNvY2tldCsrIEludGVybWVk +aWF0ZSBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMNaFAzlx0KJ +gG15yRHI3xn9+B1woHG4uuOr124Sk1JllPcO3enusgIYTMl0FiYYW9CsyPoe4L0P +wflbz20vDDjxmXG+NPgjuYmnPaq7q2JXYz+cShv9+o60EIwEIe+EWk1ZQs9YSdQ0 +r4UOxGVq6eEuWJi8Wh02cbnxdjwvrk7lTMFVY+z5EX8cCj6Tbrd0lyIf/0X8OkOb +q2HOqqzTgT2apBCWCEW6grW6rtMOoDx93BOZDBEGz39sJ5i8AQ8XIdYCdUcOMdJU +SCAw/MMyFTHXhv8hJdG5GcDSfc7woB9xRUf8UHuCH0nYkTb260TWvyDCYJy001ko +SWoRbh2hVgPqQ9FTDMzMTY8T8C5u3BRfGN5PHuSPhwfHv/p1g4uPnltDBe4CNtOs +wu8w1wbrr3uI7qETnqOzbXlcT7o4rCrrRQqLbNOssf2mMH+Phq6dINjXpZjiAhO0 +SURtBMmQdAZcQkGStzFitEkb2Py5LEIxQ068i8RCowTyD9+/jbO1fZyxJ4X8TDUe +Xx48xWnu0i4f8/9ldnWLwX9h3ilaZVsr7buNYJoMlz+v73TQoWKSybJ2SMe/Cddj +OZCy5r1UakuZhX6n1ScD/hbO8FEfmQRpAywYajyU4dZ9XMbf5bo6OAUqlJ2f4yYh +VAy5mi1JHfD5PiJN90j79GXXvtBTJc4hAgMBAAGjZjBkMB0GA1UdDgQWBBTKMn5O +3NUPpztL1bAz8UCsOBLpkjAfBgNVHSMEGDAWgBTNBBKZQN694xplMGyMruXFv27o +eTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAgEAyNkZUEAdP73th4/RLu0d2foMiIqKlcvw3JsW0tto3MT5lvQ5UugH +OwluhWhnMLE0KsknQd7/p4ZwyZugWAYjGcDydp0GDIDfNBBEOQkOAL23KkYiRFqt +VPBTZi9S7P2MJLY0j94liIg94nikhz/0q7JxxWFlvSHUjwZxXrbFjfZRPOS1vIq/ +/VK2QjUsdIXE3NOPYfQwd9FpG2YS8ZcMeipwNYVAs2FBEeWzGH1j6i2hP8FFBDYP +0LTvJYOJvlCeyIvPBjKk9461/Z4CPJcKtKC59onQmiqSK/Juak/bpPoY7jJ228KG +bEBzClIEHgbDiBewFTHbyOWhW2ySRLOGsPeqKDSbm4J1N5rfKnrSQB9PfOmWoRfJ +vqPlXFSlpdgD4j/WnEumpvt78fT+cn+AkRG8tE5DQrCWZTK47TSWn902Fm0A19Rl +pSbE9qsulXurOqEuOOayrzcUmbZ/jkU+wj+/tN4Gl8K98WbjcXvwz0sRL3SgRRrI +awUdaGWKQHrTJNEOTisepUAuHVDmvuQz0j/Ru+PbB9K3GcKY6X6+o1c2JBC1V6KX +aHHZQ+xPm+VEa1pG/QVHGpt2AbGUQlXwDYtOIRwEhO27tFbH8Q68s2cMLYjsF5gd +MWuMYCPkFv10/V2f2lAIPSEzw2pldIGERcb4VG4xuD0qU+HH/aAID7k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFpjCCA46gAwIBAgIJAL42eqbfw976MA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHQ2hpY2FnbzEUMBIGA1UECgwL +V2ViU29ja2V0KysxHDAaBgNVBAMME1dlYlNvY2tldCsrIFJvb3QgQ0EwHhcNMTYw +NTI1MTM1MTUzWhcNMzYwNTIwMTM1MTUzWjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCSUwxEDAOBgNVBAcMB0NoaWNhZ28xFDASBgNVBAoMC1dlYlNvY2tldCsrMRww +GgYDVQQDDBNXZWJTb2NrZXQrKyBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEA4QjJ0v5yri+pAN67I/XPz88D8oIczCW96CIuwc44aDC9Kptb +9iY8xwbGCyQsFZ/1IQ74QfnXZSwq8EwedcBIdcyHdBu6qtNkCVKeDIZAMBef6Hx+ +tWSe1op3sDbUlT8NHiTxZCZWk/2/yIi8yPzQTi4y1vF04vvrQS5RFomCz17kdyOa +NdxO5p+I4afdoVKtzA1aHoBqdTe7vzM3eww4AxKfgIEDdIuOGDiglI/b/frlwiOi +SfTOsPzu52TOPW2d1Ad5BG4GuMpnTUOVnc8j18w9LdeXO0J10oVyCmwiPuzFCcDB +g1xvVr5TXzIZ5J+qlso7+mUfZGH+nxOT7Tc78o1EvX6JbfQAI2PrpcksmJfFnN4l +4XnXDW/eKl8AlLUr/cW5axAfql4QHJoBCZcfYldQpMoL5R1ikLtY53cOJpycFoWm +1IEfkLBZ4C1old+KoaErG0+Aur8/kwAJGMnmMvZqGZ5pgXtVipOLy5TKuS6ZKO8g +MRzalaF/naiu3pF+/sctaqkAPvOr65WrANNGxTQ93ePdyuT6sOEUKXxaXcTtAOOM +5FCgX8dPxkOACxTrxppvb+bYmYL9GIuYDGYxSRu3Fm+04eXIh+uCqcuWPQuRPc5t +VXvk/M0fPaJvKfP6lRAoE5Dp4qPRvL6tRVtOXfP6d+O+yGnxRoLKAW7ejoMCAwEA +AaNjMGEwHQYDVR0OBBYEFM0EEplA3r3jGmUwbIyu5cW/buh5MB8GA1UdIwQYMBaA +FM0EEplA3r3jGmUwbIyu5cW/buh5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQAQ4eY4LhW795wl9XuDVg2vOFTYmIS0 +OxrunFX4f3RjddbIbXzYmQ0cJ8pJ5l7eGYcg/DYQRY4Tk6LjXMs9VhIU10akqLS4 +qGE+Bmp3Jhu5NxZbKkY+k+kTAA1LYxFCjGjSV0v5QNLFULDAmGer2zWwU5DcDwwq +8yWyBuI974UyE/49/TeckfqwBrb90LL2lFEwoL86XZK2IZMPyMBC/S1X5P/Kc15Q +d8lwOPS5AirFkkrzs/px+mRia5U1uWKIPRLq9Medvjf8HR8SFWq9eRtkxiLaWyRv +HBVyVRKCubCZR8psVLK/zrF+Bc+Hr9aAi3TuqTKjIOI7hrq5oJcJpebZDNoBIqoj +kab13WcRwG+BQvuK1CEkd1aq8Nh2GX6Reb2Zv82/WntgP1a0sztbIGgrUBYQryb5 +HF79v4e2byY613SiQ3lz+g/AWxaZsYH80/Zl+hEwEtU4fFz34Jcv9Kvda1JpknBT +Fi63ugfoNeNriO02AReMmDvuBG3X8RF1UQyBoTU3uZuW7X26MizEjiVCK9qaOLED +WDSEoyKLe4JKd387CVlsCY8K/6fBlFTI/hJhggDz8pZFj3n2irUI44kjgOmoxOrW +JY2jgY89AEMN9yOKkyQGara8pF9IJxTQ7jurYnWcUbompWeybJRwvWN0h+tGV+bd +l/aq/5LwL3fVpg== +-----END CERTIFICATE----- diff --git a/examples/print_client_tls/print_client_tls.cpp b/examples/print_client_tls/print_client_tls.cpp new file mode 100644 index 0000000..0a1c72c --- /dev/null +++ b/examples/print_client_tls/print_client_tls.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2016, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include + +#include + +typedef websocketpp::client client; +typedef websocketpp::lib::shared_ptr context_ptr; + +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; + +void on_message(websocketpp::connection_hdl, client::message_ptr msg) { + std::cout << msg->get_payload() << std::endl; +} + +/// Verify that one of the subject alternative names matches the given hostname +bool verify_subject_alternative_name(const char * hostname, X509 * cert) { + STACK_OF(GENERAL_NAME) * san_names = NULL; + + san_names = (STACK_OF(GENERAL_NAME) *) X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return false; + } + + int san_names_count = sk_GENERAL_NAME_num(san_names); + + bool result = false; + + for (int i = 0; i < san_names_count; i++) { + const GENERAL_NAME * current_name = sk_GENERAL_NAME_value(san_names, i); + + if (current_name->type != GEN_DNS) { + continue; + } + + char * dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + + // Make sure there isn't an embedded NUL character in the DNS name + if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { + break; + } + // Compare expected hostname with the CN + result = (strcasecmp(hostname, dns_name) == 0); + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return result; +} + +/// Verify that the certificate common name matches the given hostname +bool verify_common_name(const char * hostname, X509 * cert) { + // Find the position of the CN field in the Subject field of the certificate + int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1); + if (common_name_loc < 0) { + return false; + } + + // Extract the CN field + X509_NAME_ENTRY * common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc); + if (common_name_entry == NULL) { + return false; + } + + // Convert the CN field to a C string + ASN1_STRING * common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) { + return false; + } + + char * common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + // Make sure there isn't an embedded NUL character in the CN + if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) { + return false; + } + + // Compare expected hostname with the CN + return (strcasecmp(hostname, common_name_str) == 0); +} + +/** + * This code is derived from examples and documentation found ato00po + * http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp03/ssl/client.cpp + * and + * https://github.com/iSECPartners/ssl-conservatory + */ +bool verify_certificate(const char * hostname, bool preverified, boost::asio::ssl::verify_context& ctx) { + // The verify callback can be used to check whether the certificate that is + // being presented is valid for the peer. For example, RFC 2818 describes + // the steps involved in doing this for HTTPS. Consult the OpenSSL + // documentation for more details. Note that the callback is called once + // for each certificate in the certificate chain, starting from the root + // certificate authority. + + // Retrieve the depth of the current cert in the chain. 0 indicates the + // actual server cert, upon which we will perform extra validation + // (specifically, ensuring that the hostname matches. For other certs we + // will use the 'preverified' flag from Asio, which incorporates a number of + // non-implementation specific OpenSSL checking, such as the formatting of + // certs and the trusted status based on the CA certs we imported earlier. + int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle()); + + // if we are on the final cert and everything else checks out, ensure that + // the hostname is present on the list of SANs or the common name (CN). + if (depth == 0 && preverified) { + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + + if (verify_subject_alternative_name(hostname, cert)) { + return true; + } else if (verify_common_name(hostname, cert)) { + return true; + } else { + return false; + } + } + + return preverified; +} + +/// TLS Initialization handler +/** + * WebSocket++ core and the Asio Transport do not handle TLS context creation + * and setup. This callback is provided so that the end user can set up their + * TLS context using whatever settings make sense for their application. + * + * As Asio and OpenSSL do not provide great documentation for the very common + * case of connect and actually perform basic verification of server certs this + * example includes a basic implementation (using Asio and OpenSSL) of the + * following reasonable default settings and verification steps: + * + * - Disable SSLv2 and SSLv3 + * - Load trusted CA certificates and verify the server cert is trusted. + * - Verify that the hostname matches either the common name or one of the + * subject alternative names on the certificate. + * + * This is not meant to be an exhaustive reference implimentation of a perfect + * TLS client, but rather a reasonable starting point for building a secure + * TLS encrypted WebSocket client. + * + * If any TLS, Asio, or OpenSSL experts feel that these settings are poor + * defaults or there are critically missing steps please open a GitHub issue + * or drop a line on the project mailing list. + * + * Note the bundled CA cert ca-chain.cert.pem is the CA cert that signed the + * cert bundled with echo_server_tls. You can use print_client_tls with this + * CA cert to connect to echo_server_tls as long as you use /etc/hosts or + * something equivilent to spoof one of the names on that cert + * (websocketpp.org, for example). + */ +context_ptr on_tls_init(const char * hostname, websocketpp::connection_hdl) { + context_ptr ctx = websocketpp::lib::make_shared(boost::asio::ssl::context::sslv23); + + try { + ctx->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::single_dh_use); + + + ctx->set_verify_mode(boost::asio::ssl::verify_peer); + ctx->set_verify_callback(bind(&verify_certificate, hostname, ::_1, ::_2)); + + // Here we load the CA certificates of all CA's that this client trusts. + ctx->load_verify_file("ca-chain.cert.pem"); + } catch (std::exception& e) { + std::cout << e.what() << std::endl; + } + return ctx; +} + +int main(int argc, char* argv[]) { + client c; + + std::string hostname = "localhost"; + std::string port = "9002"; + + + if (argc == 3) { + hostname = argv[1]; + port = argv[2]; + } else { + std::cout << "Usage: print_server_tls " << std::endl; + return 1; + } + + std::string uri = "wss://" + hostname + ":" + port; + + try { + // Set logging to be pretty verbose (everything except message payloads) + c.set_access_channels(websocketpp::log::alevel::all); + c.clear_access_channels(websocketpp::log::alevel::frame_payload); + c.set_error_channels(websocketpp::log::elevel::all); + + // Initialize ASIO + c.init_asio(); + + // Register our message handler + c.set_message_handler(&on_message); + c.set_tls_init_handler(bind(&on_tls_init, hostname.c_str(), ::_1)); + + websocketpp::lib::error_code ec; + client::connection_ptr con = c.get_connection(uri, ec); + if (ec) { + std::cout << "could not create connection because: " << ec.message() << std::endl; + return 0; + } + + // Note that connect here only requests a connection. No network messages are + // exchanged until the event loop starts running in the next line. + c.connect(con); + + c.get_alog().write(websocketpp::log::alevel::app, "Connecting to " + uri); + + // Start the ASIO io_service run loop + // this will cause a single connection to be made to the server. c.run() + // will exit when this connection is closed. + c.run(); + } catch (websocketpp::exception const & e) { + std::cout << e.what() << std::endl; + } +}