Compare commits
	
		
			11 Commits
		
	
	
		
			v7.6.0
			...
			feature/ma
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					84361c16a9 | ||
| 
						 | 
					d72e5e70f6 | ||
| 
						 | 
					e2c5f751bd | ||
| 
						 | 
					351b86e266 | ||
| 
						 | 
					d0cbff4f4e | ||
| 
						 | 
					cbfc9b9f94 | ||
| 
						 | 
					ca816d801f | ||
| 
						 | 
					2f354d31eb | ||
| 
						 | 
					2c6c1edd37 | ||
| 
						 | 
					9799e7e84b | ||
| 
						 | 
					81be970679 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,7 @@
 | 
			
		||||
build
 | 
			
		||||
*.pyc
 | 
			
		||||
venv
 | 
			
		||||
ixsnake/ixsnake/.certs/
 | 
			
		||||
site/
 | 
			
		||||
ws/.certs/
 | 
			
		||||
ws/.srl
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM alpine:edge as build
 | 
			
		||||
FROM alpine:3.11 as build
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache gcc g++ musl-dev linux-headers cmake openssl-dev 
 | 
			
		||||
RUN apk add --no-cache make
 | 
			
		||||
@@ -16,7 +16,7 @@ WORKDIR /opt
 | 
			
		||||
USER app
 | 
			
		||||
RUN [ "make", "ws_install" ]
 | 
			
		||||
 | 
			
		||||
FROM alpine:edge as runtime
 | 
			
		||||
FROM alpine:3.11 as runtime
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache libstdc++
 | 
			
		||||
RUN apk add --no-cache strace
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,24 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
All notable changes to this project will be documented in this file.
 | 
			
		||||
All changes to this project will be documented in this file.
 | 
			
		||||
 | 
			
		||||
## [7.6.4] - 2019-12-22
 | 
			
		||||
 | 
			
		||||
(client) error handling, quote url in error case when failing to parse one
 | 
			
		||||
(ws) ws_cobra_publish: register callbacks before connecting
 | 
			
		||||
(doc) mention mbedtls in supported ssl server backend
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [7.6.3] - 2019-12-20
 | 
			
		||||
 | 
			
		||||
(tls) add a simple description of the TLS configuration routine for debugging
 | 
			
		||||
 | 
			
		||||
## [7.6.2] - 2019-12-20
 | 
			
		||||
 | 
			
		||||
(mbedtls) correct support for using own certificate and private key
 | 
			
		||||
 | 
			
		||||
## [7.6.1] - 2019-12-20
 | 
			
		||||
 | 
			
		||||
(ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11
 | 
			
		||||
 | 
			
		||||
## [7.6.0] - 2019-12-19
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -247,7 +247,7 @@ Options:
 | 
			
		||||
 | 
			
		||||
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has several sub-command to interact with cobra. There is also a minimal cobra compatible server named snake available.
 | 
			
		||||
 | 
			
		||||
Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL backend for now).
 | 
			
		||||
Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL and the Mbed TLS backend for now).
 | 
			
		||||
 | 
			
		||||
First, generate certificates.
 | 
			
		||||
 | 
			
		||||
@@ -366,4 +366,4 @@ $ ws cobra_publish --endpoint wss://127.0.0.1:8765 --appkey FC2F10139A2BAc53BB72
 | 
			
		||||
[2019-12-19 20:46:42.659] [info] Published message id 3 acked
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew.
 | 
			
		||||
To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew. Use `make ws_mbedtls` accordingly to use MbedTLS.
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,8 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        _channel = channel;
 | 
			
		||||
 | 
			
		||||
        ix::IXCoreLogger::Log(socketTLSOptions.getDescription().c_str());
 | 
			
		||||
 | 
			
		||||
        ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(enablePerMessageDeflate);
 | 
			
		||||
        _cobra_connection.configure(appkey, endpoint,
 | 
			
		||||
                                    rolename, rolesecret,
 | 
			
		||||
 
 | 
			
		||||
@@ -130,6 +130,104 @@ namespace
 | 
			
		||||
        return errMsg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#undef CURL_BUILD_IOS
 | 
			
		||||
    OSStatus CopyIdentityFromPKCS12File(
 | 
			
		||||
        const char *cPath,
 | 
			
		||||
        const char *cPassword,
 | 
			
		||||
        SecIdentityRef *out_cert_and_key)
 | 
			
		||||
    {
 | 
			
		||||
        OSStatus status = errSecItemNotFound;
 | 
			
		||||
        CFURLRef pkcs_url = CFURLCreateFromFileSystemRepresentation(
 | 
			
		||||
            NULL, (const UInt8 *)cPath, strlen(cPath), false);
 | 
			
		||||
        CFStringRef password = cPassword ? CFStringCreateWithCString(NULL,
 | 
			
		||||
            cPassword, kCFStringEncodingUTF8) : NULL;
 | 
			
		||||
        CFDataRef pkcs_data = NULL;
 | 
			
		||||
 | 
			
		||||
        /* We can import P12 files on iOS or OS X 10.7 or later: */
 | 
			
		||||
        /* These constants are documented as having first appeared in 10.6 but they
 | 
			
		||||
           raise linker errors when used on that cat for some reason. */
 | 
			
		||||
        if (CFURLCreateDataAndPropertiesFromResource(
 | 
			
		||||
            NULL, pkcs_url, &pkcs_data, NULL, NULL, &status)) {
 | 
			
		||||
            CFArrayRef items = NULL;
 | 
			
		||||
 | 
			
		||||
            /* On iOS SecPKCS12Import will never add the client certificate to the
 | 
			
		||||
             * Keychain.
 | 
			
		||||
             *
 | 
			
		||||
             * It gives us back a SecIdentityRef that we can use directly. */
 | 
			
		||||
#if CURL_BUILD_IOS
 | 
			
		||||
            const void *cKeys[] = {kSecImportExportPassphrase};
 | 
			
		||||
            const void *cValues[] = {password};
 | 
			
		||||
            CFDictionaryRef options = CFDictionaryCreate(NULL, cKeys, cValues,
 | 
			
		||||
                    password ? 1L : 0L, NULL, NULL);
 | 
			
		||||
 | 
			
		||||
            if (options != NULL) {
 | 
			
		||||
                status = SecPKCS12Import(pkcs_data, options, &items);
 | 
			
		||||
                CFRelease(options);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* On macOS SecPKCS12Import will always add the client certificate to
 | 
			
		||||
             * the Keychain.
 | 
			
		||||
             *
 | 
			
		||||
             * As this doesn't match iOS, and apps may not want to see their client
 | 
			
		||||
             * certificate saved in the the user's keychain, we use SecItemImport
 | 
			
		||||
             * with a NULL keychain to avoid importing it.
 | 
			
		||||
             *
 | 
			
		||||
             * This returns a SecCertificateRef from which we can construct a
 | 
			
		||||
             * SecIdentityRef.
 | 
			
		||||
             */
 | 
			
		||||
#else
 | 
			
		||||
            SecItemImportExportKeyParameters keyParams;
 | 
			
		||||
            SecExternalFormat inputFormat = kSecFormatPKCS12;
 | 
			
		||||
            SecExternalItemType inputType = kSecItemTypeCertificate;
 | 
			
		||||
 | 
			
		||||
            memset(&keyParams, 0x00, sizeof(keyParams));
 | 
			
		||||
            keyParams.version    = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
 | 
			
		||||
            keyParams.passphrase = password;
 | 
			
		||||
 | 
			
		||||
            status = SecItemImport(pkcs_data, NULL, &inputFormat, &inputType,
 | 
			
		||||
                                   0, &keyParams, NULL, &items);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
            /* Extract the SecIdentityRef */
 | 
			
		||||
            if (status == errSecSuccess && items && CFArrayGetCount(items))
 | 
			
		||||
            {
 | 
			
		||||
                CFIndex i, count;
 | 
			
		||||
                count = CFArrayGetCount(items);
 | 
			
		||||
 | 
			
		||||
                for (i = 0; i < count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i);
 | 
			
		||||
                    CFTypeID  itemID = CFGetTypeID(item);
 | 
			
		||||
 | 
			
		||||
                    if (itemID == CFDictionaryGetTypeID())
 | 
			
		||||
                    {
 | 
			
		||||
                        CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue(
 | 
			
		||||
                                (CFDictionaryRef) item,
 | 
			
		||||
                                kSecImportItemIdentity);
 | 
			
		||||
                        CFRetain(identity);
 | 
			
		||||
                        *out_cert_and_key = (SecIdentityRef) identity;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (itemID == SecCertificateGetTypeID())
 | 
			
		||||
                    {
 | 
			
		||||
                        status = SecIdentityCreateWithCertificate(NULL,
 | 
			
		||||
                                (SecCertificateRef) item,
 | 
			
		||||
                                out_cert_and_key);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (items) CFRelease(items);
 | 
			
		||||
            CFRelease(pkcs_data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (password) CFRelease(password);
 | 
			
		||||
        CFRelease(pkcs_url);
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} // anonymous namespace
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
@@ -153,6 +251,63 @@ namespace ix
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SocketAppleSSL::handleTLSOptions(std::string& errMsg)
 | 
			
		||||
    {
 | 
			
		||||
        SecIdentityRef cert_and_key = NULL;
 | 
			
		||||
 | 
			
		||||
        const char* ssl_cert = _tlsOptions.certFile.c_str();
 | 
			
		||||
 | 
			
		||||
        OSStatus err = CopyIdentityFromPKCS12File(ssl_cert, "foobar", &cert_and_key);
 | 
			
		||||
 | 
			
		||||
        if (err == noErr && cert_and_key)
 | 
			
		||||
        {
 | 
			
		||||
            SecCertificateRef cert = NULL;
 | 
			
		||||
            CFTypeRef certs_c[1];
 | 
			
		||||
            CFArrayRef certs;
 | 
			
		||||
 | 
			
		||||
            err = SecIdentityCopyCertificate(cert_and_key, &cert);
 | 
			
		||||
 | 
			
		||||
            certs_c[0] = cert_and_key;
 | 
			
		||||
            certs = CFArrayCreate(NULL, (const void **)certs_c, 1L,
 | 
			
		||||
                    &kCFTypeArrayCallBacks);
 | 
			
		||||
            err = SSLSetCertificate(_sslContext, certs);
 | 
			
		||||
            if (err != noErr)
 | 
			
		||||
            {
 | 
			
		||||
                errMsg = "SSLSetCertificate failed";
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            switch(err) {
 | 
			
		||||
                case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */
 | 
			
		||||
                    errMsg = "SSL: Incorrect password for the certificate \"%s\" "
 | 
			
		||||
                            "and its private key."; // , ssl_cert);
 | 
			
		||||
                    break;
 | 
			
		||||
                case -26275: /* errSecDecode */ case -25257: /* errSecUnknownFormat */
 | 
			
		||||
                    errMsg = "SSL: Couldn't make sense of the data in the "
 | 
			
		||||
                            "certificate \"%s\" and its private key.";
 | 
			
		||||
                            ; // ssl_cert);
 | 
			
		||||
                    break;
 | 
			
		||||
                case -25260: /* errSecPassphraseRequired */
 | 
			
		||||
                    errMsg = "SSL The certificate \"%s\" requires a password.";
 | 
			
		||||
                            // ssl_cert);
 | 
			
		||||
                    break;
 | 
			
		||||
                case errSecItemNotFound:
 | 
			
		||||
                    errMsg = "SSL: Can't find the certificate \"%s\" and its private "
 | 
			
		||||
                            "key in the Keychain."; // , ssl_cert);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    errMsg = "SSL: Can't load the certificate \"%s\" and its private "
 | 
			
		||||
                            "key: OSStatus %d" ; // , ssl_cert, err);
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // No wait support
 | 
			
		||||
    bool SocketAppleSSL::connect(const std::string& host,
 | 
			
		||||
                                 int port,
 | 
			
		||||
@@ -173,6 +328,8 @@ namespace ix
 | 
			
		||||
            SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
 | 
			
		||||
            SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
 | 
			
		||||
 | 
			
		||||
            if (!handleTLSOptions(errMsg)) return false; // FIXME not calling close()
 | 
			
		||||
 | 
			
		||||
            if (_tlsOptions.isPeerVerifyDisabled())
 | 
			
		||||
            {
 | 
			
		||||
                Boolean option(1);
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,8 @@ namespace ix
 | 
			
		||||
        virtual ssize_t recv(void* buffer, size_t length) final;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        bool handleTLSOptions(std::string& errMsg);
 | 
			
		||||
 | 
			
		||||
        SSLContextRef _sslContext;
 | 
			
		||||
        mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -71,11 +71,16 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        if (_tlsOptions.hasCertAndKey())
 | 
			
		||||
        {
 | 
			
		||||
            if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.certFile.c_str()) < 0)
 | 
			
		||||
            if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0)
 | 
			
		||||
            {
 | 
			
		||||
                errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
 | 
			
		||||
            {
 | 
			
		||||
                errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (_tlsOptions.isPeerVerifyDisabled())
 | 
			
		||||
@@ -84,7 +89,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
 | 
			
		||||
            mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
 | 
			
		||||
 | 
			
		||||
            // FIXME: should we call mbedtls_ssl_conf_verify ?
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +102,13 @@ namespace ix
 | 
			
		||||
                errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
 | 
			
		||||
 | 
			
		||||
            mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
 | 
			
		||||
 | 
			
		||||
            if (_tlsOptions.hasCertAndKey())
 | 
			
		||||
            {
 | 
			
		||||
                mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ namespace ix
 | 
			
		||||
        mbedtls_ctr_drbg_context _ctr_drbg;
 | 
			
		||||
        mbedtls_x509_crt _cacert;
 | 
			
		||||
        mbedtls_x509_crt _cert;
 | 
			
		||||
        mbedtls_pk_context _pkey;
 | 
			
		||||
 | 
			
		||||
        std::mutex _mutex;
 | 
			
		||||
        SocketTLSOptions _tlsOptions;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
@@ -71,4 +72,16 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        return _errMsg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string SocketTLSOptions::getDescription() const
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << "TLS Options:" << std::endl;
 | 
			
		||||
        ss << "  certFile = " << certFile << std::endl;
 | 
			
		||||
        ss << "  keyFile  = " << keyFile << std::endl;
 | 
			
		||||
        ss << "  caFile   = " << caFile << std::endl;
 | 
			
		||||
        ss << "  ciphers  = " << ciphers << std::endl;
 | 
			
		||||
        ss << "  ciphers  = " << ciphers << std::endl;
 | 
			
		||||
        return ss.str();
 | 
			
		||||
    }
 | 
			
		||||
} // namespace ix
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,8 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        const std::string& getErrorMsg() const;
 | 
			
		||||
 | 
			
		||||
        std::string getDescription() const;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        mutable std::string _errMsg;
 | 
			
		||||
        mutable bool _validated = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,9 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        if (!UrlParser::parse(url, protocol, host, path, query, port))
 | 
			
		||||
        {
 | 
			
		||||
            return WebSocketInitResult(false, 0, std::string("Could not parse URL ") + url);
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "Could not parse url: '" << url << "'";
 | 
			
		||||
            return WebSocketInitResult(false, 0, ss.str());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string errorMsg;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#define IX_WEBSOCKET_VERSION "7.5.8"
 | 
			
		||||
#define IX_WEBSOCKET_VERSION "7.6.4"
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ namespace ix
 | 
			
		||||
                       rolesecret,
 | 
			
		||||
                       ix::WebSocketPerMessageDeflateOptions(true),
 | 
			
		||||
                       tlsOptions);
 | 
			
		||||
        conn.connect();
 | 
			
		||||
 | 
			
		||||
        // Display incoming messages
 | 
			
		||||
        std::atomic<bool> authenticated(false);
 | 
			
		||||
@@ -94,6 +93,8 @@ namespace ix
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        conn.connect();
 | 
			
		||||
 | 
			
		||||
        while (!authenticated)
 | 
			
		||||
            ;
 | 
			
		||||
        while (!messageAcked)
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
            // Server connection
 | 
			
		||||
            state->webSocket().setOnMessageCallback([webSocket, state, verbose](
 | 
			
		||||
                                                        const WebSocketMessagePtr& msg) {
 | 
			
		||||
                                                     const WebSocketMessagePtr& msg) {
 | 
			
		||||
                if (msg->type == ix::WebSocketMessageType::Open)
 | 
			
		||||
                {
 | 
			
		||||
                    std::cerr << "New connection" << std::endl;
 | 
			
		||||
@@ -120,6 +120,7 @@ namespace ix
 | 
			
		||||
                    std::string url(remoteUrl);
 | 
			
		||||
                    url += msg->openInfo.uri;
 | 
			
		||||
                    state->webSocket().setUrl(url);
 | 
			
		||||
                    state->webSocket().disableAutomaticReconnection();
 | 
			
		||||
                    state->webSocket().start();
 | 
			
		||||
 | 
			
		||||
                    // we should sleep here for a bit until we've established the
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user