From daf3620c8765b73fcd28d06a9a2041286f073ba5 Mon Sep 17 00:00:00 2001 From: Yuriy Vountesmery Date: Thu, 29 Aug 2024 21:52:48 +0000 Subject: [PATCH] TLS implementation and basic tests --- nngd/nngd.d | 164 +++++++++++++++++++++++++++----- tests/ssl/certkey.pem | 51 ++++++++++ tests/stop/test_10_tls.d | 93 ------------------ tests/test_10_tls.d | 197 +++++++++++++++++++++++++++++++++++++++ tests/test_15_webtls.d | 154 ++++++++++++++++++++++++++++++ 5 files changed, 544 insertions(+), 115 deletions(-) create mode 100644 tests/ssl/certkey.pem delete mode 100644 tests/stop/test_10_tls.d create mode 100644 tests/test_10_tls.d create mode 100644 tests/test_15_webtls.d diff --git a/nngd/nngd.d b/nngd/nngd.d index 8c4ed2f82..c03b61a21 100644 --- a/nngd/nngd.d +++ b/nngd/nngd.d @@ -665,6 +665,21 @@ struct NNGSocket { return -1; } } + + version(withtls) { + int listener_set_tls ( NNGTLS* tls ) { + if(tls.mode == nng_tls_mode.NNG_TLS_MODE_SERVER){ + auto rc = nng_listener_set_ptr(m_listener, toStringz(NNG_OPT_TLS_CONFIG), tls.tls); + if(rc != 0){ + m_errno = cast(nng_errno)rc; + return rc; + } + return 0; + } else { + return -1; + } + } + } int listener_start( const bool nonblock = false ) @safe { m_errno = cast(nng_errno)0; @@ -750,6 +765,21 @@ struct NNGSocket { return -1; } } + + version(withtls) { + int dialer_set_tls ( NNGTLS* tls ) { + if(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT){ + auto rc = nng_dialer_set_ptr(m_dialer, toStringz(NNG_OPT_TLS_CONFIG), tls.tls); + if(rc != 0){ + m_errno = cast(nng_errno)rc; + return rc; + } + return 0; + } else { + return -1; + } + } + } int dialer_start( const bool nonblock = false ) @safe nothrow { m_errno = cast(nng_errno)0; @@ -1409,40 +1439,53 @@ version(withtls) { alias nng_tls_auth_mode = libnng.nng_tls_auth_mode; alias nng_tls_version = libnng.nng_tls_version; - struct WebTLS { + struct NNGTLS { nng_tls_config* tls; + nng_tls_mode _mode; @disable this(); - this(ref return scope WebTLS rhs) {} + this(ref return scope NNGTLS rhs) {} this( nng_tls_mode imode ) { int rc; + _mode = imode; rc = nng_tls_config_alloc(&tls, imode); enforce(rc == 0, "TLS config init"); nng_tls_config_hold(tls); } - ~this() { nng_tls_config_free(tls); } void set_server_name ( string iname ) { - auto rc = nng_tls_config_server_name(tls, iname.toStringz()); + enforce(_mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + auto rc = nng_tls_config_server_name(tls, toStringz(iname)); enforce(rc == 0); } void set_ca_chain ( string pem, string crl = "" ) { - auto rc = nng_tls_config_ca_chain(tls, pem.toStringz(), crl.toStringz()); + auto rc = nng_tls_config_ca_chain(tls, toStringz(pem), crl == "" ? null : toStringz(crl)); enforce(rc == 0); } + + void set_ca_chain_file_load( string filename, string crl = "" ) { + string ca = std.file.readText(filename); + set_ca_chain ( ca, crl ); + } void set_own_cert ( string pem, string key, string pwd = "" ) { - auto rc = nng_tls_config_own_cert(tls, pem.toStringz(), key.toStringz(), pwd.toStringz()); + auto rc = nng_tls_config_own_cert(tls, pem.toStringz(), toStringz(key), pwd == "" ? null : toStringz(pwd)); enforce(rc == 0); } + void set_own_cert_load ( string pemfilename, string keyfilename, string pwd = "" ){ + string pem = std.file.readText(pemfilename); + string key = std.file.readText(keyfilename); + set_own_cert ( pem, key, pwd ); + } + // TODO: check why this two excluded from the lib /* void set_pass ( string ipass ) { @@ -1456,21 +1499,20 @@ version(withtls) { } */ - void set_auth_mode ( nng_tls_auth_mode imode ) { - auto rc = nng_tls_config_auth_mode(tls, imode); - enforce(rc == 0); - } - - void set_ca_file ( string ica ) { - auto rc = nng_tls_config_ca_file(tls, ica.toStringz()); + void set_ca_file ( string icafile ) { + auto rc = nng_tls_config_ca_file(tls, toStringz(icafile)); enforce(rc == 0); } - void set_cert_key_file ( string ipem , string ikey ) { - auto rc = nng_tls_config_cert_key_file(tls, ipem.toStringz(), ikey.toStringz()); - writeln("TDEBUG: ", nng_errstr(rc)); + void set_cert_key_file ( string ipemkeyfile, string ipass ) { + auto rc = nng_tls_config_cert_key_file(tls, toStringz(ipemkeyfile), toStringz(ipass)); // pemkey file should contain both cert and key delimited with \r\n enforce(rc == 0); } + + void set_auth_mode ( nng_tls_auth_mode imode ) { + auto rc = nng_tls_config_auth_mode(tls, imode); + enforce(rc == 0); + } void set_version( nng_tls_version iminversion, nng_tls_version imaxversion ) { auto rc = nng_tls_config_version(tls, iminversion, imaxversion); @@ -1490,6 +1532,21 @@ version(withtls) { bool fips_mode() { return nng_tls_engine_fips_mode(); } + + nng_tls_mode mode() { + return _mode; + } + + string toString(){ + return "\r\n------------------------\r\n" + ~format("engine name: %s\r\n", engine_name) + ~format("engine description: %s\r\n", engine_description) + ~format("FIPS: %s\r\n", fips_mode) + ~format("mode: %s\r\n", mode) + ~"------------------------------\r\n" + ; + } + } } @@ -1995,7 +2052,8 @@ struct WebApp { } version(withtls) { - void set_tls ( WebTLS tls ) { + void set_tls ( NNGTLS* tls ) { + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_SERVER); auto rc = nng_http_server_set_tls(server, tls.tls); enforce(rc==0, "server set tls"); } @@ -2239,8 +2297,16 @@ struct WebClient { nng_http_res_free(res); } + version(withtls) { + void set_tls ( NNGTLS* tls ) { + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + auto rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } + // static sync get - static WebData get ( string uri, string[string] headers, Duration timeout = 30000.msecs ) { + static WebData get ( string uri, string[string] headers, Duration timeout = 30000.msecs, void* ptls = null ) { int rc; nng_http_client *cli; nng_url *url; @@ -2259,6 +2325,15 @@ struct WebClient { rc = nng_aio_alloc(&aio, null, null); enforce(rc == 0); nng_aio_set_timeout(aio, cast(nng_duration)timeout.total!"msecs"); + + version(withtls) { + if(ptls) { + NNGTLS *tls = cast(NNGTLS*) ptls; + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } scope(exit) { nng_http_client_free(cli); @@ -2287,7 +2362,8 @@ struct WebClient { } // static sync post - static WebData post ( string uri, const ubyte[] data, const string[string] headers, Duration timeout = 30000.msecs ) { + static WebData post ( string uri, const ubyte[] data, const string[string] headers, Duration timeout = 30000.msecs, void *ptls = null ) + { int rc; nng_http_client *cli; nng_url *url; @@ -2306,6 +2382,16 @@ struct WebClient { rc = nng_aio_alloc(&aio, null, null); enforce(rc == 0); nng_aio_set_timeout(aio, cast(nng_duration)timeout.total!"msecs"); + + version(withtls) { + if(ptls) { + NNGTLS *tls = cast(NNGTLS*) ptls; + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } + scope(exit) { nng_http_client_free(cli); nng_url_free(url); @@ -2335,7 +2421,8 @@ struct WebClient { } // static async get - static NNGAio get_async ( string uri, const string[string] headers, const webclienthandler handler, Duration timeout = 30000.msecs, void *context = null ) { + static NNGAio get_async ( string uri, const string[string] headers, const webclienthandler handler, Duration timeout = 30000.msecs, void *context = null, void *ptls = null ) + { int rc; nng_aio *aio; nng_http_client *cli; @@ -2352,6 +2439,17 @@ struct WebClient { enforce(rc == 0); rc = nng_aio_alloc(&aio, null, null); enforce(rc == 0); + + version(withtls) { + if(ptls) { + NNGTLS *tls = cast(NNGTLS*) ptls; + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } + + nng_aio_set_timeout(aio, cast(nng_duration)timeout.total!"msecs"); WebClientAsync *a = new WebClientAsync(); a.uri = cast(char*)uri.dup.toStringz(); @@ -2373,7 +2471,8 @@ struct WebClient { } // static async post - static NNGAio post_async ( string uri, const ubyte[] data, const string[string] headers, const webclienthandler handler, Duration timeout = 30000.msecs, void *context = null ) { + static NNGAio post_async ( string uri, const ubyte[] data, const string[string] headers, const webclienthandler handler, Duration timeout = 30000.msecs, void *context = null, void *ptls = null ) + { int rc; nng_aio *aio; nng_http_client *cli; @@ -2390,6 +2489,16 @@ struct WebClient { enforce(rc == 0); rc = nng_aio_alloc(&aio, null, null); enforce(rc == 0); + + version(withtls) { + if(ptls) { + NNGTLS *tls = cast(NNGTLS*) ptls; + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } + nng_aio_set_timeout(aio, cast(nng_duration)timeout.total!"msecs"); WebClientAsync *a = new WebClientAsync(); a.uri = cast(char*)uri.dup.toStringz(); @@ -2424,7 +2533,8 @@ struct WebClient { webclienthandler onsuccess, webclienthandler onerror, Duration timeout = 30000.msecs, - void *context = null ) + void *context = null, + void *ptls = null ) { int rc; nng_aio *aio; @@ -2442,6 +2552,16 @@ struct WebClient { enforce(rc == 0); rc = nng_aio_alloc(&aio, null, null); enforce(rc == 0); + + version(withtls) { + if(ptls) { + NNGTLS *tls = cast(NNGTLS*) ptls; + enforce(tls.mode == nng_tls_mode.NNG_TLS_MODE_CLIENT); + rc = nng_http_client_set_tls(cli, tls.tls); + enforce(rc==0, "client set tls"); + } + } + nng_aio_set_timeout(aio, cast(nng_duration)timeout.total!"msecs"); WebClientAsync *a = new WebClientAsync(); a.uri = cast(char*)uri.dup.toStringz(); diff --git a/tests/ssl/certkey.pem b/tests/ssl/certkey.pem new file mode 100644 index 000000000..f6d63d83c --- /dev/null +++ b/tests/ssl/certkey.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUfEYJ3h6+SldFrjkyJSeo36AoY4YwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIzMTAyMjIzNDExN1oXDTI0MTAyMTIzNDExN1owWTELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAnW4FjuDs4ArONUyGAvgVniYETptQOzvmOdY9RLMOyieUhee1FaML +UMUY8lkUTwTMwbjPALyOxhHrRltDoSuDdhOsBkQFNDZ58x/vBEc5zIOGtofDsPdy +EVznul0wFrb/8gHwDXSoewvWzhKSlDBzu0sqCa1QmwuIpjr9JuRXP0KNtphw6rju +iNE1P0x7lb7uZCjvOxzhMINc4aAlc4ZQ6IOgfVcadvXESRT35w73Q3tz4pmyXfHC +1NCWSe0hfeN2xZh0DtlvTni4dW/XBjT+TAbW1wOqw6T2H/RUr4cpmc6lI2x/+xRh +qBfbevj3lUDCfMU0Y0mM27ZFXu8eydwTGQIDAQABo1MwUTAdBgNVHQ4EFgQULuKO +TaxsGGzgjAGmkiXwbzmxSsUwHwYDVR0jBBgwFoAULuKOTaxsGGzgjAGmkiXwbzmx +SsUwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAaqidX9YdiCmK +0wbqfsW3A6GVq4xIfhQE711GaiWCJQE2dUFnf6OQbkr7cpuB9DF/q4IVb86pjUj/ +kEWYvyZEj0jwemVZy+tjK4vmgKtl8n856xobTj3U+mdczzk8/wn80n7YyTmGoyQ5 +hyuxgMfkNrqwT5fjmC9eDnqLu7ZOFwscWschmpYjH7yaBVBZTe3ItRcOJIOTI1pA +0FfBXrmnoMb0uofYrM8WunvSOkCncqsxArb8reJ3uWZyWAsJIsw0x/VH5D446AxV +m9d5tTCycc8m+DpFxoEGrYlwWQCxCg2GGETSfr9WV3iInRyCPcil4BOXsR87eKx5 +O6WZ/9epkQ== +-----END CERTIFICATE----- + +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCdbgWO4OzgCs41 +TIYC+BWeJgROm1A7O+Y51j1Esw7KJ5SF57UVowtQxRjyWRRPBMzBuM8AvI7GEetG +W0OhK4N2E6wGRAU0NnnzH+8ERznMg4a2h8Ow93IRXOe6XTAWtv/yAfANdKh7C9bO +EpKUMHO7SyoJrVCbC4imOv0m5Fc/Qo22mHDquO6I0TU/THuVvu5kKO87HOEwg1zh +oCVzhlDog6B9Vxp29cRJFPfnDvdDe3PimbJd8cLU0JZJ7SF943bFmHQO2W9OeLh1 +b9cGNP5MBtbXA6rDpPYf9FSvhymZzqUjbH/7FGGoF9t6+PeVQMJ8xTRjSYzbtkVe +7x7J3BMZAgMBAAECggEAAIr6le2Mo5El+OUUqTn1yR/Ub/j1I98nAhgNCgmcCXJt +frC33SU8ysCDP4KzITqhAiIDBLuhumkhaJKz/wBJacTTmQnvAqkVbpMznc4pEt15 +P3PkZt3TlN+/JNNhcRKce1cqXsGCOzplOTyzKLOqEoFF2daxKne1tu0JJnMvIda1 +h/C61qwwyIiuuX8mxEOObhrFpiNONOi2ZaZ3b7VZPEPrkfgIcrcyHSs0rqrCiCCi +2stDM6OhezxgD7Dy4Z4q/nS6SsAVCDY+jjJV4CjM2w8sGXfbA0QRJkPuax3SSeyX +L1/bvHpGags7fYNYhfFgNaesXynv1sx9bXvkPM9eIQKBgQDJQ+rCe1X/RFj0d12y +giwWX/H56fzg5yGvj8kyrILQ1b0J8irlBaygJxbNxCLeXVAQx/V4LlNmcMjmfBsP +kNlDzOJRDaf2hedmvFSROv64N/fZLRePLGoD7t39Al/SwdDaksHjZHK4aCa+LAJj +WrSs/SadjYYanXBv9AKvBBeGpQKBgQDIPkbaJ/2an7G66g40N3SbO313rzG8FDvz +I404zy+9rckKNYifEonmjT3EVWUTXOrXShWBqQMJ2f9rw51Qt76+uZfWkcK1nlfQ +Fz9wRwxYXIW/xSi7K4eAinuDzJlJifgA+fZhTh+EpAYIEx0NGvHbsZfDMkyBRLJS +DItWDrXkZQKBgHxYc7AYVzdQ2Myg+siQ2AAy4uMOh2fEJPG8mgeuwVuY0iRU899v +NAn2XIZgSVKswAy+HZBUvr4prFWKE4X4beMPsDt3fnA2ppK4hF793eWe3ofU7htM +y2sGpyvrzZv+lrSTuypsItx/rIAN3KPZpIrEFJl+mH3VC2R/kzDiDE7pAoGANtXv +GzDWXzCaQEB2UVBaa2Q4ML1WVlYIMMYsl0ENptjfdB9C4aT3BJ7rKkfkXRAV840D +JLW+8kSzkLRJ8V/QKXSzovvZoVjSXVbwdXPPaqczrVd1lwQNoGLL/kTaOWxB8SqC +kRUOxokQaceqLfuR/gK9N6QflUrVtmhfA9sVbo0CgYEAkFlv+2QyZOO5IvX5AQIb +XCkh1feANoUztaUMQzW9HSF8IWpm002i+jt6KVhWqjvuSV7quE+m7sgruVK7izlt +K4N4tesmoY+wyFV/ZWHCA/fJosVBMZKQfNLIDxXFNqMpKeAxa6od7WxR+kQu29nO +Oe/HPc/aL0FrIF63svAb88s= +-----END PRIVATE KEY----- diff --git a/tests/stop/test_10_tls.d b/tests/stop/test_10_tls.d deleted file mode 100644 index 2fcec65b7..000000000 --- a/tests/stop/test_10_tls.d +++ /dev/null @@ -1,93 +0,0 @@ -import std.stdio; -import std.conv; -import std.string; -import std.concurrency; -import core.thread; -import std.datetime.systime; -import std.path; -import std.file; -import std.uuid; -import std.regex; -import std.json; -import std.exception; - -import nngd; -import nngtestutil; - -static void api_handler1 ( WebData *req, WebData *rep, void* ctx ){ - rep.text = "REPLY TO: "~to!string(req); - rep.type = "text/plain"; - rep.status = nng_http_status.NNG_HTTP_STATUS_OK; -} - -static void api_handler2 ( WebData *req, WebData *rep, void* ctx ){ - JSONValue data = parseJSON("{}"); - if(req.method == "GET"){ - data["replyto"] = "REPLY TO: "~to!string(req); - rep.json = data; - rep.type = "application/json"; - rep.status = nng_http_status.NNG_HTTP_STATUS_OK; - return; - } - if(req.method == "POST"){ - if(req.type == "application/octet-stream"){ - data["datalength"] = req.rawdata.length; - data["datatype"] = req.type; - rep.json = data, - rep.type = "application/json", - rep.status = nng_http_status.NNG_HTTP_STATUS_OK; - return; - }else{ - rep.status = nng_http_status.NNG_HTTP_STATUS_BAD_REQUEST; - rep.msg = "Invalid request"; - return; - } - } -} - - -int -main() -{ - int rc; - const wd = dirName(thisExePath()); - - WebApp app = WebApp("myapp", "https://localhost:8081", parseJSON(`{"root_path":"`~wd~`/../../webapp","static_path":"static"}`), null); - - version(withtls){ - WebTLS tls = WebTLS(nng_tls_mode.NNG_TLS_MODE_SERVER); - tls.set_server_name("localhost"); - tls.set_auth_mode(nng_tls_auth_mode.NNG_TLS_AUTH_MODE_NONE); - tls.set_version(nng_tls_version.NNG_TLS_1_0, nng_tls_version.NNG_TLS_1_3); - writeln(wd~"/../../ssl/cert.crt"); - tls.set_cert_key_file(wd~"/../../ssl/cert.crt", wd~"/../../ssl/key.key"); - app.set_tls(tls); - } - - app.route("/api/v1/test1",&api_handler1); - app.route("/api/v1/test2/*",&api_handler2,["GET","POST"]); - - app.start(); - - writeln(` - Consider tests: - - curl https://localhost:8081/api/v1/test1 - curl https://localhost:8081/api/v1/test2/a/b/c?x=y - curl -X POST -H "Content-Type: application/octet-stream" -d @file.bin https://localhost:8081/api/v1/test2 - - `); - - - while(true){ - nng_sleep(1000.msecs); - } - - log("...passed"); - - writeln("Bye!"); - return 0; -} - - - diff --git a/tests/test_10_tls.d b/tests/test_10_tls.d new file mode 100644 index 000000000..e0cd2eca5 --- /dev/null +++ b/tests/test_10_tls.d @@ -0,0 +1,197 @@ +import std.stdio; +import std.conv; +import std.string; +import std.concurrency; +import core.thread; +import std.datetime.systime; +import std.path; +import std.file; +import std.uuid; +import std.regex; +import std.json; +import std.exception; + +import nngd; +import nngtestutil; + +static const string cert = + "-----BEGIN CERTIFICATE-----\n" + ~"MIIDRzCCAi8CFCOIJGs6plMawgBYdDuCRV7UuJuyMA0GCSqGSIb3DQEBCwUAMF8x\n" + ~"CzAJBgNVBAYTAlhYMQ8wDQYDVQQIDAZVdG9waWExETAPBgNVBAcMCFBhcmFkaXNl\n" + ~"MRgwFgYDVQQKDA9OTkcgVGVzdHMsIEluYy4xEjAQBgNVBAMMCWxvY2FsaG9zdDAg\n" + ~"Fw0yMDA1MjMyMzMxMTlaGA8yMTIwMDQyOTIzMzExOVowXzELMAkGA1UEBhMCWFgx\n" + ~"DzANBgNVBAgMBlV0b3BpYTERMA8GA1UEBwwIUGFyYWRpc2UxGDAWBgNVBAoMD05O\n" + ~"RyBUZXN0cywgSW5jLjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B\n" + ~"AQEFAAOCAQ8AMIIBCgKCAQEAyPdnRbMrQj9902TGQsmMbG6xTSl9XKbJr55BcnyZ\n" + ~"ifsrqA7BbNSkndVw9Qq+OJQIDBTfRhGdG+o9j3h6SDVvIb62fWtwJ5Fe0eUmeYwP\n" + ~"c1PKQzOmMFlMYekXiZsx60yu5LeuUhGlb84+csImH+m3NbutInPJcStSq0WfSV6V\n" + ~"Nk6DN3535ex66zV2Ms6ikys1vCC434YqIpe1VxUh+IC2widJcLDCxmmJt3TOlx5f\n" + ~"9OcKMkxuH4fMAzgjIEpIrUjdb19CGNVvsNrEEB2CShBMgBdqMaAnKFxpKgfzS0JF\n" + ~"ulxRGNtpsrweki+j+a4sJXTv40kELkRQS6uB6wWZNjcPywIDAQABMA0GCSqGSIb3\n" + ~"DQEBCwUAA4IBAQA86Fqrd4aiih6R3fwiMLwV6IQJv+u5rQeqA4D0xu6v6siP42SJ\n" + ~"YMaI2DkNGrWdSFVSHUK/efceCrhnMlW7VM8I1cyl2F/qKMfnT72cxqqquiKtQKdT\n" + ~"NDTzv61QMUP9n86HxMzGS7jg0Pknu55BsIRNK6ndDvI3D/K/rzZs4xbqWSSfNfQs\n" + ~"fNFBbOuDrkS6/1h3p8SY1uPM18WLVv3GO2T3aeNMHn7YJAKSn+sfaxzAPyPIK3UT\n" + ~"W8ecGQSHOqBJJQELyUfMu7lx/FCYKUhN7/1uhU5Qf1pCR8hkIMegtqr64yVBNMOn\n" + ~"248fuiHbs9BRknuA/PqjxIDDZTwtDrfVSO/S\n" + ~"-----END CERTIFICATE-----\n"; + + static const string key = + "-----BEGIN RSA PRIVATE KEY-----\n" + ~"MIIEowIBAAKCAQEAyPdnRbMrQj9902TGQsmMbG6xTSl9XKbJr55BcnyZifsrqA7B\n" + ~"bNSkndVw9Qq+OJQIDBTfRhGdG+o9j3h6SDVvIb62fWtwJ5Fe0eUmeYwPc1PKQzOm\n" + ~"MFlMYekXiZsx60yu5LeuUhGlb84+csImH+m3NbutInPJcStSq0WfSV6VNk6DN353\n" + ~"5ex66zV2Ms6ikys1vCC434YqIpe1VxUh+IC2widJcLDCxmmJt3TOlx5f9OcKMkxu\n" + ~"H4fMAzgjIEpIrUjdb19CGNVvsNrEEB2CShBMgBdqMaAnKFxpKgfzS0JFulxRGNtp\n" + ~"srweki+j+a4sJXTv40kELkRQS6uB6wWZNjcPywIDAQABAoIBAQCGSUsot+BgFCzv\n" + ~"5JbWafb7Pbwb421xS8HZJ9Zzue6e1McHNVTqc+zLyqQAGX2iMMhvykKnf32L+anJ\n" + ~"BKgxOANaeSVYCUKYLfs+JfDfp0druMGexhR2mjT/99FSkfF5WXREQLiq/j+dxiLU\n" + ~"bActq+5QaWf3bYddp6VF7O/TBvCNqBfD0+S0o0wtBdvxXItrKPTD5iKr9JfLWdAt\n" + ~"YNAk2QgFywFtY5zc2wt4queghF9GHeBzzZCuVj9QvPA4WdVq0mePaPTmvTYQUD0j\n" + ~"GT6X5j9JhqCwfh7trb/HfkmLHwwc62zPDFps+Dxao80+vss5b/EYZ4zY3S/K3vpG\n" + ~"f/e42S2BAoGBAP51HQYFJGC/wsNtOcX8RtXnRo8eYmyboH6MtBFrZxWl6ERigKCN\n" + ~"5Tjni7EI3nwi3ONg0ENPFkoQ8h0bcVFS7iW5kz5te73WaOFtpkU9rmuFDUz37eLP\n" + ~"d+JLZ5Kwfn2FM9HoiSAZAHowE0MIlmmIEXSnFtqA2zzorPQLO/4QlR+VAoGBAMov\n" + ~"R0yaHg3qPlxmCNyLXKiGaGNzvsvWjYw825uCGmVZfhzDhOiCFMaMb51BS5Uw/gwm\n" + ~"zHxmJjoqak8JjxaQ1qKPoeY1TJ5ps1+TRq9Wzm2/zGqJHOXnRPlqwBQ6AFllAMgt\n" + ~"Rlp5uqb8QJ+YEo6/1kdGhw9kZWCZEEue6MNQjxnfAoGARLkUkZ+p54di7qz9QX+V\n" + ~"EghYgibOpk6R1hviNiIvwSUByhZgbvxjwC6pB7NBg31W8wIevU8K0g4plbrnq/Md\n" + ~"5opsPhwLo4XY5albkq/J/7f7k6ISWYN2+WMsIe4Q+42SJUsMXeLiwh1h1mTnWrEp\n" + ~"JbxK69CJZbXhoDe4iDGqVNECgYAjlgS3n9ywWE1XmAHxR3osk1OmRYYMfJv3VfLV\n" + ~"QSYCNqkyyNsIzXR4qdkvVYHHJZNhcibFsnkB/dsuRCFyOFX+0McPLMxqiXIv3U0w\n" + ~"qVe2C28gRTfX40fJmpdqN/c9xMBJe2aJoClRIM8DCBIkG/HMI8a719DcGrS6iqKv\n" + ~"VeuKAwKBgEgD+KWW1KtoSjCBlS0NP8HjC/Rq7j99YhKE6b9h2slIa7JTO8RZKCa0\n" + ~"qbuomdUeJA3R8h+5CFkEKWqO2/0+dUdLNOjG+CaTFHaUJevzHOzIjpn+VsfCLV13\n" + ~"yupGzHG+tGtdrWgLn9Dzdp67cDfSnsSh+KODPECAAFfo+wPvD8DS\n" + ~"-----END RSA PRIVATE KEY-----\n"; + + +const NSTEPS = 9; + +void sender_worker(string url) +{ + int k = 0; + string line; + int rc; + thread_attachThis(); + rt_moduleTlsCtor(); + NNGSocket s = NNGSocket(nng_socket_type.NNG_SOCKET_PUSH); + s.sendtimeout = msecs(1000); + s.sendbuf = 4096; + + rc = s.dialer_create(url); + if(rc != 0){ + error("Dialer create: %s", nng_errstr(rc)); + } + + NNGTLS tls = NNGTLS(nng_tls_mode.NNG_TLS_MODE_CLIENT); + tls.set_ca_chain(cert); + tls.set_server_name("localhost"); + tls.set_auth_mode(nng_tls_auth_mode.NNG_TLS_AUTH_MODE_NONE); + + log(tls.toString()); + + rc = s.dialer_set_tls(&tls); + if(rc != 0){ + error("Dialer set TLS: %s", nng_errstr(rc)); + } + + while(1){ + log("SS: to dial..."); + rc = s.dialer_start(); + if(rc == 0) break; + error("SS: Dial error: %s", nng_errstr(rc)); + if(rc == nng_errno.NNG_ECONNREFUSED){ + nng_sleep(msecs(100)); + continue; + } + enforce(rc == 0); + } + + log(nngtest_socket_properties(s,"sender")); + while(1){ + line = format(">MSG:%d DBL:%d TRL:%d<",k,k*2,k*3); + if(k > NSTEPS) line = "END"; + auto buf = cast(ubyte[])line.dup; + rc = s.send!(ubyte[])(buf); + enforce(rc == 0); + log("SS sent: ",k," : ",line); + k++; + nng_sleep(msecs(500)); + if(k > NSTEPS + 1) break; + } + log(" SS: bye!"); +} + + +void receiver_worker(string url) +{ + int rc; + ubyte[4096] buf; + size_t sz = buf.length; + thread_attachThis(); + rt_moduleTlsCtor(); + NNGSocket s = NNGSocket(nng_socket_type.NNG_SOCKET_PULL); + s.recvtimeout = msecs(1000); + + rc = s.listener_create(url); + if(rc != 0){ + error("Listener create: %s", rc); + } + + NNGTLS tls = NNGTLS(nng_tls_mode.NNG_TLS_MODE_SERVER); + tls.set_auth_mode(nng_tls_auth_mode.NNG_TLS_AUTH_MODE_NONE); + tls.set_own_cert(cert, key); + + log(tls.toString()); + + rc = s.listener_set_tls(&tls); + if(rc != 0){ + error("Listener set TLS: %s", rc); + } + + rc = s.listener_start(); + log("RR: listening"); + enforce(rc == 0); + bool _ok = false; + int k = 0; + log(nngtest_socket_properties(s,"receiver")); + while(1){ + if(k++ > NSTEPS + 3) break; + sz = s.receivebuf(buf, buf.length); + if(sz < 0 || sz == size_t.max){ + error("REcv error: " ~ toString(s.m_errno)); + continue; + } + auto str = cast(string)buf[0..sz]; + log("RR: GOT["~(to!string(sz))~"]: >"~str~"<"); + if(str == "END"){ + _ok = true; + break; + } + } + if(!_ok){ + error("Test stopped without normal end."); + } + log(" RR: bye!"); +} + + +int main() +{ + log(">>> %s ", (toStringz("") == null)); + log("Hello NNGD!"); + log("Simple push-pull test with byte buffers"); + + string uri = "tls+tcp://127.0.0.1:31201"; + + auto tid01 = spawn(&receiver_worker, uri); + Thread.sleep(100.msecs); + auto tid02 = spawn(&sender_worker, uri); + thread_joinAll(); + + return populate_state(10, "TLS encrypted push-pull socket pair"); +} + + + diff --git a/tests/test_15_webtls.d b/tests/test_15_webtls.d new file mode 100644 index 000000000..bda0eaf7a --- /dev/null +++ b/tests/test_15_webtls.d @@ -0,0 +1,154 @@ +import std.stdio; +import std.conv; +import std.string; +import std.concurrency; +import core.thread; +import std.datetime.systime; +import std.path; +import std.file; +import std.uuid; +import std.regex; +import std.json; +import std.exception; +import std.process; + +import nngd; +import nngtestutil; + +static void api_handler1 ( WebData *req, WebData *rep, void* ctx ){ + try{ + thread_attachThis(); + rep.text = to!string((*req).toJSON("handler1")); + rep.type = "text/plain"; + rep.status = nng_http_status.NNG_HTTP_STATUS_OK; + } catch(Throwable e) { + error(dump_exception_recursive(e, "Handler - 1")); + } +} + +static void api_handler2 ( WebData *req, WebData *rep, void* ctx ){ + JSONValue data = parseJSON("{}"); + if(req.method == "GET"){ + data["replyto"] = (*req).toJSON("handler2"); + rep.json = data; + rep.type = "application/json"; + rep.status = nng_http_status.NNG_HTTP_STATUS_OK; + return; + } + if(req.method == "POST"){ + if(req.type == "application/octet-stream"){ + data["datalength"] = req.rawdata.length; + data["datatype"] = req.type; + rep.json = data, + rep.type = "application/json", + rep.status = nng_http_status.NNG_HTTP_STATUS_OK; + return; + }else{ + rep.status = nng_http_status.NNG_HTTP_STATUS_BAD_REQUEST; + rep.msg = "Invalid request"; + return; + } + } +} + + +int +main() +{ + version(withtls){ + int rc; + const wd = dirName(thisExePath()); + + NNGTLS tls = NNGTLS(nng_tls_mode.NNG_TLS_MODE_SERVER); + + try { + + //tls.set_server_name("localhost"); + tls.set_auth_mode(nng_tls_auth_mode.NNG_TLS_AUTH_MODE_NONE); + try { tls.set_version(nng_tls_version.NNG_TLS_1_0, nng_tls_version.NNG_TLS_1_0); log("TLS ver 1.0 1.0 ok"); } catch(Throwable e) { log("TLS ver 1.0 1.0 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_0, nng_tls_version.NNG_TLS_1_1); log("TLS ver 1.0 1.1 ok"); } catch(Throwable e) { log("TLS ver 1.0 1.1 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_0, nng_tls_version.NNG_TLS_1_2); log("TLS ver 1.0 1.2 ok"); } catch(Throwable e) { log("TLS ver 1.0 1.2 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_0, nng_tls_version.NNG_TLS_1_3); log("TLS ver 1.0 1.3 ok"); } catch(Throwable e) { log("TLS ver 1.0 1.3 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_1, nng_tls_version.NNG_TLS_1_1); log("TLS ver 1.1 1.1 ok"); } catch(Throwable e) { log("TLS ver 1.1 1.1 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_1, nng_tls_version.NNG_TLS_1_2); log("TLS ver 1.1 1.2 ok"); } catch(Throwable e) { log("TLS ver 1.1 1.2 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_1, nng_tls_version.NNG_TLS_1_3); log("TLS ver 1.1 1.3 ok"); } catch(Throwable e) { log("TLS ver 1.1 1.3 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_2, nng_tls_version.NNG_TLS_1_2); log("TLS ver 1.2 1.2 ok"); } catch(Throwable e) { log("TLS ver 2.1 1.2 FAIL"); } + try { tls.set_version(nng_tls_version.NNG_TLS_1_2, nng_tls_version.NNG_TLS_1_3); log("TLS ver 1.2 1.3 ok"); } catch(Throwable e) { log("TLS ver 2.1 1.3 FAIL"); } + + tls.set_cert_key_file(wd~"/../../ssl/certkey.pem", null); + + } catch(Throwable e) { + error(dump_exception_recursive(e, "TLS config")); + } + + try { + + WebApp app = WebApp("myapp", "https://localhost:8089", parseJSON(`{"root_path":"`~wd~`/../../../webapp","static_path":"static"}`), null); + app.set_tls(&tls); + + app.route("/api/v1/test1/*",&api_handler1); + app.route("/api/v1/test2/*",&api_handler2,["GET","POST"]); + + app.start(); + + } catch(Throwable e) { + error(dump_exception_recursive(e, "Server start")); + } + + log(` + Consider tests: + + curl -k https://localhost:8089/api/v1/test1 + curl -k https://localhost:8089/api/v1/test2/a/b/c?x=y + curl -k -X POST -H "Content-Type: application/octet-stream" -d @file.bin https://localhost:8089/api/v1/test2 + + `); + + try { + + { + auto res = executeShell("curl -k -s https://localhost:8089/static | grep -q 'NNG HTTP TEST'"); + assert(res.status == 0, "on static file"); + } + + { + auto res = executeShell("curl -k -s https://localhost:8089/api/v1/test1/a/b/c/?x=y"); + assert(res.status == 0); + auto jres = parseJSON(res.output); + assert(jres["#TAG"].str == "handler1"); + assert(jres["path"][2].str == "test1" ); + assert(jres["param"]["x"].str == "y" ); + } + + { + auto res = executeShell("curl -k -s https://localhost:8089/api/v1/test2/a/b/c/?x=y"); + assert(res.status == 0); + auto jres = parseJSON(res.output); + assert(jres["replyto"]["#TAG"].str == "handler2"); + assert(jres["replyto"]["path"][2].str == "test2" ); + assert(jres["replyto"]["param"]["x"].str == "y" ); + } + + { + auto drc = executeShell("dd if=/dev/urandom of=file.bin count=1 bs=1048576"); + assert(drc.status == 0); + auto res = executeShell("curl -k -s -X POST -H \"Content-Type: application/octet-stream\" --data-bin @file.bin https://localhost:8089/api/v1/test2"); + assert(res.status == 0); + auto jres = parseJSON(res.output); + assert(jres["datalength"].integer == 1048576); + assert(jres["datatype"].str == "application/octet-stream" ); + } + + } catch(Throwable e) { + error(dump_exception_recursive(e, "Endpoint tests")); + } + + } else { + log("TLS test to skip..."); + } + + return populate_state(15, "TLS webserver tests"); +} + + +