diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-01-11 14:58:09 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-01-16 08:45:11 -0800 |
| commit | bbf012364d9f1482b16c97b8bfd2fd07130446ca (patch) | |
| tree | 2cb45903b0d5aa756d44f27b39a99c318a99a9a2 | |
| parent | 18229bbb69423d64d0a1b98bcf4bf3e24fba3aa4 (diff) | |
| download | nng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.tar.gz nng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.tar.bz2 nng-bbf012364d9f1482b16c97b8bfd2fd07130446ca.zip | |
fixes #201 TLS configuration should support files for certificates and keys
This adds support for configuration of TLS websockets using the files
for keys, certificates, and CRLs. Significant changes to the websocket,
TLS, and HTTP layers were made here. We now expect TLS configuration to
be tied to the HTTP layer, and the HTTP code creates default configuration
objects based on the URL supplied. (HTTP dialers and listeners are now
created with a URL rather than a sockaddr, giving them access to the scheme
as well.)
We fixed several bugs affecting TLS validation, and added a test suite
that confirms that validation works as it should. We also fixed an orphaned
socket during HTTP negotiation, responsible for an occasional assertion
error if the http handshake does not complete successfully. Finally several
use-after-free races were closed.
TLS layer changes include reporting of handshake failures using newly
created "standard" error codes for peer authentication and cryptographic
failures.
The use of the '*' wild card in URLs at bind time is no longer supported
for websocket at least.
Documentation updates for all this are in place as well.
| -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(); +}) |
