public EpoxyTransport(
            ILayerStackProvider layerStackProvider,
            Func <string, Task <IPAddress> > resolver,
            EpoxyServerTlsConfig serverTlsConfig,
            EpoxyClientTlsConfig clientTlsConfig,
            TimeoutConfig timeoutConfig,
            ILogSink logSink, bool enableDebugLogs,
            IMetricsSink metricsSink)
        {
            // Layer stack provider may be null
            this.layerStackProvider = layerStackProvider;

            // Always need a resolver, so substitute in default if given null
            this.resolver = resolver ?? ResolveViaDnsAsync;

            // may be null - indicates no TLS for listeners
            this.serverTlsConfig = serverTlsConfig;

            // Client-side TLS is determined by how the connection is
            // established, so we substitute in the Default TLS config if we
            // happened to get null
            this.clientTlsConfig = clientTlsConfig ?? EpoxyClientTlsConfig.Default;

            this.timeoutConfig = timeoutConfig;

            // Log sink may be null
            logger = new Logger(logSink, enableDebugLogs);
            // Metrics sink may be null
            metrics = new Metrics(metricsSink);

            connections = new CleanupCollection <EpoxyConnection>();
            listeners   = new CleanupCollection <EpoxyListener>();
        }
Example #2
0
        public static async Task <EpoxyNetworkStream> MakeServerStreamAsync(
            Socket socket,
            EpoxyServerTlsConfig tlsConfig,
            Logger logger)
        {
            Stream serverStream;
            var    networkStream = new NetworkStream(socket, ownsSocket: false);

            if (tlsConfig == null)
            {
                serverStream = networkStream;
            }
            else
            {
                const bool leaveInnerStreamOpen = false;

                var sslStream = new SslStream(
                    networkStream,
                    leaveInnerStreamOpen,
                    tlsConfig.RemoteCertificateValidationCallback);

                await sslStream.AuthenticateAsServerAsync(
                    tlsConfig.Certificate,
                    clientCertificateRequired : false,
                    enabledSslProtocols : AllowedTlsProtocols,
                    checkCertificateRevocation : tlsConfig.CheckCertificateRevocation);

                logger.Site().Debug("Authenticated connection from {0}", socket.RemoteEndPoint);

                serverStream = sslStream;
            }

            return(new EpoxyNetworkStream(socket, serverStream, logger));
        }
Example #3
0
        static async Task MainAsync(X509Certificate2 serverCertificate)
        {
            var tlsConfig = new EpoxyServerTlsConfig(serverCertificate);

            EpoxyTransport transport = new EpoxyTransportBuilder().SetServerTlsConfig(tlsConfig).Construct();

            EpoxyListener listener = transport.MakeListener(serviceEndpoint);
            listener.AddService(new SimpleService());
            await listener.StartAsync();

            var connection = await transport.ConnectToAsync("epoxys://localhost");
            var proxy = new SimpleProxy<EpoxyConnection>(connection);
            IMessage<SimpleResult> response = await proxy.SimpleMethodAsync();

            PrintResponse(response);
        }
Example #4
0
        static RemoteCertificateValidationCallback MakeServerCertificateValidationCallback(
            EpoxyServerTlsConfig tlsConfig,
            Logger logger)
        {
            if (tlsConfig.ClientCertificateRequired)
            {
                // If client certificates are required, then add an explicit
                // check that the client provided a certificate. The default
                // behavior is to allow the connection even if the client
                // didn't present a certificate.
                return((sender, certificate, chain, errors) =>
                {
                    if (certificate == null)
                    {
                        logger.Site().Error("Rejecting client. Certificate required, but client did not provide one.");
                        return false;
                    }

                    if (tlsConfig.RemoteCertificateValidationCallback != null)
                    {
                        // There's a user-provided validation callback, so
                        // delegate to that.
                        return tlsConfig.RemoteCertificateValidationCallback(
                            sender,
                            certificate,
                            chain,
                            errors);
                    }
                    else
                    {
                        // Otherwise, require no errors at all to accept the
                        // certificate.
                        return errors == SslPolicyErrors.None;
                    }
                });
            }
            else
            {
                // Client certificates are not required, so just use the
                // user-provided validation callback. This may be null, but
                // that's fine. SslStream will just use its default behavior
                // then.
                return(tlsConfig.RemoteCertificateValidationCallback);
            }
        }
Example #5
0
        /// <summary>
        /// Creates a connected server-side EpoxyNetworkStream from a connected TCP socket.
        /// </summary>
        /// <remarks>
        /// If TLS is enabled, this will establish the TLS connection. Upon successful return, the
        /// stream owns the socket.
        /// </remarks>
        /// <param name="socket">The socket to wrap.</param>
        /// <param name="tlsConfig">
        /// TLS config. May be <c>null</c> to indicate a plain-text connection.
        /// </param>
        /// <param name="logger">A logger.</param>
        /// <returns>An connected EpoxyNetworkStream.</returns>
        public static async Task <EpoxyNetworkStream> MakeServerStreamAsync(
            Socket socket,
            EpoxyServerTlsConfig tlsConfig,
            Logger logger)
        {
            Stream serverStream;
            var    networkStream = new NetworkStream(socket, ownsSocket: false);

            if (tlsConfig == null)
            {
                serverStream = networkStream;
            }
            else
            {
                const bool leaveInnerStreamOpen = false;

                var sslStream = new SslStream(
                    networkStream,
                    leaveInnerStreamOpen,
                    MakeServerCertificateValidationCallback(tlsConfig, logger));

                await sslStream.AuthenticateAsServerAsync(
                    tlsConfig.Certificate,
                    tlsConfig.ClientCertificateRequired,
                    enabledSslProtocols : AllowedTlsProtocols,
                    checkCertificateRevocation : tlsConfig.CheckCertificateRevocation);

                if (tlsConfig.ClientCertificateRequired && !sslStream.IsMutuallyAuthenticated)
                {
                    sslStream.Dispose();
                    throw new AuthenticationException("Mutual authentication was required, but it could not be performed.");
                }

                logger.Site().Debug(
                    "Authenticated connection from {0}. Mutually authenticated?: {1}",
                    socket.RemoteEndPoint,
                    sslStream.IsMutuallyAuthenticated);

                serverStream = sslStream;
            }

            return(new EpoxyNetworkStream(socket, serverStream, logger));
        }
Example #6
0
        public EpoxyListener(
            EpoxyTransport parentTransport,
            IPEndPoint listenEndpoint,
            EpoxyServerTlsConfig tlsConfig,
            Logger logger, Metrics metrics) : base(logger, metrics)
        {
            Debug.Assert(parentTransport != null);
            Debug.Assert(listenEndpoint != null);

            this.parentTransport = parentTransport;

            // will be null if not using TLS
            this.tlsConfig = tlsConfig;

            listener            = new TcpListener(listenEndpoint);
            serviceHost         = new ServiceHost(logger);
            connections         = new HashSet <EpoxyConnection>();
            shutdownTokenSource = new CancellationTokenSource();

            ListenEndpoint = listenEndpoint;
        }
 /// <summary>
 /// Sets the server-side TLS config to use when creating listeners.
 /// </summary>
 /// <param name="tlsConfig">
 /// The server-side config. May be <c>null</c>.
 /// </param>
 /// <returns>The builder.</returns>
 /// <remarks>
 /// If <c>null</c>, the listeners created will not use TLS to secure
 /// their communications with clients.
 /// </remarks>
 public EpoxyTransportBuilder SetServerTlsConfig(EpoxyServerTlsConfig tlsConfig)
 {
     serverTlsConfig = tlsConfig;
     return(this);
 }
Example #8
0
        public async Task Tls_MutualNoClientCert_ProxyDoesNotWork()
        {
            var serverTlsConfig = new EpoxyServerTlsConfig(
                testServerCert,
                checkCertificateRevocation: false,
                clientCertificateRequired: true,
                remoteCertificateValidationCallback: EnsureRootedWithTestCertificate
            );

            var clientTlsConfig = new EpoxyClientTlsConfig(certificate: null,
                checkCertificateRevocation: false,
                remoteCertificateValidationCallback: EnsureRootedWithTestCertificate);

            var transport = new EpoxyTransportBuilder()
                .SetResolver(ResolveEverythingToLocalhost)
                .SetServerTlsConfig(serverTlsConfig)
                .SetClientTlsConfig(clientTlsConfig)
                .Construct();

            listener = transport.MakeListener(new IPEndPoint(IPAddress.Loopback, EpoxyTransport.DefaultSecurePort));
            listener.AddService(new DummyTestService());
            await listener.StartAsync();

            try
            {
                // The .NET SslStream implementation currently does not give us
                // a way to signal during TLS handshaking that the server is
                // rejecting the connection. Instead, we have to RST the
                // underlying socket. With Epoxy's current implementation, this
                // can't reliably be detected at connection time. So we attempt
                // to exercise the connection using a proxy and expect that to fail.
                EpoxyConnection clientConnection = await transport.ConnectToAsync("epoxys://bond-test-server1");
                var proxy = new DummyTestProxy<EpoxyConnection>(clientConnection);
                await AssertRequestResponseWorksAsync(proxy);
            }
            catch (Exception ex) when (ex is InvalidOperationException || ex is AuthenticationException)
            {
                // An expected exception type, depending on timing, so pass the
                // test.
            }
            catch (Exception ex)
            {
                Assert.Fail("Unexpected exception of type {0}: {1}", ex.GetType(), ex);
            }
            finally
            {
                await transport.StopAsync();
            }
        }
Example #9
0
        public async Task Tls_Mutual_CanAuthenticate()
        {
            var serverTlsConfig = new EpoxyServerTlsConfig(
                testServerCert,
                checkCertificateRevocation: false,
                clientCertificateRequired: true,
                remoteCertificateValidationCallback: EnsureRootedWithTestCertificate
            );

            var clientTlsConfig = new EpoxyClientTlsConfig(
                certificate: testClientCert,
                checkCertificateRevocation: false,
                remoteCertificateValidationCallback: EnsureRootedWithTestCertificate);

            var transport = new EpoxyTransportBuilder()
                .SetResolver(ResolveEverythingToLocalhost)
                .SetServerTlsConfig(serverTlsConfig)
                .SetClientTlsConfig(clientTlsConfig)
                .Construct();

            listener = transport.MakeListener(new IPEndPoint(IPAddress.Loopback, EpoxyTransport.DefaultSecurePort));
            listener.AddService(new DummyTestService());
            await listener.StartAsync();

            EpoxyConnection clientConnection = await transport.ConnectToAsync("epoxys://bond-test-server1");

            var proxy = new DummyTestProxy<EpoxyConnection>(clientConnection);

            await AssertRequestResponseWorksAsync(proxy);

            await transport.StopAsync();
        }
Example #10
0
        public async Task Tls_ServerBadCert_ConnectionFails()
        {
            var serverTlsConfig = new EpoxyServerTlsConfig(testServerCert, checkCertificateRevocation: false);

            var serverTransport = new EpoxyTransportBuilder().SetServerTlsConfig(serverTlsConfig).Construct();
            listener = serverTransport.MakeListener(new IPEndPoint(IPAddress.Loopback, EpoxyTransport.DefaultSecurePort));
            listener.AddService(new DummyTestService());
            await listener.StartAsync();

            var clientTlsConfig = new EpoxyClientTlsConfig(
                checkCertificateRevocation: false,
                // Intentionally set this to null so that the client gets an
                // invalid certificate. If this test passes on your machine,
                // it's probably because you've installed the Bond test root
                // certificate as a trusted root. This certificate cannot be
                // trusted, so you should uninstall it.
                remoteCertificateValidationCallback: null);

            var clientTransport = new EpoxyTransportBuilder()
                .SetResolver(ResolveEverythingToLocalhost)
                .SetClientTlsConfig(clientTlsConfig)
                .Construct();

            Assert.Throws<AuthenticationException>(async () => await clientTransport.ConnectToAsync("epoxys://bond-test-server1"));

            await clientTransport.StopAsync();
            await serverTransport.StopAsync();
        }
Example #11
0
        private static RemoteCertificateValidationCallback MakeServerCertificateValidationCallback(
            EpoxyServerTlsConfig tlsConfig,
            Logger logger)
        {
            if (tlsConfig.ClientCertificateRequired)
            {
                // If client certificates are required, then add an explicit
                // check that the client provided a certificate. The default
                // behavior is to allow the connection even if the client
                // didn't present a certificate.
                return (sender, certificate, chain, errors) =>
                {
                    if (certificate == null)
                    {
                        logger.Site().Error("Rejecting client. Certificate required, but client did not provide one.");
                        return false;
                    }

                    if (tlsConfig.RemoteCertificateValidationCallback != null)
                    {
                        // There's a user-provided validation callback, so
                        // delegate to that.
                        return tlsConfig.RemoteCertificateValidationCallback(
                            sender,
                            certificate,
                            chain,
                            errors);
                    }
                    else
                    {
                        // Otherwise, require no errors at all to accept the
                        // certificate.
                        return errors == SslPolicyErrors.None;
                    }
                };
            }
            else
            {
                // Client certificates are not required, so just use the
                // user-provided validation callback. This may be null, but
                // that's fine. SslStream will just use its default behavior
                // then.
                return tlsConfig.RemoteCertificateValidationCallback;
            }
        }
Example #12
0
        public static async Task<EpoxyNetworkStream> MakeServerStreamAsync(
            Socket socket,
            EpoxyServerTlsConfig tlsConfig,
            Logger logger)
        {
            Stream serverStream;
            var networkStream = new NetworkStream(socket, ownsSocket: false);

            if (tlsConfig == null)
            {
                serverStream = networkStream;
            }
            else
            {
                const bool leaveInnerStreamOpen = false;

                var sslStream = new SslStream(
                    networkStream,
                    leaveInnerStreamOpen,
                    MakeServerCertificateValidationCallback(tlsConfig, logger));

                await sslStream.AuthenticateAsServerAsync(
                    tlsConfig.Certificate,
                    tlsConfig.ClientCertificateRequired,
                    enabledSslProtocols: AllowedTlsProtocols,
                    checkCertificateRevocation: tlsConfig.CheckCertificateRevocation);

                if (tlsConfig.ClientCertificateRequired && !sslStream.IsMutuallyAuthenticated)
                {
                    sslStream.Dispose();
                    throw new AuthenticationException("Mutual authentication was required, but it could not be performed.");
                }

                logger.Site().Debug(
                    "Authenticated connection from {0}. Mutually authenticated?: {1}",
                    socket.RemoteEndPoint,
                    sslStream.IsMutuallyAuthenticated);

                serverStream = sslStream;
            }

            return new EpoxyNetworkStream(socket, serverStream, logger);
        }