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>(); }
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)); }
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); }
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); } }
/// <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)); }
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); }
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(); } }
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(); }
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(); }
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; } }
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); }