diff options
| -rw-r--r-- | docs/libnng.adoc | 8 | ||||
| -rw-r--r-- | docs/nng_tls_config_auth_mode.adoc | 4 | ||||
| -rw-r--r-- | docs/nng_tls_config_ca_chain.adoc | 12 | ||||
| -rw-r--r-- | docs/nng_tls_config_ca_file.adoc | 78 | ||||
| -rw-r--r-- | docs/nng_tls_config_cert_key_file.adoc | 83 | ||||
| -rw-r--r-- | docs/nng_tls_config_own_cert.adoc | 1 | ||||
| -rw-r--r-- | docs/nng_ws.adoc | 42 | ||||
| -rw-r--r-- | src/nng.c | 24 | ||||
| -rw-r--r-- | src/nng.h | 22 | ||||
| -rw-r--r-- | src/supplemental/http/client.c | 115 | ||||
| -rw-r--r-- | src/supplemental/http/http.h | 37 | ||||
| -rw-r--r-- | src/supplemental/http/server.c | 146 | ||||
| -rw-r--r-- | src/supplemental/tls/mbedtls/tls.c | 132 | ||||
| -rw-r--r-- | src/supplemental/tls/tls.h | 6 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.c | 329 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.h | 6 | ||||
| -rw-r--r-- | src/transport/ws/websocket.c | 162 | ||||
| -rw-r--r-- | src/transport/ws/websocket.h | 42 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | tests/httpclient.c | 14 | ||||
| -rw-r--r-- | tests/httpserver.c | 11 | ||||
| -rw-r--r-- | tests/trantest.h | 1 | ||||
| -rw-r--r-- | tests/ws.c | 8 | ||||
| -rw-r--r-- | tests/wss.c | 143 | ||||
| -rw-r--r-- | tests/wssfile.c | 329 |
25 files changed, 1386 insertions, 370 deletions
diff --git a/docs/libnng.adoc b/docs/libnng.adoc index d232dfe3..e5fd24ec 100644 --- a/docs/libnng.adoc +++ b/docs/libnng.adoc @@ -161,10 +161,12 @@ The following functions are used to manipulate TLS configuration objects. [cols="1,4"] |=== -| <<nng_tls_config_auth_alloc#nng_tls_config_alloc(3)>>|allocate TLS configuration -| <<nng_tls_config_auth_mode#nng_tls_config_auth_mode(3)>>|set authentication mode +| <<nng_tls_config_auth_alloc#,nng_tls_config_alloc(3)>>|allocate TLS configuration +| <<nng_tls_config_auth_mode#,nng_tls_config_auth_mode(3)>>|set authentication mode | <<nng_tls_config_ca_chain#,nng_tls_config_ca_chain(3)>>|set certificate authority chain -| <<nng_tls_config_own_cert#nng_tls_config_own)cert(3)>>|set own certificate and key +| <<nng_tls_config_ca_file#,nng_tls_config_ca_file(3)>>|load certificate authority from file +| <<nng_tls_config_cert_key_file#,nng_tls_config_cert_key_file_cert(3)>>|load own certificate and key from file +| <<nng_tls_config_own_cert#,nng_tls_config_own_cert(3)>>|set own certificate and key | <<nng_tls_config_free#,nng_tls_config_free(3)>>}free TLS configuration | <<nng_tls_config_server_name#,nng_tls_config_server_name(3)>>|set remote server name |=== diff --git a/docs/nng_tls_config_auth_mode.adoc b/docs/nng_tls_config_auth_mode.adoc index fc4f9cfd..e07a6a41 100644 --- a/docs/nng_tls_config_auth_mode.adoc +++ b/docs/nng_tls_config_auth_mode.adoc @@ -73,8 +73,8 @@ SEE ALSO <<nng_strerror#,nng_strerror(3)>>, <<nng_tls_config_alloc#,nng_tls_config_alloc(3)>>, -<<nng_tls_config_ca_cert#,nng_tls_config_ca_cert(3)>>, -<<nng_tls_config_crl#,nng_tls_config_crl(3)>>, +<<nng_tls_config_ca_chain#,nng_tls_config_ca_chain(3)>>, +<<nng_tls_config_ca_file#,nng_tls_config_ca_file(3)>>, <<nng_tls_config_server_name#,nng_tls_config_server_name(3)>>, <<nng#,nng(7)>> diff --git a/docs/nng_tls_config_ca_chain.adoc b/docs/nng_tls_config_ca_chain.adoc index 2888c032..dcf29a65 100644 --- a/docs/nng_tls_config_ca_chain.adoc +++ b/docs/nng_tls_config_ca_chain.adoc @@ -25,24 +25,23 @@ SYNOPSIS #include <nng/nng.h> int nng_tls_config_ca_cert(nni_tls_config *cfg, const char *chain, - const char *crl) + const char *crl); ----------- DESCRIPTION ----------- The `nng_tls_config_ca_chain()` function configures a certificate or -certificate chain to be used when validating peers using the configuragion +certificate chain to be used when validating peers using the configuration 'cfg'. -NOTE: This function *must* be called when the TLS authentication mode SYNOPSIS -`NNG_TLS_AUTH_MODE_REQUIRED` or `NNG_TLS_AUTH_MODE_OPTIONAL`. It will have -no effect if the authentication mode is `NNG_TLS_AUTH_MODE_NONE`. +NOTE: Certificates *must* be configured when using the authentication mode +`NNG_TLS_AUTH_MODE_REQUIRED`. TIP: This function may be called multiple times, to add additional chains to a configuration, without affecting those added previously. -The certificates located in 'chain' must be a NUL terminated C string in +The certificates located in 'chain' must be a zero-terminated C string in https://tools.ietf.org/html/rfc7468[PEM] format. Multiple certificates may appear concatenated together, with the leaf certificate listed first. together. @@ -68,6 +67,7 @@ SEE ALSO <<nng_strerror#,nng_strerror(3)>>, <<nng_tls_config_alloc#,nng_tls_config_alloc(3)>>, <<nng_tls_config_auth_mode#,nng_tls_config_auth_mode(3)>>, +<<nng_tls_config_ca_file#,nng_tls_config_ca_file(3)>>, <<nng#,nng(7)>> diff --git a/docs/nng_tls_config_ca_file.adoc b/docs/nng_tls_config_ca_file.adoc new file mode 100644 index 00000000..32679fc1 --- /dev/null +++ b/docs/nng_tls_config_ca_file.adoc @@ -0,0 +1,78 @@ +nng_tls_config_ca_file(3) +========================= +:doctype: manpage +:manmanual: nng +:mansource: nng +:manvolnum: 3 +:icons: font +:source-highlighter: pygments +:copyright: Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> \ + Copyright 2018 Capitar IT Group BV <info@capitar.com> \ + This software is supplied under the terms of the MIT License, a \ + copy of which should be located in the distribution where this \ + file was obtained (LICENSE.txt). A copy of the license may also \ + be found online at https://opensource.org/licenses/MIT. + +NAME +---- +nng_tls_config_ca_file - load certificate authority from file + +SYNOPSIS +-------- + +[source, c] +----------- +#include <nng/nng.h> + +int nng_tls_config_ca_file(nni_tls_config *cfg, const char *path); +----------- + +DESCRIPTION +----------- + +The `nng_tls_config_ca_file()` function configures the certificate authority +certificate chain and optional revocation list by loading the certificates +(and revocation list if present) from a single named file. The file must +at least one X.509 certificate in https://tools.ietf.org/html/rfc7468[PEM] +format, and may contain multiple such certificates, as well as zero or +more PEM CRL objects. This information is used to validate certificates +that are presented by peers, when using the configuration 'cfg'. + +NOTE: Certificates *must* be configured when using the authentication mode +`NNG_TLS_AUTH_MODE_REQUIRED`. + +TIP: This function may be called multiple times, to add additional chains +to a configuration, without affecting those added previously. + +RETURN VALUES +------------- + +This function returns 0 on success, and non-zero otherwise. + +ERRORS +------ + +`NNG_ENOMEM`:: Insufficient memory is available. +`NNG_EBUSY`:: The configuration 'cfg' is already in use, and cannot be modified. +`NNG_EINVAL`:: The contents of 'path' are invalid or did not contain a valid PEM certificate. +`NNG_ENOENT`:: The file 'path' does not exist. +`NNG_EPERM`:: The file 'path' is not readable. + +SEE ALSO +-------- + +<<nng_strerror#,nng_strerror(3)>>, +<<nng_tls_config_alloc#,nng_tls_config_alloc(3)>>, +<<nng_tls_config_auth_mode#,nng_tls_config_auth_mode(3)>>, +<<nng_tls_config_ca_chain#,nng_tls_config_ca_chain(3)>>, +<<nng#,nng(7)>> + + +COPYRIGHT +--------- + +Copyright 2018 mailto:info@staysail.tech[Staysail Systems, Inc.] + +Copyright 2018 mailto:info@capitar.com[Capitar IT Group BV] + +This document is supplied under the terms of the +https://opensource.org/licenses/MIT[MIT License]. diff --git a/docs/nng_tls_config_cert_key_file.adoc b/docs/nng_tls_config_cert_key_file.adoc new file mode 100644 index 00000000..9745d442 --- /dev/null +++ b/docs/nng_tls_config_cert_key_file.adoc @@ -0,0 +1,83 @@ +nng_tls_config_cert_key_file(3) +=============================== +:doctype: manpage +:manmanual: nng +:mansource: nng +:manvolnum: 3 +:icons: font +:source-highlighter: pygments +:copyright: Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> \ + Copyright 2018 Capitar IT Group BV <info@capitar.com> \ + This software is supplied under the terms of the MIT License, a \ + copy of which should be located in the distribution where this \ + file was obtained (LICENSE.txt). A copy of the license may also \ + be found online at https://opensource.org/licenses/MIT. + +NAME +---- +nng_tls_config_cert_key_file - load own certificate and key from file + +SYNOPSIS +-------- + +[source, c] +----------- +#include <nng/nng.h> + +int nng_tls_config_cert_key_file(nni_tls_config *cfg, const char *path, + const char *pass); +----------- + +DESCRIPTION +----------- + +The `nng_tls_config_cert_key_file()` function loads a certificate (or +certificate chain) and a private key from the file named by 'path'. + +The file must contain both the https://tools.ietf.org/html/rfc7468[PEM] +encoded certificate and associated private key, which will be used when +establishing TLS sessions using 'cfg'. It may contain additional certificates +leading to a validation chain, with the leaf certificate first. +There is no need to include the self-signed root, as the peer +will need to have that already in order to perform it's own validation. + +The private key may be encrypted with a password, in which can be supplied in +'pass'. The value NULL should be supplied for 'pass' if the key is not +encrypted. + +On servers, it is possible to call this function multiple times for the +same configuration. This can be useful for specifying different parameters +to be used for different cryptographic algorithms. + + +RETURN VALUES +------------- + +This function returns 0 on success, and non-zero otherwise. + +ERRORS +------ + +`NNG_ENOMEM`:: Insufficient memory is available. +`NNG_EBUSY`:: The configuration 'cfg' is already in use, and cannot be modified. +`NNG_EINVAL`:: The contents of 'path' are invalid. +`NNG_ENOENT`:: The file named by 'path' does not exist. +`NNG_EPERM`:: The file named by 'path' cannot be opened. + +SEE ALSO +-------- + +<<nng_strerror#,nng_strerror(3)>>, +<<nng_tls_config_alloc#,nng_tls_config_alloc(3)>>, +<<nng_tls_config_own_cert#,nng_tls_config_own_cert(3)>>, +<<nng#,nng(7)>> + + +COPYRIGHT +--------- + +Copyright 2018 mailto:info@staysail.tech[Staysail Systems, Inc.] + +Copyright 2018 mailto:info@capitar.com[Capitar IT Group BV] + +This document is supplied under the terms of the +https://opensource.org/licenses/MIT[MIT License]. diff --git a/docs/nng_tls_config_own_cert.adoc b/docs/nng_tls_config_own_cert.adoc index 485e44ca..dd8f7362 100644 --- a/docs/nng_tls_config_own_cert.adoc +++ b/docs/nng_tls_config_own_cert.adoc @@ -67,6 +67,7 @@ SEE ALSO <<nng_strerror#,nng_strerror(3)>>, <<nng_tls_config_alloc#,nng_tls_config_alloc(3)>>, +<<nng_tls_config_cert_key_file#,nng_tls_config_cert_key_file(3)>>, <<nng#,nng(7)>> diff --git a/docs/nng_ws.adoc b/docs/nng_ws.adoc index 0afb417e..d36062ab 100644 --- a/docs/nng_ws.adoc +++ b/docs/nng_ws.adoc @@ -81,16 +81,12 @@ usually.footnote:[This is a bug and will likely be fixed in the future.] NOTE: The value specified as the host, if any, will also be used in the `Host:` HTTP header during HTTP negotiation. -The special value of 0 (`INADDR_ANY`) can be used for a listener -to indicate that it should listen on all interfaces on the host. -A short-hand for this form is to either omit the address, or specify -the asterisk (`*`) character. For example, the following three -URIs are all equivalent, and could be used to listen to port 9999 -on the host: - - 1. `ws://0.0.0.0:9999` - 2. `ws://*:9999` - 3. `ws://:9999` +To listen to all ports on the system, the host name may be elided from +the URL on the listener. This will wind up listening to all interfaces +on the system, with possible caveats for IPv4 and IPv6 depending on what +the underlying system supports. (On most modern systems it will map to the +special IPv6 address `::`, and both IPv4 and IPv6 connections will be +permitted, with IPv4 addresses mapped to IPv6 addresses.) Socket Address ~~~~~~~~~~~~~~ @@ -159,6 +155,32 @@ the server is already running. Furthermore, attempts to modify the configuration object will fail if it is already in active use. This object is only available for `wss://` endpoints. +`NNG_OPT_WSS_TLS_CA_FILE`:: + +This is a write-only option used to load certificates associated +associated private key from a file. The value is a C string +containing the path name of the file. The file itself must contain +https://tools.ietf.org/html/rfc7468[PEM] format objects for one or more +X.509 certificates. It may also contain certificate revocation list (CRL) +objects well. Note that attempts to call this will fail if the +configuration associated with the underlying endpoint +is already in use. This option is only available for `wss://` endpoints. + +`NNG_OPT_WSS_TLS_CERT_KEY_FILE`:: + +This is a write-only option used to load the local certificate and +associated private key from a file. The value is a C string +containing the path name of the file. The file itself must contain PEM +format objects for the X.509 certificate and private key. Multiple +certificates may be listed in the file, to provide a validation chain, +with the leaf certificate listed first, and subsequent certificates listed +afterwards. Note that attempts to call this will fail if the +configuration associated with the underlying endpoint +is already in use. This option is only available for `wss://` endpoints. +The private key must not be encrypted. (Use the `NNG_OPT_WSS_TLS_CONFIG` +option to get the underlying TLS configuration if more advanced +configuration is needed.) + // We should also look at a hook mechanism for listeners. Probably this could // look like NNG_OPT_WS_LISTEN_HOOK_FUNC which would take a function pointer // along the lines of int hook(void *, char *req_headers, char **res_headers), @@ -1,6 +1,6 @@ // -// Copyright 2017 Garrett D'Amore <garrett@damore.org> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -411,6 +411,12 @@ nng_dialer_setopt_ptr(nng_dialer id, const char *name, void *val) } int +nng_dialer_setopt_string(nng_dialer id, const char *name, const char *val) +{ + return (nng_dialer_setopt(id, name, val, strlen(val) + 1)); +} + +int nng_dialer_getopt(nng_dialer id, const char *name, void *val, size_t *szp) { return (nng_ep_getopt(id, name, val, szp, NNI_EP_MODE_DIAL)); @@ -489,6 +495,12 @@ nng_listener_setopt_ptr(nng_listener id, const char *name, void *val) } int +nng_listener_setopt_string(nng_listener id, const char *name, const char *val) +{ + return (nng_listener_setopt(id, name, val, strlen(val) + 1)); +} + +int nng_listener_getopt(nng_listener id, const char *name, void *val, size_t *szp) { return (nng_ep_getopt(id, name, val, szp, NNI_EP_MODE_LISTEN)); @@ -620,6 +632,12 @@ nng_setopt_ptr(nng_socket sid, const char *name, void *val) } int +nng_setopt_string(nng_socket sid, const char *name, const char *val) +{ + return (nng_setopt(sid, name, val, strlen(val) + 1)); +} + +int nng_getopt_int(nng_socket sid, const char *name, int *valp) { size_t sz = sizeof(*valp); @@ -714,6 +732,8 @@ static const struct { { NNG_EEXIST, "Resource already exists" }, { NNG_EREADONLY, "Read only resource" }, { NNG_EWRITEONLY, "Write only resource" }, + { NNG_ECRYPTO, "Cryptographic error" }, + { NNG_EPEERAUTH, "Peer could not be authenticated" }, { NNG_EINTERNAL, "Internal error detected" }, { 0, NULL }, // clang-format on @@ -84,6 +84,7 @@ NNG_DECL int nng_setopt_int(nng_socket, const char *, int); NNG_DECL int nng_setopt_ms(nng_socket, const char *, nng_duration); NNG_DECL int nng_setopt_size(nng_socket, const char *, size_t); NNG_DECL int nng_setopt_uint64(nng_socket, const char *, uint64_t); +NNG_DECL int nng_setopt_string(nng_socket, const char *, const char *); // nng_socket_getopt obtains the option for a socket. NNG_DECL int nng_getopt(nng_socket, const char *, void *, size_t *); @@ -141,6 +142,7 @@ NNG_DECL int nng_dialer_setopt_ms(nng_dialer, const char *, nng_duration); NNG_DECL int nng_dialer_setopt_size(nng_dialer, const char *, size_t); NNG_DECL int nng_dialer_setopt_uint64(nng_dialer, const char *, uint64_t); NNG_DECL int nng_dialer_setopt_ptr(nng_dialer, const char *, void *); +NNG_DECL int nng_dialer_setopt_string(nng_dialer, const char *, const char *); // nng_dialer_getopt obtains the option for a dialer. This will // fail for options that a particular dialer is not interested in, @@ -163,6 +165,8 @@ NNG_DECL int nng_listener_setopt_ms(nng_listener, const char *, nng_duration); NNG_DECL int nng_listener_setopt_size(nng_listener, const char *, size_t); NNG_DECL int nng_listener_setopt_uint64(nng_listener, const char *, uint64_t); NNG_DECL int nng_listener_setopt_ptr(nng_listener, const char *, void *); +NNG_DECL int nng_listener_setopt_string( + nng_listener, const char *, const char *); // nng_listener_getopt obtains the option for a listener. This will // fail for options that a particular listener is not interested in, @@ -507,6 +511,8 @@ enum nng_errno_enum { NNG_EEXIST = 23, NNG_EREADONLY = 24, NNG_EWRITEONLY = 25, + NNG_ECRYPTO = 26, + NNG_EPEERAUTH = 27, NNG_EINTERNAL = 1000, NNG_ESYSERR = 0x10000000, NNG_ETRANERR = 0x20000000, @@ -640,6 +646,22 @@ NNG_DECL int nng_tls_config_pass(nng_tls_config *, const char *); // practice. NNG_DECL int nng_tls_config_auth_mode(nng_tls_config *, nng_tls_auth_mode); +// nng_tls_config_ca_file is used to pass a CA chain and optional CRL +// via the filesystem. If CRL data is present, it must be contained +// in the file, along with the CA certificate data. The format is PEM. +// The path name must be a legal file name. +NNG_DECL int nng_tls_config_ca_file(nng_tls_config *, const char *); + +// nng_tls_config_cert_key_file is used to pass our own certificate and +// private key data via the filesystem. Both the key and certificate +// must be present as PEM blocks in the same file. A password is used to +// decrypt the private key if it is encrypted and the password supplied is not +// NULL. This may be called multiple times on servers, but only once on a +// client. (Servers can support multiple different certificates and keys for +// different cryptographic algorithms. Clients only get one.) +NNG_DECL int nng_tls_config_cert_key_file( + nng_tls_config *, const char *, const char *); + #ifdef __cplusplus } #endif diff --git a/src/supplemental/http/client.c b/src/supplemental/http/client.c index b1794f93..7542043d 100644 --- a/src/supplemental/http/client.c +++ b/src/supplemental/http/client.c @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -19,12 +19,12 @@ #include "http.h" struct nni_http_client { - nng_sockaddr addr; nni_list aios; nni_mtx mtx; bool closed; nng_tls_config * tls; nni_aio * connaio; + nni_url * url; nni_plat_tcp_ep *tep; }; @@ -92,26 +92,98 @@ nni_http_client_fini(nni_http_client *c) nni_tls_config_fini(c->tls); } #endif + if (c->url != NULL) { + nni_url_free(c->url); + } NNI_FREE_STRUCT(c); } int -nni_http_client_init(nni_http_client **cp, nng_sockaddr *sa) +nni_http_client_init(nni_http_client **cp, const char *urlstr) { - int rv; - + int rv; + nni_url * url; nni_http_client *c; - if ((c = NNI_ALLOC_STRUCT(c)) == NULL) { - return (NNG_ENOMEM); + nni_aio * aio; + nni_sockaddr sa; + char * host; + char * port; + + if ((rv = nni_url_parse(&url, urlstr)) != 0) { + return (rv); + } + + if (strlen(url->u_hostname) == 0) { + // We require a valid hostname. + nni_url_free(url); + return (NNG_EADDRINVAL); + } + if ((strcmp(url->u_scheme, "http") != 0) && +#ifdef NNG_SUPP_TLS + (strcmp(url->u_scheme, "https") != 0) && + (strcmp(url->u_scheme, "wss") != 0) && +#endif + (strcmp(url->u_scheme, "ws") != 0)) { + return (NNG_EADDRINVAL); } - c->addr = *sa; - rv = nni_plat_tcp_ep_init(&c->tep, NULL, &c->addr, NNI_EP_MODE_DIAL); + + // For now we are looking up the address. We would really like + // to do this later, but we need TcP support for this. One + // imagines the ability to create a tcp dialer that does the + // necessary DNS lookups, etc. all asynchronously. + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + nni_url_free(url); + return (rv); + } + aio->a_addr = &sa; + host = (strlen(url->u_hostname) != 0) ? url->u_hostname : NULL; + port = (strlen(url->u_port) != 0) ? url->u_port : NULL; + nni_plat_tcp_resolv(host, port, NNG_AF_UNSPEC, false, aio); + nni_aio_wait(aio); + rv = nni_aio_result(aio); + nni_aio_fini(aio); if (rv != 0) { - NNI_FREE_STRUCT(c); + nni_url_free(url); return (rv); } + + if ((c = NNI_ALLOC_STRUCT(c)) == NULL) { + return (NNG_ENOMEM); + } nni_mtx_init(&c->mtx); nni_aio_list_init(&c->aios); + c->url = url; + +#ifdef NNG_SUPP_TLS + if ((strcmp(url->u_scheme, "https") == 0) || + (strcmp(url->u_scheme, "wss") == 0)) { + rv = nni_tls_config_init(&c->tls, NNG_TLS_MODE_CLIENT); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } + // Take the server name right from the client URL. We only + // consider the name, as the port is never part of the + // certificate. + rv = nng_tls_config_server_name(c->tls, url->u_hostname); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } + + // Note that the application has to supply the location of + // certificates. We could probably use a default based + // on environment or common locations used by OpenSSL, but + // as there is no way to *unload* the cert file, lets not + // do that. (We might want to consider a mode to reset.) + } +#endif + + rv = nni_plat_tcp_ep_init(&c->tep, NULL, &sa, NNI_EP_MODE_DIAL); + if (rv != 0) { + nni_http_client_fini(c); + return (rv); + } if ((rv = nni_aio_init(&c->connaio, http_conn_done, c)) != 0) { nni_http_client_fini(c); @@ -121,10 +193,10 @@ nni_http_client_init(nni_http_client **cp, nng_sockaddr *sa) return (0); } -#ifdef NNG_SUPP_TLS int nni_http_client_set_tls(nni_http_client *c, nng_tls_config *tls) { +#ifdef NNG_SUPP_TLS nng_tls_config *old; nni_mtx_lock(&c->mtx); old = c->tls; @@ -137,8 +209,27 @@ nni_http_client_set_tls(nni_http_client *c, nng_tls_config *tls) nni_tls_config_fini(old); } return (0); +#else + return (NNG_EINVAL); +#endif } + +int +nni_http_client_get_tls(nni_http_client *c, nng_tls_config **tlsp) +{ +#ifdef NNG_SUPP_TLS + nni_mtx_lock(&c->mtx); + if (c->tls == NULL) { + nni_mtx_unlock(&c->mtx); + return (NNG_EINVAL); + } + *tlsp = c->tls; + nni_mtx_unlock(&c->mtx); + return (0); +#else + return (NNG_ENOTSUP); #endif +} static void http_connect_cancel(nni_aio *aio, int rv) diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h index 0f3affc0..06394fdd 100644 --- a/src/supplemental/http/http.h +++ b/src/supplemental/http/http.h @@ -222,11 +222,14 @@ typedef struct { } nni_http_handler; // nni_http_server will look for an existing server with the same -// socket address, or create one if one does not exist. The servers +// name and port, or create one if one does not exist. The servers // are reference counted to permit sharing the server object across -// multiple subsystems. The sockaddr matching is very limited though, -// and the addresses must match *exactly*. -extern int nni_http_server_init(nni_http_server **, nng_sockaddr *); +// multiple subsystems. The URL hostname matching is very limited, +// and the names must match *exactly* (without DNS resolution). Unless +// a restricted binding is required, we recommend using a URL consisting +// of an empty host name, such as http:// or https:// -- this would +// convert to binding to the default port on all interfaces on the host. +extern int nni_http_server_init(nni_http_server **, const char *); // nni_http_server_fini drops the reference count on the server, and // if this was the last reference, closes down the server and frees @@ -245,9 +248,17 @@ extern void nni_http_server_del_handler(nni_http_server *, void *); // nni_http_server_set_tls adds a TLS configuration to the server, // and enables the use of it. This returns NNG_EBUSY if the server is -// already started. +// already started. This wipes out the entire TLS configuration on the +// server client, so the caller must have configured it reasonably. +// This API is not recommended unless the caller needs complete control +// over the TLS configuration. extern int nni_http_server_set_tls(nni_http_server *, nng_tls_config *); +// nni_http_server_get_tls obtains the TLS configuration if one is present, +// or returns NNG_EINVAL. The TLS configuration is invalidated if the +// nni_http_server_set_tls function is called, so be careful. +extern int nni_http_server_get_tls(nni_http_server *, nng_tls_config **); + // nni_http_server_start starts listening on the supplied port. extern int nni_http_server_start(nni_http_server *); @@ -279,9 +290,21 @@ extern int nni_http_server_add_file(nni_http_server *, const char *host, typedef struct nni_http_client nni_http_client; -extern int nni_http_client_init(nni_http_client **, nng_sockaddr *); +// https vs. http; would also allow us to defer DNS lookups til later. +extern int nni_http_client_init(nni_http_client **, const char *); extern void nni_http_client_fini(nni_http_client *); -extern int nni_http_client_set_tls(nni_http_client *, nng_tls_config *); + +// nni_http_client_set_tls sets the TLS configuration. This wipes out +// the entire TLS configuration on the client, so the caller must have +// configured it reasonably. This API is not recommended unless the +// caller needs complete control over the TLS configuration. +extern int nni_http_client_set_tls(nni_http_client *, nng_tls_config *); + +// nni_http_client_get_tls obtains the TLS configuration if one is present, +// or returns NNG_EINVAL. The supplied TLS configuration object may +// be invalidated by any future calls to nni_http_client_set_tls. +extern int nni_http_client_get_tls(nni_http_client *, nng_tls_config **); + extern void nni_http_client_connect(nni_http_client *, nni_aio *); #endif // NNG_SUPPLEMENTAL_HTTP_HTTP_H diff --git a/src/supplemental/http/server.c b/src/supplemental/http/server.c index 01f1230d..700ec536 100644 --- a/src/supplemental/http/server.c +++ b/src/supplemental/http/server.c @@ -69,6 +69,7 @@ struct nni_http_server { nng_tls_config * tls; nni_aio * accaio; nni_plat_tcp_ep *tep; + nni_url * url; }; static nni_list http_servers; @@ -78,13 +79,15 @@ static void http_handler_fini(http_handler *); static void http_sconn_reap(void *arg) { - http_sconn *sc = arg; + http_sconn * sc = arg; + nni_http_server *s = sc->server; NNI_ASSERT(!sc->finished); sc->finished = true; nni_aio_stop(sc->rxaio); nni_aio_stop(sc->txaio); nni_aio_stop(sc->txdataio); nni_aio_stop(sc->cbaio); + if (sc->http != NULL) { nni_http_fini(sc->http); } @@ -98,6 +101,17 @@ http_sconn_reap(void *arg) nni_aio_fini(sc->txaio); nni_aio_fini(sc->txdataio); nni_aio_fini(sc->cbaio); + + // Now it is safe to release our reference on the server. + nni_mtx_lock(&s->mtx); + if (nni_list_node_active(&sc->node)) { + nni_list_remove(&s->conns, sc); + if (nni_list_empty(&s->conns)) { + nni_cv_wake(&s->cv); + } + } + nni_mtx_unlock(&s->mtx); + NNI_FREE_STRUCT(sc); } @@ -120,13 +134,6 @@ http_sconn_close_locked(http_sconn *sc) NNI_ASSERT(!sc->finished); sc->closed = true; - // Close the underlying transport. - if (nni_list_node_active(&sc->node)) { - nni_list_remove(&s->conns, sc); - if (nni_list_empty(&s->conns)) { - nni_cv_wake(&s->cv); - } - } nni_aio_cancel(sc->rxaio, NNG_ECLOSED); nni_aio_cancel(sc->txaio, NNG_ECLOSED); nni_aio_cancel(sc->txdataio, NNG_ECLOSED); @@ -144,10 +151,6 @@ http_sconn_close(http_sconn *sc) nni_http_server *s; s = sc->server; - if (sc->closed) { - return; - } - nni_mtx_lock(&s->mtx); http_sconn_close_locked(sc); nni_mtx_unlock(&s->mtx); @@ -631,6 +634,9 @@ http_server_fini(nni_http_server *s) http_handler_fini(h); } nni_mtx_unlock(&s->mtx); + if (s->url != NULL) { + nni_url_free(s->url); + } #ifdef NNG_SUPP_TLS if (s->tls != NULL) { nni_tls_config_fini(s->tls); @@ -655,14 +661,34 @@ nni_http_server_fini(nni_http_server *s) } static int -http_server_init(nni_http_server **serverp, nng_sockaddr *sa) +http_server_init(nni_http_server **serverp, nni_url *url) { nni_http_server *s; int rv; + const char * host; + const char * port; + nni_aio * aio; + + host = url->u_hostname; + if (strlen(host) == 0) { + host = NULL; + } + port = url->u_port; + if ((strcmp(url->u_scheme, "http") != 0) && +#ifdef NNG_SUPP_TLS + (strcmp(url->u_scheme, "https") != 0) && + (strcmp(url->u_scheme, "wss") != 0) && +#endif + (strcmp(url->u_scheme, "ws") != 0)) { + nni_url_free(url); + return (NNG_EADDRINVAL); + } if ((s = NNI_ALLOC_STRUCT(s)) == NULL) { + nni_url_free(url); return (NNG_ENOMEM); } + s->url = url; nni_mtx_init(&s->mtx); nni_cv_init(&s->cv, &s->mtx); NNI_LIST_INIT(&s->handlers, http_handler, node); @@ -671,49 +697,71 @@ http_server_init(nni_http_server **serverp, nng_sockaddr *sa) http_server_fini(s); return (rv); } - s->addr = *sa; - *serverp = s; +#ifdef NNG_SUPP_TLS + if ((strcmp(url->u_scheme, "https") == 0) || + (strcmp(url->u_scheme, "wss") == 0)) { + rv = nni_tls_config_init(&s->tls, NNG_TLS_MODE_SERVER); + if (rv != 0) { + http_server_fini(s); + return (rv); + } + } +#endif + + // Do the DNS lookup *now*. This means that this is synchronous, + // but it should be fast, since it should either resolve as a number, + // or resolve locally, without having to hit up DNS. + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + http_server_fini(s); + return (rv); + } + aio->a_addr = &s->addr; + host = (strlen(url->u_hostname) != 0) ? url->u_hostname : NULL; + port = (strlen(url->u_port) != 0) ? url->u_port : NULL; + nni_plat_tcp_resolv(host, port, NNG_AF_UNSPEC, true, aio); + nni_aio_wait(aio); + rv = nni_aio_result(aio); + nni_aio_fini(aio); + if (rv != 0) { + http_server_fini(s); + return (rv); + } + s->refcnt = 1; + *serverp = s; return (0); } int -nni_http_server_init(nni_http_server **serverp, nng_sockaddr *sa) +nni_http_server_init(nni_http_server **serverp, const char *urlstr) { int rv; nni_http_server *s; + nni_url * url; + + if ((rv = nni_url_parse(&url, urlstr)) != 0) { + return (rv); + } nni_initialize(&http_server_initializer); nni_mtx_lock(&http_servers_lk); NNI_LIST_FOREACH (&http_servers, s) { - switch (sa->s_un.s_family) { - case NNG_AF_INET: - if (memcmp(&s->addr.s_un.s_in, &sa->s_un.s_in, - sizeof(sa->s_un.s_in)) == 0) { - *serverp = s; - s->refcnt++; - nni_mtx_unlock(&http_servers_lk); - return (0); - } - break; - case NNG_AF_INET6: - if (memcmp(&s->addr.s_un.s_in6, &sa->s_un.s_in6, - sizeof(sa->s_un.s_in6)) == 0) { - *serverp = s; - s->refcnt++; - nni_mtx_unlock(&http_servers_lk); - return (0); - } - break; + if ((strcmp(url->u_port, s->url->u_port) == 0) && + (strcmp(url->u_hostname, s->url->u_hostname) == 0)) { + nni_url_free(url); + *serverp = s; + s->refcnt++; + nni_mtx_unlock(&http_servers_lk); + return (0); } } // We didn't find a server, try to make a new one. - if ((rv = http_server_init(&s, sa)) == 0) { - s->addr = *sa; - s->refcnt = 1; + if ((rv = http_server_init(&s, url)) == 0) { nni_list_append(&http_servers, s); *serverp = s; + } else { + nni_url_free(url); } nni_mtx_unlock(&http_servers_lk); @@ -724,7 +772,6 @@ static int http_server_start(nni_http_server *s) { int rv; - rv = nni_plat_tcp_ep_init(&s->tep, &s->addr, NULL, NNI_EP_MODE_LISTEN); if (rv != 0) { return (rv); @@ -1116,10 +1163,10 @@ nni_http_server_add_static(nni_http_server *s, const char *host, return (0); } -#ifdef NNG_SUPP_TLS int nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) { +#ifdef NNG_SUPP_TLS nng_tls_config *old; nni_mtx_lock(&s->mtx); if (s->starts) { @@ -1136,8 +1183,27 @@ nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) nni_tls_config_fini(old); } return (0); +#else + return (NNG_ENOTSUP); +#endif } + +int +nni_http_server_get_tls(nni_http_server *s, nng_tls_config **tp) +{ +#ifdef NNG_SUPP_TLS + nni_mtx_lock(&s->mtx); + if (s->tls == NULL) { + nni_mtx_unlock(&s->mtx); + return (NNG_EINVAL); + } + *tp = s->tls; + nni_mtx_unlock(&s->mtx); + return (0); +#else + return (NNG_ENOTSUP); #endif +} static int http_server_sys_init(void) diff --git a/src/supplemental/tls/mbedtls/tls.c b/src/supplemental/tls/mbedtls/tls.c index c37d3d13..4e846f98 100644 --- a/src/supplemental/tls/mbedtls/tls.c +++ b/src/supplemental/tls/mbedtls/tls.c @@ -92,8 +92,6 @@ struct nng_tls_config { #endif mbedtls_x509_crt ca_certs; mbedtls_x509_crl crl; - bool have_ca_certs; - bool have_crl; int refcnt; // servers increment the reference @@ -275,27 +273,33 @@ nni_tls_fini(nni_tls *tp) NNI_FREE_STRUCT(tp); } -void -nni_tls_strerror(int errnum, char *buf, size_t sz) -{ - if (errnum & NNG_ETRANERR) { - errnum &= ~NNG_ETRANERR; - errnum = -errnum; - - mbedtls_strerror(errnum, buf, sz); - } else { - (void) snprintf(buf, sz, "%s", nng_strerror(errnum)); - } -} - // nni_tls_mkerr converts an mbed error to an NNG error. In all cases // we just encode with NNG_ETRANERR. +static struct { + int tls; + int nng; +} nni_tls_errs[] = { + { MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE, NNG_EPEERAUTH }, + { MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY, NNG_ECONNREFUSED }, + { MBEDTLS_ERR_SSL_ALLOC_FAILED, NNG_ENOMEM }, + { MBEDTLS_ERR_SSL_TIMEOUT, NNG_ETIMEDOUT }, + { MBEDTLS_ERR_SSL_CONN_EOF, NNG_ECLOSED }, + // terminator + { 0, 0 }, +}; + static int nni_tls_mkerr(int err) { - err = -err; - err |= NNG_ETRANERR; - return (err); + for (int i = 0; nni_tls_errs[i].tls != 0; i++) { + if (nni_tls_errs[i].tls == err) { + return (nni_tls_errs[i].nng); + } + } + return (NNG_ECRYPTO); } int @@ -370,6 +374,23 @@ nni_tls_cancel(nni_aio *aio, int rv) nni_mtx_unlock(&tp->lk); } +static void +nni_tls_fail(nni_tls *tp, int rv) +{ + nni_aio *aio; + tp->tls_closed = true; + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + while ((aio = nni_list_first(&tp->recvs)) != NULL) { + nni_list_remove(&tp->recvs, aio); + nni_aio_finish_error(aio, rv); + } + while ((aio = nni_list_first(&tp->sends)) != NULL) { + nni_list_remove(&tp->recvs, aio); + nni_aio_finish_error(aio, rv); + } +} + // nni_tls_send_cb is called when the underlying TCP send completes. static void nni_tls_send_cb(void *ctx) @@ -602,11 +623,8 @@ nni_tls_do_handshake(nni_tls *tp) return; default: - // Some other error occurred... would be nice to be - // able to diagnose it better. - tp->tls_closed = true; - nni_plat_tcp_pipe_close(tp->tcp); - tp->tcp_closed = true; + // some other error occurred, this causes us to tear it down + nni_tls_fail(tp, nni_tls_mkerr(rv)); } } @@ -723,7 +741,6 @@ nni_tls_close(nni_tls *tp) (void) mbedtls_ssl_close_notify(&tp->ctx); } else { nni_plat_tcp_pipe_close(tp->tcp); - tp->tcp_closed = true; } nni_mtx_unlock(&tp->lk); } @@ -817,8 +834,10 @@ nng_tls_config_ca_chain( rv = nni_tls_mkerr(rv); goto err; } - cfg->have_crl = true; } + + mbedtls_ssl_conf_ca_chain(&cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl); + err: nni_mtx_unlock(&cfg->lk); return (rv); @@ -881,6 +900,69 @@ err: } int +nng_tls_config_ca_file(nng_tls_config *cfg, const char *path) +{ + int rv; + void * fdata; + size_t fsize; + char * pem; + // Note that while mbedTLS supports its own file methods, we want + // to avoid depending on that because it might not have been + // included, so we use our own. We have to read the file, and + // then allocate a buffer that has an extra byte so we can + // ensure NUL termination. The file named by path may contain + // both a ca chain, and crl chain, or just a ca chain. + if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { + return (rv); + } + if ((pem = nni_alloc(fsize + 1)) == NULL) { + nni_free(fdata, fsize); + return (NNG_ENOMEM); + } + memcpy(pem, fdata, fsize); + pem[fsize] = '\0'; + nni_free(fdata, fsize); + if (strstr(pem, "-----BEGIN X509 CRL-----") != NULL) { + rv = nng_tls_config_ca_chain(cfg, pem, pem); + } else { + rv = nng_tls_config_ca_chain(cfg, pem, NULL); + } + nni_free(pem, fsize + 1); + return (rv); +} + +int +nng_tls_config_cert_key_file( + nng_tls_config *cfg, const char *path, const char *pass) +{ + int rv; + void * fdata; + size_t fsize; + char * pem; + + // Note that while mbedTLS supports its own file methods, we want + // to avoid depending on that because it might not have been + // included, so we use our own. We have to read the file, and + // then allocate a buffer that has an extra byte so we can + // ensure NUL termination. The file named by path must contain + // both our certificate, and our private key. The password + // may be NULL if the key is not encrypted. + if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { + return (rv); + } + if ((pem = nni_alloc(fsize + 1)) == NULL) { + nni_free(fdata, fsize); + return (NNG_ENOMEM); + } + memcpy(pem, fdata, fsize); + pem[fsize] = '\0'; + nni_free(fdata, fsize); + rv = nng_tls_config_own_cert(cfg, pem, pem, pass); + nni_free(pem, fsize + 1); + return (rv); +} + +int nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode) { return (nni_tls_config_init(cfgp, mode)); diff --git a/src/supplemental/tls/tls.h b/src/supplemental/tls/tls.h index 8175854d..5fde50b4 100644 --- a/src/supplemental/tls/tls.h +++ b/src/supplemental/tls/tls.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -48,6 +48,4 @@ extern const char *nni_tls_ciphersuite_name(nni_tls *); // TBD: getting additional peer certificate information... -extern void nni_tls_strerror(int, char *, size_t); // review this - #endif // NNG_SUPPLEMENTAL_TLS_TLS_H diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index aca09749..fe4f002f 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -30,28 +30,32 @@ typedef struct ws_header { } ws_header; struct nni_ws { - nni_list_node node; - nni_reap_item reap; - int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN - bool closed; - bool ready; - bool wclose; - nni_mtx mtx; - nni_list txmsgs; - nni_list rxmsgs; - ws_frame * txframe; - ws_frame * rxframe; - nni_aio * txaio; // physical aios - nni_aio * rxaio; - nni_aio * closeaio; - nni_aio * httpaio; // server only, HTTP reply pending - nni_http * http; - nni_http_req *req; - nni_http_res *res; - char * reqhdrs; - char * reshdrs; - size_t maxframe; - size_t fragsize; + nni_list_node node; + nni_reap_item reap; + int mode; // NNI_EP_MODE_DIAL or NNI_EP_MODE_LISTEN + bool closed; + bool ready; + bool wclose; + nni_mtx mtx; + nni_list txmsgs; + nni_list rxmsgs; + ws_frame * txframe; + ws_frame * rxframe; + nni_aio * txaio; // physical aios + nni_aio * rxaio; + nni_aio * closeaio; + nni_aio * httpaio; + nni_aio * connaio; // connect aio + nni_aio * useraio; // user aio, during HTTP negotiation + nni_http * http; + nni_http_req * req; + nni_http_res * res; + char * reqhdrs; + char * reshdrs; + size_t maxframe; + size_t fragsize; + nni_ws_listener *listener; + nni_ws_dialer * dialer; }; struct nni_ws_listener { @@ -83,12 +87,9 @@ struct nni_ws_dialer { nni_http_res * res; nni_http_client *client; nni_mtx mtx; - nni_aio * conaio; char * proto; nni_url * url; - nni_list conaios; // user aios waiting for connect. - nni_list httpaios; // user aios waiting for HTTP nego. - bool started; + nni_list wspend; // ws structures still negotiating bool closed; nng_sockaddr sa; nni_list headers; // request headers @@ -139,6 +140,10 @@ struct ws_msg { }; static void ws_send_close(nni_ws *ws, uint16_t code); +static void ws_conn_cb(void *); +static void ws_close_cb(void *); +static void ws_read_cb(void *); +static void ws_write_cb(void *); // This looks, case independently for a word in a list, which is either // space or comma separated. @@ -455,9 +460,23 @@ ws_close(nni_ws *ws, uint16_t code) // If were closing "gracefully", then don't abort in-flight // stuff yet. Note that reads should have stopped already. + // However, we *do* abort any inflight HTTP negotiation, or + // pending connect request. if (!ws->closed) { + // ABORT connection negotiation. + nni_aio_cancel(ws->connaio, NNG_ECLOSED); + nni_aio_cancel(ws->httpaio, NNG_ECLOSED); ws_send_close(ws, code); - return; + } + + if (nni_list_node_active(&ws->node)) { + nni_ws_dialer *d; + + if ((d = ws->dialer) != NULL) { + nni_mtx_lock(&d->mtx); + nni_list_node_remove(&ws->node); + nni_mtx_unlock(&d->mtx); + } } } @@ -514,7 +533,12 @@ ws_write_cb(void *arg) nni_mtx_lock(&ws->mtx); - if (ws->txframe->op == WS_CLOSE) { + if ((frame = ws->txframe) == NULL) { + nni_mtx_unlock(&ws->mtx); + return; + } + ws->txframe = NULL; + if (frame->op == WS_CLOSE) { // If this was a close frame, we are done. // No other messages may succeed.. while ((wm = nni_list_first(&ws->txmsgs)) != NULL) { @@ -533,10 +557,8 @@ ws_write_cb(void *arg) return; } - frame = ws->txframe; - wm = frame->wmsg; - aio = wm->aio; - ws->txframe = NULL; + wm = frame->wmsg; + aio = wm->aio; if ((rv = nni_aio_result(ws->txaio)) != 0) { @@ -603,7 +625,7 @@ ws_send_close(nni_ws *ws, uint16_t code) NNI_PUT16(buf, code); - if (ws->closed) { + if (ws->closed || !ws->ready) { return; } ws->closed = true; @@ -1067,6 +1089,7 @@ ws_fini(void *arg) nni_aio_stop(ws->txaio); nni_aio_stop(ws->closeaio); nni_aio_stop(ws->httpaio); + nni_aio_stop(ws->connaio); nni_mtx_lock(&ws->mtx); while ((wm = nni_list_first(&ws->rxmsgs)) != NULL) { @@ -1090,6 +1113,9 @@ ws_fini(void *arg) } nni_mtx_unlock(&ws->mtx); + if (ws->http) { + nni_http_fini(ws->http); + } if (ws->req) { nni_http_req_fini(ws->req); } @@ -1099,11 +1125,11 @@ ws_fini(void *arg) nni_strfree(ws->reqhdrs); nni_strfree(ws->reshdrs); - nni_http_fini(ws->http); nni_aio_fini(ws->rxaio); nni_aio_fini(ws->txaio); nni_aio_fini(ws->closeaio); nni_aio_fini(ws->httpaio); + nni_aio_fini(ws->connaio); nni_mtx_fini(&ws->mtx); NNI_FREE_STRUCT(ws); } @@ -1150,10 +1176,10 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) char wskey[29]; const char * ptr; - d = nni_aio_get_data(aio, 0); + d = ws->dialer; + uaio = ws->useraio; nni_mtx_lock(&d->mtx); - uaio = nni_list_first(&d->httpaios); NNI_ASSERT(uaio != NULL); // We have two steps. In step 1, we just sent the request, // and need to retrieve the reply. In step two we have @@ -1222,16 +1248,18 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) #undef GETH // At this point, we are in business! - ws->ready = true; - nni_aio_list_remove(uaio); + nni_list_remove(&d->wspend, ws); + ws->ready = true; + ws->useraio = NULL; nni_aio_finish_pipe(uaio, ws); nni_mtx_unlock(&d->mtx); return; err: - nni_aio_list_remove(uaio); + nni_list_remove(&d->wspend, ws); + ws->useraio = NULL; nni_aio_finish_error(uaio, rv); - nni_ws_fini(ws); nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); } static void @@ -1251,7 +1279,7 @@ ws_http_cb(void *arg) } static int -ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) +ws_init(nni_ws **wsp) { nni_ws *ws; int rv; @@ -1266,8 +1294,9 @@ ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) if (((rv = nni_aio_init(&ws->closeaio, ws_close_cb, ws)) != 0) || ((rv = nni_aio_init(&ws->txaio, ws_write_cb, ws)) != 0) || ((rv = nni_aio_init(&ws->rxaio, ws_read_cb, ws)) != 0) || - ((rv = nni_aio_init(&ws->httpaio, ws_http_cb, ws)) != 0)) { - nni_ws_fini(ws); + ((rv = nni_aio_init(&ws->httpaio, ws_http_cb, ws)) != 0) || + ((rv = nni_aio_init(&ws->connaio, ws_conn_cb, ws)) != 0)) { + ws_fini(ws); return (rv); } @@ -1276,9 +1305,6 @@ ws_init(nni_ws **wsp, nni_http *http, nni_http_req *req, nni_http_res *res) ws->fragsize = 1 << 20; // we won't send a frame larger than this ws->maxframe = (1 << 20) * 10; // default limit on incoming frame size - ws->http = http; - ws->req = req; - ws->res = res; *wsp = ws; return (0); } @@ -1444,11 +1470,15 @@ ws_handler(nni_aio *aio) // We are good to go, provided we can get the websocket struct, // and send the reply. - if ((rv = ws_init(&ws, http, req, res)) != 0) { + if ((rv = ws_init(&ws)) != 0) { + nni_http_req_fini(req); nni_http_res_fini(res); status = NNI_HTTP_STATUS_INTERNAL_SERVER_ERROR; goto err; } + ws->http = http; + ws->req = req; + ws->res = res; ws->mode = NNI_EP_MODE_LISTEN; // XXX: Inherit fragmentation and message size limits! @@ -1475,8 +1505,6 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) { nni_ws_listener *l; int rv; - nni_aio * aio; - nni_sockaddr sa; char * host; char * serv; @@ -1511,20 +1539,7 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *addr) l->handler.h_host = host; // ignore the port l->handler.h_cb = ws_handler; - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - nni_ws_listener_fini(l); - return (rv); - } - aio->a_addr = &sa; - nni_plat_tcp_resolv(host, serv, NNG_AF_UNSPEC, true, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - nni_ws_listener_fini(l); - return (rv); - } - if ((rv = nni_http_server_init(&l->server, &sa)) != 0) { + if ((rv = nni_http_server_init(&l->server, addr)) != 0) { nni_ws_listener_fini(l); return (rv); } @@ -1666,7 +1681,6 @@ nni_ws_listener_hook( nni_mtx_unlock(&l->mtx); } -#ifdef NNG_SUPP_TLS int nni_ws_listener_set_tls(nni_ws_listener *l, nng_tls_config *tls) { @@ -1676,47 +1690,61 @@ nni_ws_listener_set_tls(nni_ws_listener *l, nng_tls_config *tls) nni_mtx_unlock(&l->mtx); return (rv); } -#endif + +int +nni_ws_listener_get_tls(nni_ws_listener *l, nng_tls_config **tlsp) +{ + int rv; + nni_mtx_lock(&l->mtx); + rv = nni_http_server_get_tls(l->server, tlsp); + nni_mtx_unlock(&l->mtx); + return (rv); +} void ws_conn_cb(void *arg) { - nni_ws_dialer *d = arg; - nni_aio * aio = d->conaio; + nni_ws_dialer *d; + nni_ws * ws; nni_aio * uaio; nni_http * http; nni_http_req * req = NULL; int rv; uint8_t raw[16]; char wskey[25]; - nni_ws * ws; ws_header * hdr; - nni_mtx_lock(&d->mtx); - uaio = nni_list_first(&d->conaios); - rv = nni_aio_result(aio); - http = rv == 0 ? nni_aio_get_output(aio, 0) : NULL; + ws = arg; - if (uaio == NULL) { - if (http) { - // Nobody listening anymore - hard abort. - nni_http_fini(http); + d = ws->dialer; + if ((rv = nni_aio_result(ws->connaio)) != 0) { + nni_mtx_lock(&ws->mtx); + if ((uaio = ws->useraio) != NULL) { + ws->useraio = NULL; + nni_aio_finish_error(uaio, rv); + } + nni_mtx_unlock(&ws->mtx); + nni_mtx_lock(&d->mtx); + if (nni_list_node_active(&ws->node)) { + nni_list_remove(&d->wspend, ws); + nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); + } else { + nni_mtx_unlock(&d->mtx); } - nni_mtx_unlock(&d->mtx); return; } - nni_aio_list_remove(uaio); - nni_aio_set_output(aio, 0, NULL); - - // We are done with this aio, start another connection request while - // we finish up, if we have more clients waiting. - if (!nni_list_empty(&d->conaios)) { - nni_http_client_connect(d->client, aio); - } - - if (rv != 0) { - goto err; + nni_mtx_lock(&ws->mtx); + uaio = ws->useraio; + http = nni_aio_get_output(ws->connaio, 0); + nni_aio_set_output(ws->connaio, 0, NULL); + if (uaio == NULL) { + // This request was canceled for some reason. + nni_http_fini(http); + nni_mtx_unlock(&ws->mtx); + nni_ws_fini(ws); + return; } for (int i = 0; i < 16; i++) { @@ -1738,7 +1766,6 @@ ws_conn_cb(void *arg) goto err; } - // If consumer asked for protocol, pass it on. if ((d->proto != NULL) && ((rv = SETH("Sec-WebSocket-Protocol", d->proto)) != 0)) { goto err; @@ -1749,33 +1776,25 @@ ws_conn_cb(void *arg) goto err; } } - #undef SETH - if ((rv = ws_init(&ws, http, req, NULL)) != 0) { - goto err; - } - ws->mode = NNI_EP_MODE_DIAL; + ws->http = http; + ws->req = req; - // Move this uaio to the http wait list. Note that it is not - // required that the uaio will be completed by this connection. - // If another connection attempt completes first, then the first - // aio queued will get the result. - nni_list_append(&d->httpaios, uaio); - nni_aio_set_data(ws->httpaio, 0, d); nni_http_write_req(http, req, ws->httpaio); - nni_mtx_unlock(&d->mtx); + nni_mtx_unlock(&ws->mtx); return; err: nni_aio_finish_error(uaio, rv); + nni_mtx_unlock(&ws->mtx); if (http != NULL) { nni_http_fini(http); } if (req != NULL) { nni_http_req_fini(req); } - nni_mtx_unlock(&d->mtx); + nni_ws_fini(ws); } void @@ -1783,7 +1802,6 @@ nni_ws_dialer_fini(nni_ws_dialer *d) { ws_header *hdr; - nni_aio_fini(d->conaio); nni_strfree(d->proto); while ((hdr = nni_list_first(&d->headers)) != NULL) { nni_list_remove(&d->headers, hdr); @@ -1806,62 +1824,20 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *addr) { nni_ws_dialer *d; int rv; - nni_aio * aio; - char * serv; if ((d = NNI_ALLOC_STRUCT(d)) == NULL) { return (NNG_ENOMEM); } NNI_LIST_INIT(&d->headers, ws_header, node); + NNI_LIST_INIT(&d->wspend, nni_ws, node); nni_mtx_init(&d->mtx); - nni_aio_list_init(&d->conaios); - nni_aio_list_init(&d->httpaios); if ((rv = nni_url_parse(&d->url, addr)) != 0) { nni_ws_dialer_fini(d); return (rv); } - // Dialer requires a valid host. - if ((strlen(d->url->u_hostname) == 0) || - (strcmp(d->url->u_hostname, "*") == 0)) { - nni_ws_dialer_fini(d); - return (NNG_EADDRINVAL); - } - - // Default port is 80 for ws, and 443 for wss. - if ((d->url->u_port == NULL) || (strlen(d->url->u_port) == 0)) { - if (strcmp(d->url->u_scheme, "wss") == 0) { - serv = "443"; - } else { - serv = "80"; - } - } else { - serv = d->url->u_port; - } - - if ((rv = nni_aio_init(&d->conaio, ws_conn_cb, d)) != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - // XXX: this is synchronous. We should fix this in the HTTP layer. - aio->a_addr = &d->sa; - nni_plat_tcp_resolv( - d->url->u_hostname, serv, NNG_AF_UNSPEC, false, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - nni_ws_dialer_fini(d); - return (rv); - } - - if ((rv = nni_http_client_init(&d->client, &d->sa)) != 0) { + if ((rv = nni_http_client_init(&d->client, addr)) != 0) { nni_ws_dialer_fini(d); return (rv); } @@ -1870,7 +1846,6 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *addr) return (0); } -#ifdef NNG_SUPP_TLS int nni_ws_dialer_set_tls(nni_ws_dialer *d, nng_tls_config *tls) { @@ -1880,19 +1855,32 @@ nni_ws_dialer_set_tls(nni_ws_dialer *d, nng_tls_config *tls) nni_mtx_unlock(&d->mtx); return (rv); } -#endif + +int +nni_ws_dialer_get_tls(nni_ws_dialer *d, nng_tls_config **tlsp) +{ + int rv; + nni_mtx_lock(&d->mtx); + rv = nni_http_client_get_tls(d->client, tlsp); + nni_mtx_unlock(&d->mtx); + return (rv); +} void nni_ws_dialer_close(nni_ws_dialer *d) { + nni_ws *ws; nni_mtx_lock(&d->mtx); if (d->closed) { nni_mtx_unlock(&d->mtx); return; } d->closed = true; + while ((ws = nni_list_first(&d->wspend)) != 0) { + nni_list_remove(&d->wspend, ws); + nni_ws_close(ws); + } nni_mtx_unlock(&d->mtx); - nni_aio_cancel(d->conaio, NNG_ECLOSED); } int @@ -1916,38 +1904,45 @@ nni_ws_dialer_proto(nni_ws_dialer *d, const char *proto) static void ws_dial_cancel(nni_aio *aio, int rv) { - nni_ws_dialer *d = aio->a_prov_data; - nni_mtx_lock(&d->mtx); - // If we are waiting, then we can cancel. Otherwise we need - // to abort. - if (nni_aio_list_active(aio)) { - nni_aio_list_remove(aio); + nni_ws *ws = aio->a_prov_data; + + nni_mtx_lock(&ws->mtx); + if (aio == ws->useraio) { + nni_aio_cancel(ws->connaio, rv); + nni_aio_cancel(ws->httpaio, rv); + ws->useraio = NULL; nni_aio_finish_error(aio, rv); } - // This does not cancel in-flight client negotiations with HTTP. - nni_mtx_unlock(&d->mtx); + nni_mtx_unlock(&ws->mtx); } void nni_ws_dialer_dial(nni_ws_dialer *d, nni_aio *aio) { - nni_mtx_lock(&d->mtx); - // First look up the host. - if (nni_aio_start(aio, ws_dial_cancel, d) != 0) { - nni_mtx_unlock(&d->mtx); + nni_ws *ws; + int rv; + + if ((rv = ws_init(&ws)) != 0) { + nni_aio_finish_error(aio, rv); return; } + nni_mtx_lock(&d->mtx); if (d->closed) { nni_aio_finish_error(aio, NNG_ECLOSED); nni_mtx_unlock(&d->mtx); + ws_fini(ws); return; } - nni_list_append(&d->conaios, aio); - - if (!d->started) { - d->started = true; - nni_http_client_connect(d->client, d->conaio); + if (nni_aio_start(aio, ws_dial_cancel, ws) != 0) { + nni_mtx_unlock(&d->mtx); + ws_fini(ws); + return; } + ws->dialer = d; + ws->useraio = aio; + ws->mode = NNI_EP_MODE_DIAL; + nni_list_append(&d->wspend, ws); + nni_http_client_connect(d->client, ws->connaio); nni_mtx_unlock(&d->mtx); } diff --git a/src/supplemental/websocket/websocket.h b/src/supplemental/websocket/websocket.h index ccf549df..9a52f78c 100644 --- a/src/supplemental/websocket/websocket.h +++ b/src/supplemental/websocket/websocket.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -36,6 +36,7 @@ extern void nni_ws_listener_accept(nni_ws_listener *, nni_aio *); extern void nni_ws_listener_hook( nni_ws_listener *, nni_ws_listen_hook, void *); extern int nni_ws_listener_set_tls(nni_ws_listener *, nng_tls_config *); +extern int nni_ws_listener_get_tls(nni_ws_listener *, nng_tls_config **s); extern int nni_ws_dialer_init(nni_ws_dialer **, const char *); extern void nni_ws_dialer_fini(nni_ws_dialer *); @@ -44,6 +45,7 @@ extern int nni_ws_dialer_proto(nni_ws_dialer *, const char *); extern int nni_ws_dialer_header(nni_ws_dialer *, const char *, const char *); extern void nni_ws_dialer_dial(nni_ws_dialer *, nni_aio *); extern int nni_ws_dialer_set_tls(nni_ws_dialer *, nng_tls_config *); +extern int nni_ws_dialer_get_tls(nni_ws_dialer *, nng_tls_config **); // Dialer does not get a hook chance, as it can examine the request and reply // after dial is done; this is not a 3-way handshake, so the dialer does diff --git a/src/transport/ws/websocket.c b/src/transport/ws/websocket.c index 05cb9513..16cdf47b 100644 --- a/src/transport/ws/websocket.c +++ b/src/transport/ws/websocket.c @@ -1,6 +1,6 @@ // -// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -43,7 +43,6 @@ struct ws_ep { nni_ws_listener *listener; nni_ws_dialer * dialer; nni_list headers; // to send, res or req - nng_tls_config * tls; }; struct ws_pipe { @@ -222,7 +221,6 @@ ws_pipe_init(ws_pipe **pipep, ws_ep *ep, void *ws) p->mode = ep->mode; p->rcvmax = ep->rcvmax; - // p->addr = ep->addr; p->rproto = ep->rproto; p->lproto = ep->lproto; p->ws = ws; @@ -603,11 +601,6 @@ ws_ep_fini(void *arg) nni_strfree(ep->addr); nni_strfree(ep->protoname); nni_mtx_fini(&ep->mtx); -#ifdef NNG_TRANSPORT_WSS - if (ep->tls) { - nni_tls_config_fini(ep->tls); - } -#endif NNI_FREE_STRUCT(ep); } @@ -706,18 +699,6 @@ ws_ep_init(void **epp, const char *url, nni_sock *sock, int mode) nni_mtx_init(&ep->mtx); NNI_LIST_INIT(&ep->headers, ws_hdr, node); -#ifdef NNG_TRANSPORT_WSS - if (strncmp(url, "wss://", 4) == 0) { - rv = nni_tls_config_init(&ep->tls, - mode == NNI_EP_MODE_DIAL ? NNG_TLS_MODE_CLIENT - : NNG_TLS_MODE_SERVER); - if (rv != 0) { - NNI_FREE_STRUCT(ep); - return (rv); - } - } -#endif - // List of pipes (server only). nni_aio_list_init(&ep->aios); @@ -798,10 +779,29 @@ nng_ws_register(void) #ifdef NNG_TRANSPORT_WSS static int +wss_get_tls(ws_ep *ep, nng_tls_config **tlsp) +{ + switch (ep->mode) { + case NNI_EP_MODE_DIAL: + return (nni_ws_dialer_get_tls(ep->dialer, tlsp)); + case NNI_EP_MODE_LISTEN: + return (nni_ws_listener_get_tls(ep->listener, tlsp)); + } + return (NNG_EINVAL); +} + +static int wss_ep_getopt_tlsconfig(void *arg, void *v, size_t *szp) { - ws_ep *ep = arg; - return (nni_getopt_ptr(ep->tls, v, szp)); + ws_ep * ep = arg; + nng_tls_config *tls; + int rv; + + if (((rv = wss_get_tls(ep, &tls)) != 0) || + ((rv = nni_getopt_ptr(tls, v, szp)) != 0)) { + return (rv); + } + return (0); } static int @@ -828,13 +828,98 @@ wss_ep_setopt_tlsconfig(void *arg, const void *v, size_t sz) } else { rv = nni_ws_dialer_set_tls(ep->dialer, cfg); } - if (rv == 0) { - if (ep->tls != NULL) { - nni_tls_config_fini(ep->tls); + nni_mtx_unlock(&ep->mtx); + return (rv); +} + +static int +wss_ep_setopt_tls_cert_key_file(void *arg, const void *v, size_t sz) +{ + ws_ep * ep = arg; + int rv; + nng_tls_config *tls; + + if (ep == NULL) { + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); } - nni_tls_config_hold(cfg); - ep->tls = cfg; + return (0); + } + nni_mtx_lock(&ep->mtx); + if (((rv = wss_get_tls(ep, &tls)) != 0) || + ((rv = nng_tls_config_cert_key_file(tls, v, NULL)) != 0)) { + goto done; } +done: + nni_mtx_unlock(&ep->mtx); + return (rv); +} + +static int +wss_ep_setopt_tls_ca_file(void *arg, const void *v, size_t sz) +{ + ws_ep * ep = arg; + int rv; + nng_tls_config *tls; + + if (ep == NULL) { + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); + } + return (0); + } + nni_mtx_lock(&ep->mtx); + if (((rv = wss_get_tls(ep, &tls)) != 0) || + ((rv = nng_tls_config_ca_file(tls, v)) != 0)) { + goto done; + } +done: + nni_mtx_unlock(&ep->mtx); + return (rv); +} + +static int +wss_ep_setopt_tls_auth_mode(void *arg, const void *v, size_t sz) +{ + ws_ep * ep = arg; + int rv; + nng_tls_config *tls; + int mode; + + rv = nni_setopt_int( + &mode, v, sz, NNG_TLS_AUTH_MODE_NONE, NNG_TLS_AUTH_MODE_REQUIRED); + if ((rv != 0) || (ep == NULL)) { + return (rv); + } + nni_mtx_lock(&ep->mtx); + if (((rv = wss_get_tls(ep, &tls)) != 0) || + ((rv = nng_tls_config_auth_mode(tls, mode)) != 0)) { + goto done; + } +done: + nni_mtx_unlock(&ep->mtx); + return (rv); +} + +static int +wss_ep_setopt_tls_server_name(void *arg, const void *v, size_t sz) +{ + ws_ep * ep = arg; + int rv; + nng_tls_config *tls; + + if (ep == NULL) { + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); + } + return (0); + } + nni_mtx_lock(&ep->mtx); + if (((rv = wss_get_tls(ep, &tls)) != 0) || + ((rv = nng_tls_config_server_name(tls, v)) != 0)) { + goto done; + } +done: nni_mtx_unlock(&ep->mtx); return (rv); } @@ -860,7 +945,26 @@ static nni_tran_ep_option wss_ep_options[] = { .eo_getopt = wss_ep_getopt_tlsconfig, .eo_setopt = wss_ep_setopt_tlsconfig, }, - + { + .eo_name = NNG_OPT_WSS_TLS_CERT_KEY_FILE, + .eo_getopt = NULL, + .eo_setopt = wss_ep_setopt_tls_cert_key_file, + }, + { + .eo_name = NNG_OPT_WSS_TLS_CA_FILE, + .eo_getopt = NULL, + .eo_setopt = wss_ep_setopt_tls_ca_file, + }, + { + .eo_name = NNG_OPT_WSS_TLS_AUTH_MODE, + .eo_getopt = NULL, + .eo_setopt = wss_ep_setopt_tls_auth_mode, + }, + { + .eo_name = NNG_OPT_WSS_TLS_SERVER_NAME, + .eo_getopt = NULL, + .eo_setopt = wss_ep_setopt_tls_server_name, + }, // terminate list { NULL, NULL, NULL }, }; diff --git a/src/transport/ws/websocket.h b/src/transport/ws/websocket.h index a0d8a6cc..1f261067 100644 --- a/src/transport/ws/websocket.h +++ b/src/transport/ws/websocket.h @@ -35,6 +35,48 @@ NNG_DECL int nng_ws_register(void); // behavior. #define NNG_OPT_WSS_TLS_CONFIG "wss:tls-config" +// NNG_OPT_WSS_TLS_CERT_KEY_FILE names a single file that +// contains a certificate and key identifying ourself. This +// is a write-only value. Listeners can call this multiple +// times for different keys/certs corresponding to different +// algorithms, whereas clients only get one. The file must +// contain both cert and key as PEM blocks, and the key must +// not be encrypted. (If more flexibility is needed, use the +// TLS configuration directly.) Note that TLS configuration +// cannot be changed if the listener, or any other from the same +// server and port, is already started. +#define NNG_OPT_WSS_TLS_CERT_KEY_FILE "wss:tls-cert-key-file" + +// NNG_OPT_WSS_TLS_CA_FILE names a single file that +// contains certificate(s) for a CA, and optinally CRLs. This +// is a write-only value. Listeners can call this multiple +// times for different keys/certs corresponding to different +// algorithms, whereas clients only get one. The file must +// contain certs as PEM blocks, and may contain CRLs as PEM +// as well. (If more flexibility is needed, use the +// TLS configuration directly.) Note that TLS configuration +// cannot be changed if the listener, or any other from the same +// server and port, is already started. +#define NNG_OPT_WSS_TLS_CA_FILE "wss:tls-ca-file" + +// NNG_OPT_WSS_TLS_AUTH_MODE is a write-only integer (int) option +// that specifies whether the peer is verified or not. The option +// can take one of the values of NNG_TLS_AUTH_MODE_NONE, +// NNG_TLS_AUTH_MODE_OPTIONAL, or NNG_TLS_AUTH_MODE_REQUIRED. +// The default is NNG_TLS_AUTH_MODE_NONE for listeners, and +// NNG_TLS_AUTH_MODE_REQUIRED for dialers. +#define NNG_OPT_WSS_TLS_AUTH_MODE "wss:tls-auth-mode" + +// NNG_OPT_WSS_TLS_SERVER_NAME is a write-only string that can be +// set on dialers to check the CN of the server for a match. This +// can also affect SNI (server name indication). +#define NNG_OPT_WSS_TLS_SERVER_NAME "wss:tls-server-name" + +// NNG_OPT_WSS_TLS_VERIFIED returns a single integer, indicating +// whether the peer was verified or not. This is a read-only value +// available only on pipes. +#define NNT_OPT_WSS_TLS_VERIFIED "wss:tls-verified" + // These aliases are for WSS naming consistency. #define NNG_OPT_WSS_REQUEST_HEADERS NNG_OPT_WS_REQUEST_HEADERS #define NNG_OPT_WSS_RESPONSE_HEADERS NNG_OPT_WS_RESPONSE_HEADERS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f6c9a62d..358e9d00 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -161,6 +161,7 @@ add_nng_test(udp 5 ON) add_nng_test(url 5 ON) add_nng_test(ws 30 NNG_TRANSPORT_WS) add_nng_test(wss 30 NNG_TRANSPORT_WSS) +add_nng_test(wssfile 30 NNG_TRANSPORT_WSS) add_nng_test(zt 60 NNG_TRANSPORT_ZEROTIER) add_nng_proto_test(bus 5 NNG_PROTO_BUS0 NNG_PROTO_BUS0) diff --git a/tests/httpclient.c b/tests/httpclient.c index e377d334..76a3566b 100644 --- a/tests/httpclient.c +++ b/tests/httpclient.c @@ -1,5 +1,5 @@ // -// Copyright 2018 Garrett D'Amore <garrett@damore.org> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a @@ -32,21 +32,13 @@ TestMain("HTTP Client", { Convey("Given a TCP connection to httpbin.org", { nng_aio * aio; nni_aio * iaio; - nng_sockaddr rsa; nni_http_client *cli; nni_http * http; So(nng_aio_alloc(&aio, NULL, NULL) == 0); - iaio = (nni_aio *) aio; - iaio->a_addr = &rsa; + iaio = (nni_aio *) aio; - nng_aio_set_timeout(aio, 20000); - nni_plat_tcp_resolv("httpbin.org", "80", NNG_AF_INET, 0, iaio); - nng_aio_wait(aio); - So(nng_aio_result(aio) == 0); - So(rsa.s_un.s_in.sa_port == htons(80)); - - So(nni_http_client_init(&cli, &rsa) == 0); + So(nni_http_client_init(&cli, "http://httpbin.org") == 0); nni_http_client_connect(cli, iaio); nng_aio_wait(aio); So(nng_aio_result(aio) == 0); diff --git a/tests/httpserver.c b/tests/httpserver.c index fa2753ea..147d0532 100644 --- a/tests/httpserver.c +++ b/tests/httpserver.c @@ -1,6 +1,6 @@ // -// Copyright 2017 Garrett D'Amore <garrett@damore.org> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Garrett D'Amore <garrett@damore.org> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -41,17 +41,20 @@ TestMain("HTTP Client", { nng_sockaddr sa; nni_aio * aio; char portbuf[16]; + char url[32]; char *doc = "<html><body>Someone <b>is</b> home!</body</html>"; trantest_next_address(portbuf, "%u"); + snprintf(url, sizeof(url), "http://127.0.0.1:%s", portbuf); + So(nni_aio_init(&aio, NULL, NULL) == 0); aio->a_addr = &sa; nni_plat_tcp_resolv("127.0.0.1", portbuf, NNG_AF_INET, 0, aio); nni_aio_wait(aio); So(nni_aio_result(aio) == 0); - So(nni_http_server_init(&s, &sa) == 0); + So(nni_http_server_init(&s, url) == 0); Reset({ nni_aio_fini(aio); @@ -68,7 +71,7 @@ TestMain("HTTP Client", { nni_http_req * req; nni_http_res * res; - So(nni_http_client_init(&cli, &sa) == 0); + So(nni_http_client_init(&cli, url) == 0); nni_http_client_connect(cli, aio); nni_aio_wait(aio); diff --git a/tests/trantest.h b/tests/trantest.h index 205a54b1..c85b3429 100644 --- a/tests/trantest.h +++ b/tests/trantest.h @@ -217,6 +217,7 @@ trantest_conn_refused(trantest *tt) Convey("Connection refused works", { nng_dialer d = 0; + int rv = trantest_dial(tt, &d); So(trantest_dial(tt, &d) == NNG_ECONNREFUSED); So(d == 0); So(trantest_dial(tt, &d) == NNG_ECONNREFUSED); @@ -80,7 +80,7 @@ TestMain("WebSocket Transport", { trantest_test_extended("ws://127.0.0.1:%u/test", check_props_v4); - Convey("Wild cards work", { + Convey("Empty hostname works", { nng_socket s1; nng_socket s2; char addr[NNG_MAXADDRLEN]; @@ -91,7 +91,7 @@ TestMain("WebSocket Transport", { nng_close(s2); nng_close(s1); }); - trantest_next_address(addr, "ws://*:%u/test"); + trantest_next_address(addr, "ws://:%u/test"); So(nng_listen(s1, addr, NULL, 0) == 0); nng_msleep(100); // reset port back one @@ -110,10 +110,10 @@ TestMain("WebSocket Transport", { nng_close(s2); nng_close(s1); }); - trantest_next_address(addr, "ws://*:%u/test"); + trantest_next_address(addr, "ws://:%u/test"); So(nng_listen(s1, addr, NULL, 0) == 0); // reset port back one - trantest_prev_address(addr, "ws://127.0.0.1:%u/nothere"); + trantest_prev_address(addr, "ws://localhost:%u/nothere"); So(nng_dial(s2, addr, NULL, 0) == NNG_ECONNREFUSED); }); diff --git a/tests/wss.c b/tests/wss.c index 00d37621..c087ed1e 100644 --- a/tests/wss.c +++ b/tests/wss.c @@ -27,50 +27,110 @@ // // Generated using openssl: // -// % openssl ecparam -name secp521r1 -noout -genkey -out key.key -// % openssl req -new -key key.key -out cert.csr -// % openssl x509 -req -in cert.csr -days 36500 -out cert.crt -signkey key.key +// % openssl rsa -genkey -out key.key +// % openssl req -new -key key.key -out cert.csr -sha256 +// % openssl x509 -req -in cert.csr -days 36500 -out cert.crt +// -signkey key.key -sha256 // // Relevant metadata: // // Certificate: -// Data: +// Data: // Version: 1 (0x0) -// Serial Number: 9808857926806240008 (0x882010509b8f7b08) -// Signature Algorithm: ecdsa-with-SHA1 -// Issuer: C=US, ST=CA, L=San Diego, O=nanomsg, CN=127.0.0.1 +// Serial Number: 17127835813110005400 (0xedb24becc3a2be98) +// Signature Algorithm: sha256WithRSAEncryption +// Issuer: C=US, ST=CA, L=San Diego, O=nanomsg.org, CN=localhost // Validity -// Not Before: Nov 17 20:08:06 2017 GMT -// Not After : Oct 24 20:08:06 2117 GMT -// Subject: C=US, ST=CA, L=San Diego, O=nanomsg, CN=127.0.0.1 +// Not Before: Jan 11 22:34:35 2018 GMT +// Not After : Dec 18 22:34:35 2117 GMT +// Subject: C=US, ST=CA, L=San Diego, O=nanomsg.org, CN=localhost +// Subject Public Key Info: +// Public Key Algorithm: rsaEncryption +// Public-Key: (2048 bit) // static const char cert[] = "-----BEGIN CERTIFICATE-----\n" - "MIICIjCCAYMCCQDaC9ARg31kIjAKBggqhkjOPQQDAjBUMQswCQYDVQQGEwJVUzEL\n" - "MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNhbiBEaWVnbzEQMA4GA1UECgwHbmFub21z\n" - "ZzESMBAGA1UEAwwJMTI3LjAuMC4xMCAXDTE3MTExNzIwMjczMloYDzIxMTcxMDI0\n" - "MjAyNzMyWjBUMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNh\n" - "biBEaWVnbzEQMA4GA1UECgwHbmFub21zZzESMBAGA1UEAwwJMTI3LjAuMC4xMIGb\n" - "MBAGByqGSM49AgEGBSuBBAAjA4GGAAQAN7vDK6GEiSguMsOuhfOvGyiVc37Sog0b\n" - "UkpaiS6+SagTmXFSN1Rgh9isxKFYJvcCtAko3v0I8rAVQucdhf5B3hEBMQlbBIuM\n" - "rMKT6ZQJ+eiwyb4O3Scgd7DoL3tc/kOqijwB/5hJ4sZdquDKP5DDFe5fAf4MNtzY\n" - "4C+iApWlKq/LoXkwCgYIKoZIzj0EAwIDgYwAMIGIAkIBOuJAWmNSdd6Ovmr6Ebg3\n" - "UF9ZrsNwARd9BfYbBk5OQhUOjCLB6d8aLi49WOm1WoRvOS5PaVvmvSfNhaw8b5nV\n" - "hnYCQgC+EmJ6C3bEcZrndhfbqvCaOGkc7/SrKhC6fS7mJW4wL90QUV9WjQ2Ll6X5\n" - "PxkSj7s0SvD6T8j7rju5LDgkdZc35A==\n" + "MIIDLjCCAhYCCQDtskvsw6K+mDANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJV\n" + "UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNhbiBEaWVnbzEUMBIGA1UECgwLbmFu\n" + "b21zZy5vcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0xODAxMTEyMjM0MzVaGA8y\n" + "MTE3MTIxODIyMzQzNVowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYD\n" + "VQQHDAlTYW4gRGllZ28xFDASBgNVBAoMC25hbm9tc2cub3JnMRIwEAYDVQQDDAls\n" + "b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMvoHdEnfO\n" + "hmG3PTj6YC5qz6N5hgmcwf4EZkor4+R1Q5hDOKqOknWmVuGBD5mA61ObK76vycIT\n" + "Tp+H+vKvfgunySZrlyYg8IbgoDbvVgj9RF8xFHdN0PVeqnkBCsCzLtSu6TP8PSgI\n" + "SKiRMH0NUSakWqCPEc2E1r1CKdOpa7av/Na30LPsuKFcAUhu7QiVYfER86ktrO8G\n" + "F2PeVy44Q8RkiLw8uhU0bpAflqkR1KCjOLajw1eL3C+Io75Io8qUOLxWc3LH0hl3\n" + "oEI0jWu7JYlRAw/O7xm4pcGTwy5L8Odz4a7ZTAmuapFRarGOIcDg8Yr0tllRd1mH\n" + "1T4Z2Wv7Rs0tAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIfUXK7UonrYAOrlXUHH\n" + "gfHNdOXMzQP2Ms6Sxov+1tCTfgsYE65Mggo7hRJUqmKpstpbdRBVXhTyht/xjyTz\n" + "5sMjoeCyv1tXOHpLTfD3LBXwYZwsFdoLS1UHhD3qiYjCyyY2LWa6S786CtlcbCvu\n" + "Uij2q8zJ4WFrNqAzxZtsTfg16/6JRFw9zpVSCNlHqCxNQxzWucbmUFTiWn9rnc/N\n" + "r7utG4JsDPZbEI6QS43R7gGLDF7s0ftWKqzlQiZEtuDQh2p7Uejbft8XmZd/VuV/\n" + "dFMXOO1rleU0lWAJcXWOWHH3er0fivu2ISL8fRjjikYvhRGxtkwC0kPDa2Ntzgd3\n" + "Hsg=\n" "-----END CERTIFICATE-----\n"; - static const char key[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "MIHcAgEBBEIB20OHMntU2UJW2yuQn2f+bLsuhTT5KRGorcocnqxatWLvxuF1cfUA\n" - "TjQxRRS6BIUvFt1fMIklp9qedJF00JHy4qWgBwYFK4EEACOhgYkDgYYABAA3u8Mr\n" - "oYSJKC4yw66F868bKJVzftKiDRtSSlqJLr5JqBOZcVI3VGCH2KzEoVgm9wK0CSje\n" - "/QjysBVC5x2F/kHeEQExCVsEi4yswpPplAn56LDJvg7dJyB3sOgve1z+Q6qKPAH/\n" - "mEnixl2q4Mo/kMMV7l8B/gw23NjgL6IClaUqr8uheQ==\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEAzL6B3RJ3zoZhtz04+mAuas+jeYYJnMH+BGZKK+PkdUOYQziq\n" + "jpJ1plbhgQ+ZgOtTmyu+r8nCE06fh/ryr34Lp8kma5cmIPCG4KA271YI/URfMRR3\n" + "TdD1Xqp5AQrAsy7Urukz/D0oCEiokTB9DVEmpFqgjxHNhNa9QinTqWu2r/zWt9Cz\n" + "7LihXAFIbu0IlWHxEfOpLazvBhdj3lcuOEPEZIi8PLoVNG6QH5apEdSgozi2o8NX\n" + "i9wviKO+SKPKlDi8VnNyx9IZd6BCNI1ruyWJUQMPzu8ZuKXBk8MuS/Dnc+Gu2UwJ\n" + "rmqRUWqxjiHA4PGK9LZZUXdZh9U+Gdlr+0bNLQIDAQABAoIBAC82HqvjfkzZH98o\n" + "9uKFGy72AjQbfEvxT6mkDKZiPmPr2khl4K5Ph2F71zPzbOoVWYoGZEoUs/PPxWmN\n" + "rDhbUES4VWupxtkBnZheWUyHAjukcG7Y0UnYTTwvAwgCerzWp6RNkfcwAvMmDfis\n" + "vak8dTSg0TUsXb+r5KhFDNGcTNv3f7R0cJmaZ/t9FT7SerXf1LW7itvTjRor8/ZK\n" + "KPwT4oklp1o6RFXSenn/e2e3rAjI+TEwJA3Zp5dqO/M/AhaZKVaxL4voDVdVVkT+\n" + "LHJWVhjLY5ilPkmPWqmZ2reTaF+gGSSjAQ+t/ahGWFqEdWIz9UoXhBBOd1ibeyvd\n" + "Kyxp1QECgYEA8KcDkmwPrhqFlQe/U+Md27OhrQ4cecLCa6EVLsCXN1bFyCi3NSo2\n" + "o5zFCC699KOL0ZwSmYlaQP4xjnqv4Gsa0s3uL7tqOJR2UuEtGK/MPMluGHVaWsGt\n" + "zbnWH3xgsvvsxdt6hInFhcABLDupW336tJ8EcH7mOKoIP+azwF4kPiUCgYEA2c09\n" + "zJBUW6SZXhgJ5vgENYc+UwDT7pfhIWZaRL+wXnwSoa7igodTKJtQp/KfFBJK4RA0\n" + "prvwj4Wr/1ScaboR2hYZApbqXU5zkEkjC1hHIbg1fBe0EcnhP7ojMXrk6B5ed+Lq\n" + "OVdYhUuvtdL/perelmbTJLnb8S214+tzVyg7EGkCgYEA6JLwX8zxpnhZSztOjBr9\n" + "2zuSb7YojQBNd0kZOLLGMaQ5xwSactYWMi8rOIo76Lc6RFxKmXnl8NP5PtKRMRkx\n" + "tjNxE05UDNRmOhkGxUn433JoZVjc9sMhXqZQKuPAbJoOLPW9RWQEsgtq1r3eId7x\n" + "sSfRWYs6od6p1F/4rlwNOMUCgYEAtJmqf+DCAoe3IL3gICRSISy28k7CbZqE9JQR\n" + "j+Y/Uemh7W29pyydOROoysq1PAh7DKrKbeNzcx8NYxh+5nCC8wrVzD7lsV8nFmJ+\n" + "655UxVIhD3f8Oa/j1lr7acEU5KCiBtkjDU8vOMBsv+FpWOQrlB1JQa/X/+G+bHLF\n" + "XmUerNkCgYEAv7R8vIKgJ1f69imgHdB31kue3wnOO/6NlfY3GTcaZcTdChY8SZ5B\n" + "xits8xog0VcaxXhWlfO0hyCnZ9YRQbyDu0qp5eBU2p3qcE01x4ljJBZUOTweG06N\n" + "cL9dYcwse5FhNMjrQ/OKv6B38SIXpoKQUtjgkaMtmpK8cXX1eqEMNkM=\n" + "-----END RSA PRIVATE KEY-----\n"; + +static int +validloopback(nng_sockaddr *sa) +{ + char ipv6[16]; + memset(ipv6, 0, sizeof(ipv6)); + ipv6[15] = 1; + + switch (sa->s_un.s_family) { + case NNG_AF_INET: + if (sa->s_un.s_in.sa_port == 0) { + return (0); + } + if (sa->s_un.s_in.sa_addr != htonl(0x7f000001)) { + return (0); + } + return (1); + + case NNG_AF_INET6: + if (sa->s_un.s_in6.sa_port == 0) { + return (0); + } + if (memcmp(sa->s_un.s_in6.sa_addr, ipv6, sizeof(ipv6)) != 0) { + return (0); + } + return (1); + + default: + return (0); + } +} static int -check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d) +check_props(nng_msg *msg, nng_listener l, nng_dialer d) { nng_pipe p; size_t z; @@ -85,17 +145,12 @@ check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d) z = sizeof(nng_sockaddr); So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0); So(z == sizeof(la)); - So(la.s_un.s_family == NNG_AF_INET); - So(la.s_un.s_in.sa_port == htons(trantest_port - 1)); - So(la.s_un.s_in.sa_port != 0); - So(la.s_un.s_in.sa_addr == htonl(0x7f000001)); + So(validloopback(&la)); z = sizeof(nng_sockaddr); So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0); So(z == sizeof(ra)); - So(ra.s_un.s_family == NNG_AF_INET); - So(ra.s_un.s_in.sa_port != 0); - So(ra.s_un.s_in.sa_addr == htonl(0x7f000001)); + So(validloopback(&ra)); // Request header z = 0; @@ -136,10 +191,13 @@ init_dialer_wss(trantest *tt, nng_dialer d) if ((rv = nng_tls_config_ca_chain(cfg, cert, NULL)) != 0) { goto out; } - if ((rv = nng_tls_config_server_name(cfg, "127.0.0.1")) != 0) { + if ((rv = nng_tls_config_server_name(cfg, "localhost")) != 0) { + goto out; + } + if ((rv = nng_tls_config_auth_mode(cfg, NNG_TLS_AUTH_MODE_REQUIRED)) != + 0) { goto out; } - nng_tls_config_auth_mode(cfg, NNG_TLS_AUTH_MODE_NONE); rv = nng_dialer_setopt_ptr(d, NNG_OPT_WSS_TLS_CONFIG, cfg); out: @@ -174,12 +232,13 @@ out: } TestMain("WebSocket Secure (TLS) Transport", { + static trantest tt; tt.dialer_init = init_dialer_wss; tt.listener_init = init_listener_wss; - tt.tmpl = "wss://127.0.0.1:%u/test"; - tt.proptest = check_props_v4; + tt.tmpl = "wss://localhost:%u/test"; + tt.proptest = check_props; trantest_test(&tt); diff --git a/tests/wssfile.c b/tests/wssfile.c new file mode 100644 index 00000000..120e575d --- /dev/null +++ b/tests/wssfile.c @@ -0,0 +1,329 @@ +// +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "convey.h" +#include "nng.h" +#include "protocol/pair1/pair.h" +#include "transport/ws/websocket.h" +#include "trantest.h" + +#include "stubs.h" +// TCP tests. + +#ifndef _WIN32 +#include <arpa/inet.h> +#endif + +// These keys are for demonstration purposes ONLY. DO NOT USE. +// The certificate is valid for 100 years, because I don't want to +// have to regenerate it ever again. The CN is 127.0.0.1, and self-signed. +// +// Generated using openssl: +// +// % openssl rsa -genkey -out key.key +// % openssl req -new -key key.key -out cert.csr -sha256 +// % openssl x509 -req -in cert.csr -days 36500 -out cert.crt +// -signkey key.key -sha256 +// +// Relevant metadata: +// +// Certificate: +// Data: +// Version: 1 (0x0) +// Serial Number: 17127835813110005400 (0xedb24becc3a2be98) +// Signature Algorithm: sha256WithRSAEncryption +// Issuer: C=US, ST=CA, L=San Diego, O=nanomsg.org, CN=localhost +// Validity +// Not Before: Jan 11 22:34:35 2018 GMT +// Not After : Dec 18 22:34:35 2117 GMT +// Subject: C=US, ST=CA, L=San Diego, O=nanomsg.org, CN=localhost +// Subject Public Key Info: +// Public Key Algorithm: rsaEncryption +// Public-Key: (2048 bit) +// +static const char cert[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDLjCCAhYCCQDtskvsw6K+mDANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJV\n" + "UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNhbiBEaWVnbzEUMBIGA1UECgwLbmFu\n" + "b21zZy5vcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0xODAxMTEyMjM0MzVaGA8y\n" + "MTE3MTIxODIyMzQzNVowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYD\n" + "VQQHDAlTYW4gRGllZ28xFDASBgNVBAoMC25hbm9tc2cub3JnMRIwEAYDVQQDDAls\n" + "b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMvoHdEnfO\n" + "hmG3PTj6YC5qz6N5hgmcwf4EZkor4+R1Q5hDOKqOknWmVuGBD5mA61ObK76vycIT\n" + "Tp+H+vKvfgunySZrlyYg8IbgoDbvVgj9RF8xFHdN0PVeqnkBCsCzLtSu6TP8PSgI\n" + "SKiRMH0NUSakWqCPEc2E1r1CKdOpa7av/Na30LPsuKFcAUhu7QiVYfER86ktrO8G\n" + "F2PeVy44Q8RkiLw8uhU0bpAflqkR1KCjOLajw1eL3C+Io75Io8qUOLxWc3LH0hl3\n" + "oEI0jWu7JYlRAw/O7xm4pcGTwy5L8Odz4a7ZTAmuapFRarGOIcDg8Yr0tllRd1mH\n" + "1T4Z2Wv7Rs0tAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIfUXK7UonrYAOrlXUHH\n" + "gfHNdOXMzQP2Ms6Sxov+1tCTfgsYE65Mggo7hRJUqmKpstpbdRBVXhTyht/xjyTz\n" + "5sMjoeCyv1tXOHpLTfD3LBXwYZwsFdoLS1UHhD3qiYjCyyY2LWa6S786CtlcbCvu\n" + "Uij2q8zJ4WFrNqAzxZtsTfg16/6JRFw9zpVSCNlHqCxNQxzWucbmUFTiWn9rnc/N\n" + "r7utG4JsDPZbEI6QS43R7gGLDF7s0ftWKqzlQiZEtuDQh2p7Uejbft8XmZd/VuV/\n" + "dFMXOO1rleU0lWAJcXWOWHH3er0fivu2ISL8fRjjikYvhRGxtkwC0kPDa2Ntzgd3\n" + "Hsg=\n" + "-----END CERTIFICATE-----\n"; +static const char key[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEAzL6B3RJ3zoZhtz04+mAuas+jeYYJnMH+BGZKK+PkdUOYQziq\n" + "jpJ1plbhgQ+ZgOtTmyu+r8nCE06fh/ryr34Lp8kma5cmIPCG4KA271YI/URfMRR3\n" + "TdD1Xqp5AQrAsy7Urukz/D0oCEiokTB9DVEmpFqgjxHNhNa9QinTqWu2r/zWt9Cz\n" + "7LihXAFIbu0IlWHxEfOpLazvBhdj3lcuOEPEZIi8PLoVNG6QH5apEdSgozi2o8NX\n" + "i9wviKO+SKPKlDi8VnNyx9IZd6BCNI1ruyWJUQMPzu8ZuKXBk8MuS/Dnc+Gu2UwJ\n" + "rmqRUWqxjiHA4PGK9LZZUXdZh9U+Gdlr+0bNLQIDAQABAoIBAC82HqvjfkzZH98o\n" + "9uKFGy72AjQbfEvxT6mkDKZiPmPr2khl4K5Ph2F71zPzbOoVWYoGZEoUs/PPxWmN\n" + "rDhbUES4VWupxtkBnZheWUyHAjukcG7Y0UnYTTwvAwgCerzWp6RNkfcwAvMmDfis\n" + "vak8dTSg0TUsXb+r5KhFDNGcTNv3f7R0cJmaZ/t9FT7SerXf1LW7itvTjRor8/ZK\n" + "KPwT4oklp1o6RFXSenn/e2e3rAjI+TEwJA3Zp5dqO/M/AhaZKVaxL4voDVdVVkT+\n" + "LHJWVhjLY5ilPkmPWqmZ2reTaF+gGSSjAQ+t/ahGWFqEdWIz9UoXhBBOd1ibeyvd\n" + "Kyxp1QECgYEA8KcDkmwPrhqFlQe/U+Md27OhrQ4cecLCa6EVLsCXN1bFyCi3NSo2\n" + "o5zFCC699KOL0ZwSmYlaQP4xjnqv4Gsa0s3uL7tqOJR2UuEtGK/MPMluGHVaWsGt\n" + "zbnWH3xgsvvsxdt6hInFhcABLDupW336tJ8EcH7mOKoIP+azwF4kPiUCgYEA2c09\n" + "zJBUW6SZXhgJ5vgENYc+UwDT7pfhIWZaRL+wXnwSoa7igodTKJtQp/KfFBJK4RA0\n" + "prvwj4Wr/1ScaboR2hYZApbqXU5zkEkjC1hHIbg1fBe0EcnhP7ojMXrk6B5ed+Lq\n" + "OVdYhUuvtdL/perelmbTJLnb8S214+tzVyg7EGkCgYEA6JLwX8zxpnhZSztOjBr9\n" + "2zuSb7YojQBNd0kZOLLGMaQ5xwSactYWMi8rOIo76Lc6RFxKmXnl8NP5PtKRMRkx\n" + "tjNxE05UDNRmOhkGxUn433JoZVjc9sMhXqZQKuPAbJoOLPW9RWQEsgtq1r3eId7x\n" + "sSfRWYs6od6p1F/4rlwNOMUCgYEAtJmqf+DCAoe3IL3gICRSISy28k7CbZqE9JQR\n" + "j+Y/Uemh7W29pyydOROoysq1PAh7DKrKbeNzcx8NYxh+5nCC8wrVzD7lsV8nFmJ+\n" + "655UxVIhD3f8Oa/j1lr7acEU5KCiBtkjDU8vOMBsv+FpWOQrlB1JQa/X/+G+bHLF\n" + "XmUerNkCgYEAv7R8vIKgJ1f69imgHdB31kue3wnOO/6NlfY3GTcaZcTdChY8SZ5B\n" + "xits8xog0VcaxXhWlfO0hyCnZ9YRQbyDu0qp5eBU2p3qcE01x4ljJBZUOTweG06N\n" + "cL9dYcwse5FhNMjrQ/OKv6B38SIXpoKQUtjgkaMtmpK8cXX1eqEMNkM=\n" + "-----END RSA PRIVATE KEY-----\n"; + +static int +validloopback(nng_sockaddr *sa) +{ + char ipv6[16]; + memset(ipv6, 0, sizeof(ipv6)); + ipv6[15] = 1; + + switch (sa->s_un.s_family) { + case NNG_AF_INET: + if (sa->s_un.s_in.sa_port == 0) { + return (0); + } + if (sa->s_un.s_in.sa_addr != htonl(0x7f000001)) { + return (0); + } + return (1); + + case NNG_AF_INET6: + if (sa->s_un.s_in6.sa_port == 0) { + return (0); + } + if (memcmp(sa->s_un.s_in6.sa_addr, ipv6, sizeof(ipv6)) != 0) { + return (0); + } + return (1); + + default: + return (0); + } +} + +static int +check_props(nng_msg *msg, nng_listener l, nng_dialer d) +{ + nng_pipe p; + size_t z; + nng_sockaddr la; + nng_sockaddr ra; + char * buf; + size_t len; + + p = nng_msg_get_pipe(msg); + So(p > 0); + + z = sizeof(nng_sockaddr); + So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0); + So(z == sizeof(la)); + So(validloopback(&la)); + + z = sizeof(nng_sockaddr); + So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0); + So(z == sizeof(ra)); + So(validloopback(&ra)); + + // Request header + z = 0; + buf = NULL; + So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0); + So(z > 0); + len = z; + So((buf = nni_alloc(len)) != NULL); + So(nng_pipe_getopt(p, NNG_OPT_WS_REQUEST_HEADERS, buf, &z) == 0); + So(strstr(buf, "Sec-WebSocket-Key") != NULL); + So(z == len); + nni_free(buf, len); + + // Response header + z = 0; + buf = NULL; + So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0); + So(z > 0); + len = z; + So((buf = nni_alloc(len)) != NULL); + So(nng_pipe_getopt(p, NNG_OPT_WS_RESPONSE_HEADERS, buf, &z) == 0); + So(strstr(buf, "Sec-WebSocket-Accept") != NULL); + So(z == len); + nni_free(buf, len); + + return (0); +} + +static int +init_dialer_wss_file(trantest *tt, nng_dialer d) +{ + int rv; + char *tmpdir; + char *pth; + + if ((tmpdir = nni_plat_temp_dir()) == NULL) { + return (NNG_ENOTSUP); + } + if ((pth = nni_file_join(tmpdir, "wss_test_cacert.pem")) == NULL) { + nni_strfree(tmpdir); + return (NNG_ENOMEM); + } + nni_strfree(tmpdir); + + if ((rv = nni_file_put(pth, cert, strlen(cert))) != 0) { + nni_strfree(pth); + return (rv); + } + + rv = nng_dialer_setopt_string(d, NNG_OPT_WSS_TLS_CA_FILE, pth); + nni_file_delete(pth); + nni_strfree(pth); + + return (rv); +} + +static int +init_listener_wss_file(trantest *tt, nng_listener l) +{ + int rv; + char *tmpdir; + char *pth; + char *certkey; + + if ((tmpdir = nni_plat_temp_dir()) == NULL) { + return (NNG_ENOTSUP); + } + + if ((pth = nni_file_join(tmpdir, "wss_test_certkey.pem")) == NULL) { + nni_strfree(tmpdir); + return (NNG_ENOMEM); + } + nni_strfree(tmpdir); + + if ((rv = nni_asprintf(&certkey, "%s\r\n%s\r\n", cert, key)) != 0) { + nni_strfree(pth); + return (rv); + } + + rv = nni_file_put(pth, certkey, strlen(certkey)); + nni_strfree(certkey); + if (rv != 0) { + nni_strfree(pth); + return (rv); + } + + rv = nng_listener_setopt_string(l, NNG_OPT_WSS_TLS_CERT_KEY_FILE, pth); + if (rv != 0) { + // We can wind up with EBUSY from the server already + // running. + if (rv == NNG_EBUSY) { + rv = 0; + } + } + + nni_file_delete(pth); + nni_strfree(pth); + return (rv); +} + +TestMain("WebSocket Secure (TLS) Transport (file based)", { + + static trantest tt; + + tt.dialer_init = init_dialer_wss_file; + tt.listener_init = init_listener_wss_file; + tt.tmpl = "wss://localhost:%u/test"; + tt.proptest = check_props; + + trantest_test(&tt); + + Convey("Verify works", { + nng_socket s1; + nng_socket s2; + nng_listener l; + char * buf; + size_t sz; + char addr[NNG_MAXADDRLEN]; + + So(nng_pair_open(&s1) == 0); + So(nng_pair_open(&s2) == 0); + Reset({ + nng_close(s2); + nng_close(s1); + }); + trantest_next_address(addr, "wss://:%u/test"); + So(nng_listener_create(&l, s1, addr) == 0); + So(init_listener_wss_file(NULL, l) == 0); + So(nng_listener_start(l, 0) == 0); + nng_msleep(100); + + // reset port back one + trantest_prev_address(addr, "wss://127.0.0.1:%u/test"); + So(nng_setopt_int(s2, NNG_OPT_WSS_TLS_AUTH_MODE, + NNG_TLS_AUTH_MODE_REQUIRED) == 0); + + So(nng_dial(s2, addr, NULL, 0) == NNG_EPEERAUTH); + }); + + Convey("No verify works", { + nng_socket s1; + nng_socket s2; + nng_listener l; + char * buf; + size_t sz; + char addr[NNG_MAXADDRLEN]; + + So(nng_pair_open(&s1) == 0); + So(nng_pair_open(&s2) == 0); + Reset({ + nng_close(s2); + nng_close(s1); + }); + trantest_next_address(addr, "wss://:%u/test"); + So(nng_listener_create(&l, s1, addr) == 0); + So(init_listener_wss_file(NULL, l) == 0); + So(nng_listener_start(l, 0) == 0); + nng_msleep(100); + + // reset port back one + trantest_prev_address(addr, "wss://127.0.0.1:%u/test"); + So(nng_setopt_int(s2, NNG_OPT_WSS_TLS_AUTH_MODE, + NNG_TLS_AUTH_MODE_NONE) == 0); + So(nng_setopt_ms(s2, NNG_OPT_RECVTIMEO, 200) == 0); + So(nng_dial(s2, addr, NULL, 0) == 0); + nng_msleep(100); + + So(nng_send(s1, "hello", 6, 0) == 0); + So(nng_recv(s2, &buf, &sz, NNG_FLAG_ALLOC) == 0); + So(sz == 6); + So(strcmp(buf, "hello") == 0); + nng_free(buf, sz); + }); + + nng_fini(); +}) |
