public async Task SslStream_StreamToStream_Alpn_NonMatchingProtocols_Fail() { TcpListener listener = new TcpListener(IPAddress.Loopback, 0); try { listener.Start(); using (TcpClient client = new TcpClient()) { Task <TcpClient> serverTask = listener.AcceptTcpClientAsync(); await client.ConnectAsync(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port); using (TcpClient server = await serverTask) using (SslStream serverStream = new SslStream(server.GetStream(), leaveInnerStreamOpen: false)) using (SslStream clientStream = new SslStream(client.GetStream(), leaveInnerStreamOpen: false)) using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) { SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol> { SslApplicationProtocol.Http2 }, ServerCertificate = certificate, }; SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol> { SslApplicationProtocol.Http11 }, RemoteCertificateValidationCallback = AllowAnyServerCertificate, TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false), }; // Test alpn failure only on platforms that supports ALPN. if (BackendSupportsAlpn) { Task t1 = Assert.ThrowsAsync <IOException>(() => clientStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None)); try { await serverStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None); Assert.True(false, "AuthenticationException was not thrown."); } catch (AuthenticationException) { server.Dispose(); } await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1); } else { Task t1 = clientStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None); Task t2 = serverStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None); await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); Assert.Equal(default(SslApplicationProtocol), clientStream.NegotiatedApplicationProtocol); Assert.Equal(default(SslApplicationProtocol), serverStream.NegotiatedApplicationProtocol); } } } } finally { listener.Stop(); } }
public async Task SslStream_TargetHostName_Succeeds(bool useEmptyName) { string tagetName = useEmptyName ? string.Empty : Guid.NewGuid().ToString("N"); (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams(); using (clientStream) using (serverStream) using (var client = new SslStream(clientStream)) using (var server = new SslStream(serverStream)) using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) { // It should be empty before handshake. Assert.Equal(string.Empty, client.TargetHostName); Assert.Equal(string.Empty, server.TargetHostName); SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() { TargetHost = tagetName }; clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { SslStream stream = (SslStream)sender; if (useEmptyName) { Assert.Equal('?', stream.TargetHostName[0]); } else { Assert.Equal(tagetName, stream.TargetHostName); } return(true); }; SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions(); serverOptions.ServerCertificateSelectionCallback = (sender, name) => { SslStream stream = (SslStream)sender; if (useEmptyName) { Assert.Equal('?', stream.TargetHostName[0]); } else { Assert.Equal(tagetName, stream.TargetHostName); } return(certificate); }; await TestConfiguration.WhenAllOrAnyFailedWithTimeout( client.AuthenticateAsClientAsync(clientOptions), server.AuthenticateAsServerAsync(serverOptions)); if (useEmptyName) { Assert.Equal('?', client.TargetHostName[0]); Assert.Equal('?', server.TargetHostName[0]); } else { Assert.Equal(tagetName, client.TargetHostName); Assert.Equal(tagetName, server.TargetHostName); } } }
/// <summary> /// This is called when client is aware of proxy /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy /// </summary> /// <param name="endPoint">The explicit endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <returns>The task.</returns> private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection) { var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize); Task <TcpServerConnection> prefetchConnectionTask = null; bool closeServerConnection = false; bool calledRequestHandler = false; SslStream sslStream = null; try { string connectHostname = null; TunnelConnectSessionEventArgs connectArgs = null; // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1) { // read the first line HTTP command string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version); var httpRemoteUri = new Uri("http://" + httpUrl); connectHostname = httpRemoteUri.Host; var connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, OriginalUrl = httpUrl, HttpVersion = version }; await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken); connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest, cancellationTokenSource); connectArgs.ProxyClient.Connection = clientConnection; connectArgs.ProxyClient.ClientStream = clientStream; await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); // filter out excluded host names bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl; if (connectArgs.DenyConnect) { if (connectArgs.HttpClient.Response.StatusCode == 0) { connectArgs.HttpClient.Response = new Response { HttpVersion = HttpHeader.Version11, StatusCode = (int)HttpStatusCode.Forbidden, StatusDescription = "Forbidden" }; } // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken : cancellationToken); return; } if (await checkAuthorization(connectArgs) == false) { await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc); // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken : cancellationToken); return; } // write back successful CONNECT response var response = ConnectResponse.CreateSuccessfulConnectResponse(version); // Set ContentLength explicitly to properly handle HTTP 1.0 response.ContentLength = 0; response.Headers.FixProxyHeaders(); connectArgs.HttpClient.Response = response; await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken); bool isClientHello = clientHelloInfo != null; if (isClientHello) { connectRequest.TunnelType = TunnelType.Https; connectRequest.ClientHelloInfo = clientHelloInfo; } await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc, isClientHello); if (decryptSsl && isClientHello) { connectRequest.RequestUri = new Uri("https://" + httpUrl); bool http2Supported = false; var alpn = clientHelloInfo.GetAlpn(); if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2)) { // test server HTTP/2 support try { // todo: this is a hack, because Titanium does not support HTTP protocol changing currently var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); http2Supported = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2; //release connection back to pool instead of closing when connection pool is enabled. await tcpConnectionFactory.Release(connection, true); } catch (Exception) { // ignore } } if (EnableTcpServerConnectionPrefetch) { IPAddress[] ipAddresses = null; try { //make sure the host can be resolved before creating the prefetch task ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host); } catch (SocketException) { } if (ipAddresses != null && ipAddresses.Length > 0) { //don't pass cancellation token here //it could cause floating server connections when client exits prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect: true, applicationProtocols: null, noCache: false, cancellationToken: CancellationToken.None); } } X509Certificate2 certificate = null; try { sslStream = new SslStream(clientStream, false); string certName = HttpHelper.GetWildCardDomainName(connectHostname); certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateServerCertificate(certName); // Successfully managed to authenticate the client using the fake certificate var options = new SslServerAuthenticationOptions(); if (EnableHttp2 && http2Supported) { options.ApplicationProtocols = clientHelloInfo.GetAlpn(); if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0) { options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList; } } options.ServerCertificate = certificate; options.ClientCertificateRequired = false; options.EnabledSslProtocols = SupportedSslProtocols; options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; await sslStream.AuthenticateAsServerAsync(options, cancellationToken); #if NETCOREAPP2_1 clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif // HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferPool, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize); } catch (Exception e) { var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false); throw new ProxyConnectException( $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs); } if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == -1) { decryptSsl = false; } if (!decryptSsl) { await tcpConnectionFactory.Release(prefetchConnectionTask, true); prefetchConnectionTask = null; } } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } // Hostname is excluded or it is not an HTTPS connect if (!decryptSsl || !isClientHello) { if (!isClientHello) { connectRequest.TunnelType = TunnelType.Websocket; } // create new connection to server. // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then // this connection should not be HTTPS. var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); try { if (isClientHello) { int available = clientStream.Available; if (available > 0) { // send the buffered data var data = BufferPool.GetBuffer(BufferSize); try { await clientStream.ReadAsync(data, 0, available, cancellationToken); // clientStream.Available should be at most BufferSize because it is using the same buffer size await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken); } finally { BufferPool.ReturnBuffer(data); } } var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, BufferPool, cancellationToken); ((ConnectResponse)connectArgs.HttpClient.Response).ServerHelloInfo = serverHelloInfo; } await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, connectArgs.CancellationTokenSource, ExceptionFunc); } finally { await tcpConnectionFactory.Release(connection, true); } return; } } if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1) { // todo string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (httpCmd == "PRI * HTTP/2.0") { connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2; // HTTP/2 Connection Preface string line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != "SM") { throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); try { await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); await connection.StreamWriter.WriteLineAsync("SM", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); #if NETCOREAPP2_1 await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, () => new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { Connection = clientConnection }, HttpClient = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest }, UserData = connectArgs?.UserData }, async args => { await invokeBeforeRequest(args); }, async args => { await invokeBeforeResponse(args); }, connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc); #endif } finally { await tcpConnectionFactory.Release(connection, true); } } } calledRequestHandler = true; // Now create the request await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource, connectHostname, connectArgs, prefetchConnectionTask); } catch (ProxyException e) { closeServerConnection = true; onException(clientStream, e); } catch (IOException e) { closeServerConnection = true; onException(clientStream, new Exception("Connection was aborted", e)); } catch (SocketException e) { closeServerConnection = true; onException(clientStream, new Exception("Could not connect", e)); } catch (Exception e) { closeServerConnection = true; onException(clientStream, new Exception("Error occured in whilst handling the client", e)); } finally { if (!calledRequestHandler) { await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection); } sslStream?.Dispose(); clientStream.Dispose(); if (!cancellationTokenSource.IsCancellationRequested) { cancellationTokenSource.Cancel(); } } }
public SniOptionsSelector( string endpointName, Dictionary <string, SniConfig> sniDictionary, ICertificateConfigLoader certifcateConfigLoader, HttpsConnectionAdapterOptions fallbackHttpsOptions, HttpProtocols fallbackHttpProtocols, ILogger <HttpsConnectionMiddleware> logger) { _endpointName = endpointName; _fallbackServerCertificateSelector = fallbackHttpsOptions.ServerCertificateSelector; _onAuthenticateCallback = fallbackHttpsOptions.OnAuthenticate; foreach (var(name, sniConfig) in sniDictionary) { var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = certifcateConfigLoader.LoadCertificate(sniConfig.Certificate, $"{endpointName}:Sni:{name}"), EnabledSslProtocols = sniConfig.SslProtocols ?? fallbackHttpsOptions.SslProtocols, CertificateRevocationCheckMode = fallbackHttpsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, }; if (sslOptions.ServerCertificate is null) { if (fallbackHttpsOptions.ServerCertificate is null && _fallbackServerCertificateSelector is null) { throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound); } if (_fallbackServerCertificateSelector is null) { // Cache the fallback ServerCertificate since there's no fallback ServerCertificateSelector taking precedence. sslOptions.ServerCertificate = fallbackHttpsOptions.ServerCertificate; } } if (sslOptions.ServerCertificate != null) { // This might be do blocking IO but it'll resolve the certificate chain up front before any connections are // made to the server sslOptions.ServerCertificateContext = SslStreamCertificateContext.Create((X509Certificate2)sslOptions.ServerCertificate, additionalCertificates: null); } if (!certifcateConfigLoader.IsTestMock && sslOptions.ServerCertificate is X509Certificate2 cert2) { HttpsConnectionMiddleware.EnsureCertificateIsAllowedForServerAuth(cert2); } var clientCertificateMode = sniConfig.ClientCertificateMode ?? fallbackHttpsOptions.ClientCertificateMode; if (clientCertificateMode != ClientCertificateMode.NoCertificate) { sslOptions.ClientCertificateRequired = clientCertificateMode == ClientCertificateMode.AllowCertificate || clientCertificateMode == ClientCertificateMode.RequireCertificate; sslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => HttpsConnectionMiddleware.RemoteCertificateValidationCallback( clientCertificateMode, fallbackHttpsOptions.ClientCertificateValidation, certificate, chain, sslPolicyErrors); } var httpProtocols = sniConfig.Protocols ?? fallbackHttpProtocols; httpProtocols = HttpsConnectionMiddleware.ValidateAndNormalizeHttpProtocols(httpProtocols, logger); HttpsConnectionMiddleware.ConfigureAlpn(sslOptions, httpProtocols); var sniOptions = new SniOptions(sslOptions, httpProtocols, clientCertificateMode); if (name.Equals(WildcardHost, StringComparison.Ordinal)) { _wildcardOptions = sniOptions; } else if (name.StartsWith(WildcardPrefix, StringComparison.Ordinal)) { // Only slice off 1 character, the `*`. We want to match the leading `.` also. _wildcardPrefixOptions.Add(name.Substring(1), sniOptions); } else { _exactNameOptions.Add(name, sniOptions); } } }
private async Task <SslServerAuthenticationOptions> OptionsTask(SslServerAuthenticationOptions value) { await Task.Yield(); return(value); }
/// <summary> /// Create a QUIC listener. /// </summary> /// <param name="listenEndPoint">The local endpoint to listen on.</param> /// <param name="sslServerAuthenticationOptions">TLS options for the listener.</param> public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) : this(QuicImplementationProviders.Default, listenEndPoint, sslServerAuthenticationOptions) { }
private async Task InnerOnConnectionAsync(ConnectionContext context) { SslStream sslStream; bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); // TODO: Handle the cases where this can be null var memoryPoolFeature = context.Features.Get <IMemoryPoolFeature>(); var inputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: _options.Scheduler, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxInputBufferSize ?? 0, resumeWriterThreshold: _options.MaxInputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); var outputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxOutputBufferSize ?? 0, resumeWriterThreshold: _options.MaxOutputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); // TODO: eventually make SslDuplexStream : Stream, IDuplexPipe to avoid RawStream allocation and pipe allocations var adaptedPipeline = new AdaptedPipeline(context.Transport, new Pipe(inputPipeOptions), new Pipe(outputPipeOptions), _logger, memoryPoolFeature.MemoryPool.GetMinimumAllocSize()); var transportStream = adaptedPipeline.TransportStream; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslStream = new SslStream(transportStream); certificateRequired = false; } else { sslStream = new SslStream(transportStream, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.ClientCertificateMode != ClientCertificateMode.RequireCertificate); } if (_options.ClientCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.ClientCertificateValidation != null) { if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); }); certificateRequired = true; } using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return(cert); }; } var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = _serverCertificate, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; // This is order sensitive if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); // https://tools.ietf.org/html/rfc7540#section-9.2.1 sslOptions.AllowRenegotiation = false; } if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } _options.OnAuthenticate?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); sslStream.Dispose(); return; } catch (Exception ex) when(ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); sslStream.Dispose(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; var original = context.Transport; try { context.Transport = adaptedPipeline; using (sslStream) { try { adaptedPipeline.RunAsync(sslStream); await _next(context); } finally { await adaptedPipeline.CompleteAsync(); } } } finally { // Restore the original so that it gets closed appropriately context.Transport = original; } }
public void CloneSslOptionsClonesAllProperties() { var propertyNames = typeof(SslServerAuthenticationOptions).GetProperties().Select(property => property.Name).ToList(); CipherSuitesPolicy cipherSuitesPolicy = null; if (!OperatingSystem.IsWindows()) { try { // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Windows. cipherSuitesPolicy = new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 }); } catch (PlatformNotSupportedException) { // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Ubuntu 16.04. // I don't know exactly which other distros/versions throw PNEs, but it isn't super relevant to this test, // so let's just swallow this exception. } } // Set options properties to non-default values to verify they're copied. var options = new SslServerAuthenticationOptions { // Defaults to true AllowRenegotiation = false, // Defaults to null ApplicationProtocols = new List <SslApplicationProtocol> { SslApplicationProtocol.Http2 }, // Defaults to X509RevocationMode.NoCheck CertificateRevocationCheckMode = X509RevocationMode.Offline, // Defaults to null CipherSuitesPolicy = cipherSuitesPolicy, // Defaults to false ClientCertificateRequired = true, // Defaults to SslProtocols.None #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls11, #pragma warning restore SYSLIB0039 #pragma warning disable SYSLIB0040 // EncryptionPolicy.NoEncryption is obsolete // Defaults to EncryptionPolicy.RequireEncryption EncryptionPolicy = EncryptionPolicy.NoEncryption, #pragma warning restore SYSLIB0040 // Defaults to null RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, // Defaults to null ServerCertificate = new X509Certificate2(Array.Empty <byte>()), // Defaults to null ServerCertificateContext = SslStreamCertificateContext.Create(_x509Certificate2, additionalCertificates: null, offline: true), // Defaults to null ServerCertificateSelectionCallback = (sender, serverName) => null, }; var clonedOptions = SniOptionsSelector.CloneSslOptions(options); Assert.NotSame(options, clonedOptions); Assert.Equal(options.AllowRenegotiation, clonedOptions.AllowRenegotiation); Assert.True(propertyNames.Remove(nameof(options.AllowRenegotiation))); // Ensure the List<SslApplicationProtocol> is also cloned since it could be modified by a user callback. Assert.NotSame(options.ApplicationProtocols, clonedOptions.ApplicationProtocols); Assert.Equal(Assert.Single(options.ApplicationProtocols), Assert.Single(clonedOptions.ApplicationProtocols)); Assert.True(propertyNames.Remove(nameof(options.ApplicationProtocols))); Assert.Equal(options.CertificateRevocationCheckMode, clonedOptions.CertificateRevocationCheckMode); Assert.True(propertyNames.Remove(nameof(options.CertificateRevocationCheckMode))); Assert.Same(options.CipherSuitesPolicy, clonedOptions.CipherSuitesPolicy); Assert.True(propertyNames.Remove(nameof(options.CipherSuitesPolicy))); Assert.Equal(options.ClientCertificateRequired, clonedOptions.ClientCertificateRequired); Assert.True(propertyNames.Remove(nameof(options.ClientCertificateRequired))); Assert.Equal(options.EnabledSslProtocols, clonedOptions.EnabledSslProtocols); Assert.True(propertyNames.Remove(nameof(options.EnabledSslProtocols))); Assert.Equal(options.EncryptionPolicy, clonedOptions.EncryptionPolicy); Assert.True(propertyNames.Remove(nameof(options.EncryptionPolicy))); Assert.Same(options.RemoteCertificateValidationCallback, clonedOptions.RemoteCertificateValidationCallback); Assert.True(propertyNames.Remove(nameof(options.RemoteCertificateValidationCallback))); // Technically the ServerCertificate could be reset/reimported, but I'm hoping this is uncommon. Trying to clone the certificate and/or context seems risky. Assert.Same(options.ServerCertificate, clonedOptions.ServerCertificate); Assert.True(propertyNames.Remove(nameof(options.ServerCertificate))); Assert.Same(options.ServerCertificateContext, clonedOptions.ServerCertificateContext); Assert.True(propertyNames.Remove(nameof(options.ServerCertificateContext))); Assert.Same(options.ServerCertificateSelectionCallback, clonedOptions.ServerCertificateSelectionCallback); Assert.True(propertyNames.Remove(nameof(options.ServerCertificateSelectionCallback))); // Ensure we've checked every property. When new properties get added, we'll have to update this test along with the CloneSslOptions implementation. Assert.Empty(propertyNames); }
private async Task <IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context) { SslStream sslStream; bool certificateRequired; var feature = new TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslStream = new SslStream(context.ConnectionStream); certificateRequired = false; } else { sslStream = new SslStream(context.ConnectionStream, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.ClientCertificateMode != ClientCertificateMode.RequireCertificate); } if (_options.ClientCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.ClientCertificateValidation != null) { if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); }); certificateRequired = true; } var timeoutFeature = context.Features.Get <IConnectionTimeoutFeature>(); timeoutFeature.SetTimeout(_options.HandshakeTimeout); try { #if NETCOREAPP2_1 var sslOptions = new SslServerAuthenticationOptions() { ServerCertificate = _serverCertificate, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; // This is order sensitive if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); } if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); #else await sslStream.AuthenticateAsServerAsync(_serverCertificate, certificateRequired, _options.SslProtocols, _options.CheckCertificateRevocation); #endif } catch (OperationCanceledException) { _logger?.LogInformation(2, CoreStrings.AuthenticationTimedOut); sslStream.Dispose(); return(_closedAdaptedConnection); } catch (IOException ex) { _logger?.LogInformation(1, ex, CoreStrings.AuthenticationFailed); sslStream.Dispose(); return(_closedAdaptedConnection); } finally { timeoutFeature.CancelTimeout(); } #if NETCOREAPP2_1 feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); #endif feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); return(new HttpsAdaptedConnection(sslStream)); }
public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication() { using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate()) using (X509Certificate2 serverCert = Configuration.Certificates.GetServerCertificate()) { // Values used to populate client options bool clientAllowRenegotiation = false; List <SslApplicationProtocol> clientAppProtocols = new List <SslApplicationProtocol> { SslApplicationProtocol.Http11 }; X509RevocationMode clientRevocation = X509RevocationMode.NoCheck; X509CertificateCollection clientCertificates = new X509CertificateCollection() { clientCert }; SslProtocols clientSslProtocols = SslProtocols.Tls12; EncryptionPolicy clientEncryption = EncryptionPolicy.RequireEncryption; LocalCertificateSelectionCallback clientLocalCallback = new LocalCertificateSelectionCallback(delegate { return(null); }); RemoteCertificateValidationCallback clientRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); }); string clientHost = serverCert.GetNameInfo(X509NameType.SimpleName, false); // Values used to populate server options bool serverAllowRenegotiation = true; List <SslApplicationProtocol> serverAppProtocols = new List <SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 }; X509RevocationMode serverRevocation = X509RevocationMode.NoCheck; bool serverCertRequired = false; #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete SslProtocols serverSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12; #pragma warning restore SYSLIB0039 #pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete EncryptionPolicy serverEncryption = EncryptionPolicy.AllowNoEncryption; #pragma warning restore SYSLIB0040 RemoteCertificateValidationCallback serverRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); }); SslStreamCertificateContext certificateContext = SslStreamCertificateContext.Create(serverCert, null, false); (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); using (var client = new SslStream(stream1)) using (var server = new SslStream(stream2)) { // Create client options var clientOptions = new SslClientAuthenticationOptions { AllowRenegotiation = clientAllowRenegotiation, ApplicationProtocols = clientAppProtocols, CertificateRevocationCheckMode = clientRevocation, ClientCertificates = clientCertificates, EnabledSslProtocols = clientSslProtocols, EncryptionPolicy = clientEncryption, LocalCertificateSelectionCallback = clientLocalCallback, RemoteCertificateValidationCallback = clientRemoteCallback, TargetHost = clientHost }; // Create server options var serverOptions = new SslServerAuthenticationOptions { AllowRenegotiation = serverAllowRenegotiation, ApplicationProtocols = serverAppProtocols, CertificateRevocationCheckMode = serverRevocation, ClientCertificateRequired = serverCertRequired, EnabledSslProtocols = serverSslProtocols, EncryptionPolicy = serverEncryption, RemoteCertificateValidationCallback = serverRemoteCallback, ServerCertificate = serverCert, ServerCertificateContext = certificateContext, }; // Authenticate Task clientTask = client.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions); Task serverTask = server.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions); await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(); // Validate that client options are unchanged Assert.Equal(clientAllowRenegotiation, clientOptions.AllowRenegotiation); Assert.Same(clientAppProtocols, clientOptions.ApplicationProtocols); Assert.Equal(1, clientOptions.ApplicationProtocols.Count); Assert.Equal(clientRevocation, clientOptions.CertificateRevocationCheckMode); Assert.Same(clientCertificates, clientOptions.ClientCertificates); Assert.Contains(clientCert, clientOptions.ClientCertificates.Cast <X509Certificate2>()); Assert.Equal(clientSslProtocols, clientOptions.EnabledSslProtocols); Assert.Equal(clientEncryption, clientOptions.EncryptionPolicy); Assert.Same(clientLocalCallback, clientOptions.LocalCertificateSelectionCallback); Assert.Same(clientRemoteCallback, clientOptions.RemoteCertificateValidationCallback); Assert.Same(clientHost, clientOptions.TargetHost); // Validate that server options are unchanged Assert.Equal(serverAllowRenegotiation, serverOptions.AllowRenegotiation); Assert.Same(serverAppProtocols, serverOptions.ApplicationProtocols); Assert.Equal(2, serverOptions.ApplicationProtocols.Count); Assert.Equal(clientRevocation, serverOptions.CertificateRevocationCheckMode); Assert.Equal(serverCertRequired, serverOptions.ClientCertificateRequired); Assert.Equal(serverSslProtocols, serverOptions.EnabledSslProtocols); Assert.Equal(serverEncryption, serverOptions.EncryptionPolicy); Assert.Same(serverRemoteCallback, serverOptions.RemoteCertificateValidationCallback); Assert.Same(serverCert, serverOptions.ServerCertificate); Assert.Same(certificateContext, serverOptions.ServerCertificateContext); } } }
private async Task InnerOnConnectionAsync(ConnectionContext context) { bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); var memoryPool = context.Features.Get <IMemoryPoolFeature>()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPool, bufferSize: memoryPool.GetMinimumSegmentSize(), minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( pool: memoryPool, leaveOpen: true ); SslDuplexPipe sslDuplexPipe = null; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream(s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.ClientCertificateMode != ClientCertificateMode.RequireCertificate); } if (_options.ClientCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.ClientCertificateValidation != null) { if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); })); certificateRequired = true; } var sslStream = sslDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return(cert); }; } var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = _serverCertificate, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; // This is order sensitive if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); // https://tools.ietf.org/html/rfc7540#section-9.2.1 sslOptions.AllowRenegotiation = false; } if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } _options.OnAuthenticate?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); await sslStream.DisposeAsync(); return; } catch (IOException ex) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); await sslStream.DisposeAsync(); return; } catch (AuthenticationException ex) { if (_serverCertificate == null || !CertificateManager.IsHttpsDevelopmentCertificate(_serverCertificate) || CertificateManager.CheckDeveloperCertificateKey(_serverCertificate)) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); } else { _logger?.LogError(3, ex, CoreStrings.BadDeveloperCertificateState); } await sslStream.DisposeAsync(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; try { context.Transport = sslDuplexPipe; // Disposing the stream will dispose the sslDuplexPipe await using (sslStream) await using (sslDuplexPipe) { await _next(context); // Dispose the inner stream (SslDuplexPipe) before disposing the SslStream // as the duplex pipe can hit an ODE as it still may be writing. } } finally { // Restore the original so that it gets closed appropriately context.Transport = originalTransport; } }
public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndPoint endpoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) { if (!QuicImplementationProviders.Default.IsSupported) { throw new NotSupportedException("QUIC is not supported or enabled on this platform. See https://aka.ms/aspnet/kestrel/http3reqs for details."); } _log = log; _context = new QuicTransportContext(_log, options); var quicListenerOptions = new QuicListenerOptions(); var listenEndPoint = endpoint as IPEndPoint; if (listenEndPoint == null) { throw new InvalidOperationException($"QUIC doesn't support listening on the configured endpoint type. Expected {nameof(IPEndPoint)} but got {endpoint.GetType().Name}."); } // Workaround for issue in System.Net.Quic // https://github.com/dotnet/runtime/issues/57241 if (listenEndPoint.Address.Equals(IPAddress.Any) && listenEndPoint.Address != IPAddress.Any) { listenEndPoint = new IPEndPoint(IPAddress.Any, listenEndPoint.Port); } if (listenEndPoint.Address.Equals(IPAddress.IPv6Any) && listenEndPoint.Address != IPAddress.IPv6Any) { listenEndPoint = new IPEndPoint(IPAddress.IPv6Any, listenEndPoint.Port); } quicListenerOptions.ServerAuthenticationOptions = sslServerAuthenticationOptions; quicListenerOptions.ListenEndPoint = listenEndPoint; quicListenerOptions.IdleTimeout = options.IdleTimeout; quicListenerOptions.MaxBidirectionalStreams = options.MaxBidirectionalStreamCount; quicListenerOptions.MaxUnidirectionalStreams = options.MaxUnidirectionalStreamCount; quicListenerOptions.ListenBacklog = options.Backlog; _listener = new QuicListener(quicListenerOptions); // Listener endpoint will resolve an ephemeral port, e.g. 127.0.0.1:0, into the actual port. EndPoint = _listener.ListenEndPoint; }
internal abstract QuicListenerProvider CreateListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions);
private async Task InnerOnConnectionAsync(ConnectionContext context) { bool certificateRequired; var feature = new TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); var memoryPool = context.Features.Get <IMemoryPoolFeature>()?.MemoryPool; var inputPipeOptions = new StreamPipeReaderOptions ( pool: memoryPool, bufferSize: memoryPool.GetMinimumSegmentSize(), minimumReadSize: memoryPool.GetMinimumAllocSize(), leaveOpen: true ); var outputPipeOptions = new StreamPipeWriterOptions ( pool: memoryPool, leaveOpen: true ); SslDuplexPipe sslDuplexPipe = null; if (_options.RemoteCertificateMode == RemoteCertificateMode.NoCertificate) { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions); certificateRequired = false; } else { sslDuplexPipe = new SslDuplexPipe(context.Transport, inputPipeOptions, outputPipeOptions, s => new SslStream( s, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.RemoteCertificateMode != RemoteCertificateMode.RequireCertificate); } if (_options.RemoteCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.RemoteCertificateValidation != null) { if (!_options.RemoteCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); })); certificateRequired = true; } var sslStream = sslDuplexPipe.Stream; using (var cancellationTokeSource = new CancellationTokenSource(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : _options.HandshakeTimeout)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_certificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _certificateSelector(context, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return(cert); }; } var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = _certificate, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; _options.OnAuthenticateAsServer?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, cancellationTokeSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { _logger?.LogDebug(2, "Authentication timed out"); await sslStream.DisposeAsync().ConfigureAwait(false); return; } catch (Exception ex) when(ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, "Authentication failed"); await sslStream.DisposeAsync().ConfigureAwait(false); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); feature.LocalCertificate = ConvertToX509Certificate2(sslStream.LocalCertificate); feature.RemoteCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; try { context.Transport = sslDuplexPipe; // Disposing the stream will dispose the sslDuplexPipe await using (sslStream) await using (sslDuplexPipe) { await _next(context).ConfigureAwait(false); // Dispose the inner stream (SslDuplexPipe) before disposing the SslStream // as the duplex pipe can hit an ODE as it still may be writing. } } finally { // Restore the original so that it gets closed appropriately context.Transport = originalTransport; } }
private async Task DoHandshakeWithOptions(SslStream clientSslStream, SslStream serverSslStream, SslClientAuthenticationOptions clientOptions, SslServerAuthenticationOptions serverOptions) { using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) { clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate; clientOptions.TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false); serverOptions.ServerCertificate = certificate; Task t1 = clientSslStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None); Task t2 = serverSslStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None); await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); } }
private bool DoHandshakeWithOptions(SslStream clientSslStream, SslStream serverSslStream, SslClientAuthenticationOptions clientOptions, SslServerAuthenticationOptions serverOptions) { using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate()) { clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate; clientOptions.TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false); serverOptions.ServerCertificate = certificate; Task t1 = clientSslStream.AuthenticateAsClientAsync(clientOptions, CancellationToken.None); Task t2 = serverSslStream.AuthenticateAsServerAsync(serverOptions, CancellationToken.None); return(Task.WaitAll(new[] { t1, t2 }, TestConfiguration.PassingTestTimeoutMilliseconds)); } }
/// <summary> /// This is called when client is aware of proxy /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy /// </summary> /// <param name="endPoint">The explicit endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <returns>The task.</returns> internal async Task handleClient(ExplicitProxyEndPoint endPoint, RequestStateBase state) { TcpClientConnection clientConnection = state.ClientConnection; var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var clientStream = new HttpClientStream(clientConnection, clientConnection.GetStream(), BufferPool); Task <TcpServerConnection>?prefetchConnectionTask = null; bool closeServerConnection = false; bool calledRequestHandler = false; SslStream?sslStream = null; try { TunnelConnectSessionEventArgs?connectArgs = null; var method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken); if (clientStream.IsClosed) { return; } // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (method == KnownMethod.Connect) { // read the first line HTTP command var requestLine = await clientStream.ReadRequestLine(cancellationToken); if (requestLine.IsEmpty()) { return; } var connectRequest = new ConnectRequest(requestLine.RequestUri) { RequestUriString8 = requestLine.RequestUri, HttpVersion = requestLine.Version }; await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken); connectArgs = new TunnelConnectSessionEventArgs(state, endPoint, connectRequest, clientConnection, clientStream, cancellationTokenSource); clientStream.DataRead += (o, args) => connectArgs.OnDataSent(args.Buffer, args.Offset, args.Count); clientStream.DataWrite += (o, args) => connectArgs.OnDataReceived(args.Buffer, args.Offset, args.Count); await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); // filter out excluded host names bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl; bool sendRawData = !decryptSsl; if (connectArgs.DenyConnect) { if (connectArgs.HttpClient.Response.StatusCode == 0) { connectArgs.HttpClient.Response = new Response { HttpVersion = HttpHeader.Version11, StatusCode = (int)HttpStatusCode.Forbidden, StatusDescription = "Forbidden" }; } // send the response await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken); return; } if (await checkAuthorization(connectArgs) == false) { await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc); // send the response await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken); return; } // write back successful CONNECT response var response = ConnectResponse.CreateSuccessfulConnectResponse(requestLine.Version); // Set ContentLength explicitly to properly handle HTTP 1.0 response.ContentLength = 0; response.Headers.FixProxyHeaders(); connectArgs.HttpClient.Response = response; await clientStream.WriteResponseAsync(response, cancellationToken); var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken); if (clientStream.IsClosed) { return; } bool isClientHello = clientHelloInfo != null; if (clientHelloInfo != null) { connectRequest.TunnelType = TunnelType.Https; connectRequest.ClientHelloInfo = clientHelloInfo; } await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc, isClientHello); if (decryptSsl && clientHelloInfo != null) { connectRequest.IsHttps = true; // todo: move this line to the previous "if" clientStream.Connection.SslProtocol = clientHelloInfo.SslProtocol; bool http2Supported = false; if (EnableHttp2) { var alpn = clientHelloInfo.GetAlpn(); if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2)) { // test server HTTP/2 support try { // todo: this is a hack, because Titanium does not support HTTP protocol changing currently var connection = await tcpConnectionFactory.GetServerConnection(state, true, SslExtensions.Http2ProtocolAsList, true, cancellationToken); http2Supported = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2; // release connection back to pool instead of closing when connection pool is enabled. await tcpConnectionFactory.Release(connection, true); } catch (Exception) { // ignore } } } if (EnableTcpServerConnectionPrefetch) { IPAddress[]? ipAddresses = null; try { // make sure the host can be resolved before creating the prefetch task ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host); } catch (SocketException) { } if (ipAddresses != null && ipAddresses.Length > 0) { // don't pass cancellation token here // it could cause floating server connections when client exits prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(state, true, null, false, CancellationToken.None); } } string connectHostname = requestLine.RequestUri.GetString(); int idx = connectHostname.IndexOf(":"); if (idx >= 0) { connectHostname = connectHostname.Substring(0, idx); } X509Certificate2?certificate = null; try { sslStream = new SslStream(clientStream, false); string certName = HttpHelper.GetWildCardDomainName(connectHostname); certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateServerCertificate(certName); // Successfully managed to authenticate the client using the fake certificate var options = new SslServerAuthenticationOptions(); if (EnableHttp2 && http2Supported) { options.ApplicationProtocols = clientHelloInfo.GetAlpn(); if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0) { options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList; } } options.ServerCertificate = certificate; options.ClientCertificateRequired = false; options.EnabledSslProtocols = SupportedSslProtocols; options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; await sslStream.AuthenticateAsServerAsync(options, cancellationToken); #if NETSTANDARD2_1 clientStream.Connection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif // HTTPS server created - we can now decrypt the client's traffic clientStream = new HttpClientStream(clientStream.Connection, sslStream, BufferPool); sslStream = null; // clientStream was created, no need to keep SSL stream reference clientStream.DataRead += (o, args) => connectArgs.OnDecryptedDataSent(args.Buffer, args.Offset, args.Count); clientStream.DataWrite += (o, args) => connectArgs.OnDecryptedDataReceived(args.Buffer, args.Offset, args.Count); } catch (Exception e) { var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false); throw new ProxyConnectException( $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs); } method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken); if (clientStream.IsClosed) { return; } if (method == KnownMethod.Invalid) { sendRawData = true; await tcpConnectionFactory.Release(prefetchConnectionTask, true); prefetchConnectionTask = null; } } else if (clientHelloInfo == null) { method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken); if (clientStream.IsClosed) { return; } } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } if (method == KnownMethod.Invalid) { sendRawData = true; } // Hostname is excluded or it is not an HTTPS connect if (sendRawData) { // create new connection to server. // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then // this connection should not be HTTPS. var connection = await tcpConnectionFactory.GetServerConnection(state, true, SslExtensions.Http2ProtocolAsList, true, cancellationToken); try { if (isClientHello) { int available = clientStream.Available; if (available > 0) { // send the buffered data var data = BufferPool.GetBuffer(); try { // clientStream.Available should be at most BufferSize because it is using the same buffer size int read = await clientStream.ReadAsync(data, 0, available, cancellationToken); if (read != available) { throw new Exception("Internal error."); } await connection.Stream.WriteAsync(data, 0, available, true, cancellationToken); } finally { BufferPool.ReturnBuffer(data); } } var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, BufferPool, cancellationToken); ((ConnectResponse)connectArgs.HttpClient.Response).ServerHelloInfo = serverHelloInfo; } if (!clientStream.IsClosed && !connection.Stream.IsClosed) { await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, null, null, connectArgs.CancellationTokenSource, ExceptionFunc); } } finally { await tcpConnectionFactory.Release(connection, true); } return; } } if (connectArgs != null && method == KnownMethod.Pri) { // todo string?httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (httpCmd == "PRI * HTTP/2.0") { connectArgs.HttpClient.ConnectRequest !.TunnelType = TunnelType.Http2; // HTTP/2 Connection Preface string?line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != "SM") { throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } var connection = await tcpConnectionFactory.GetServerConnection(state, true, SslExtensions.Http2ProtocolAsList, true, cancellationToken); try { #if NETSTANDARD2_1 var connectionPreface = new ReadOnlyMemory <byte>(Http2Helper.ConnectionPreface); await connection.Stream.WriteAsync(connectionPreface, cancellationToken); await Http2Helper.SendHttp2(clientStream, connection.Stream, () => new SessionEventArgs(state, endPoint, clientStream, connectArgs?.HttpClient.ConnectRequest, cancellationTokenSource) { UserData = connectArgs?.UserData }, async args => { await onBeforeRequest(args); }, async args => { await onBeforeResponse(args); }, connectArgs.CancellationTokenSource, clientStream.Connection.Id, ExceptionFunc); #endif } finally { await tcpConnectionFactory.Release(connection, true); } } } calledRequestHandler = true; // Now create the request await handleHttpSessionRequest(endPoint, state, clientStream, cancellationTokenSource, connectArgs, prefetchConnectionTask); } catch (ProxyException e) { closeServerConnection = true; onException(clientStream, e); } catch (IOException e) { closeServerConnection = true; onException(clientStream, new Exception("Connection was aborted", e)); } catch (SocketException e) { closeServerConnection = true; onException(clientStream, new Exception("Could not connect", e)); } catch (Exception e) { closeServerConnection = true; onException(clientStream, new Exception("Error occured in whilst handling the client", e)); } finally { if (!calledRequestHandler) { await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection); } sslStream?.Dispose(); clientStream.Dispose(); } }
public override async Task <GenericLoopbackConnection> EstablishGenericConnectionAsync() { Socket socket = await _listenSocket.AcceptAsync().ConfigureAwait(false); Stream stream = new NetworkStream(socket, ownsSocket: true); var options = new GenericLoopbackOptions() { Address = _options.Address, SslProtocols = _options.SslProtocols, UseSsl = false, ListenBacklog = _options.ListenBacklog }; GenericLoopbackConnection connection = null; try { if (_options.UseSsl) { var sslStream = new SslStream(stream, false, delegate { return(true); }); using (X509Certificate2 cert = Configuration.Certificates.GetServerCertificate()) { SslServerAuthenticationOptions sslOptions = new SslServerAuthenticationOptions(); sslOptions.EnabledSslProtocols = _options.SslProtocols; sslOptions.ApplicationProtocols = _options.SslApplicationProtocols; sslOptions.ServerCertificate = cert; await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None).ConfigureAwait(false); } stream = sslStream; if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2) { // Do not pass original options so the CreateConnectionAsync won't try to do ALPN again. return(connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false)); } if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http11 || sslStream.NegotiatedApplicationProtocol == default) { // Do not pass original options so the CreateConnectionAsync won't try to do ALPN again. return(connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false)); } else { throw new Exception($"Unsupported negotiated protocol {sslStream.NegotiatedApplicationProtocol}"); } } if (_options.ClearTextVersion == HttpVersion.Version11) { return(connection = await Http11LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false)); } else if (_options.ClearTextVersion == HttpVersion.Version20) { return(connection = await Http2LoopbackServerFactory.Singleton.CreateConnectionAsync(new SocketWrapper(socket), stream, options).ConfigureAwait(false)); } else { throw new Exception($"Invalid ClearTextVersion={_options.ClearTextVersion} specified"); } } catch { if (connection is not null) { await connection.DisposeAsync(); connection = null; } stream.Dispose(); throw; } finally { if (connection != null) { await connection.InitializeConnectionAsync().ConfigureAwait(false); } } }
// !!! TEMPORARY: Remove or make internal before shipping public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions) : this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions }) { }
private async Task <IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context) { SslStream sslStream; bool certificateRequired; var feature = new TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslStream = new SslStream(context.ConnectionStream); certificateRequired = false; } else { sslStream = new SslStream(context.ConnectionStream, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.ClientCertificateMode != ClientCertificateMode.RequireCertificate); } if (_options.ClientCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.ClientCertificateValidation != null) { if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); }); certificateRequired = true; } var timeoutFeature = context.Features.Get <IConnectionTimeoutFeature>(); timeoutFeature.SetTimeout(_options.HandshakeTimeout); _options.OnHandshakeStarted?.Invoke(); try { #if NETCOREAPP2_1 // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _serverCertificateSelector(context.ConnectionContext, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return(cert); }; } var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = _serverCertificate, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; // This is order sensitive if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); // https://tools.ietf.org/html/rfc7540#section-9.2.1 sslOptions.AllowRenegotiation = false; } if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); #elif NETSTANDARD2_0 // No ALPN support var serverCert = _serverCertificate; if (_serverCertificateSelector != null) { context.Features.Set(sslStream); serverCert = _serverCertificateSelector(context.ConnectionContext, null); if (serverCert != null) { EnsureCertificateIsAllowedForServerAuth(serverCert); } } await sslStream.AuthenticateAsServerAsync(serverCert, certificateRequired, _options.SslProtocols, _options.CheckCertificateRevocation); #else #error TFMs need to be updated #endif } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); sslStream.Dispose(); return(_closedAdaptedConnection); } catch (Exception ex) when(ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); sslStream.Dispose(); return(_closedAdaptedConnection); } finally { timeoutFeature.CancelTimeout(); } #if NETCOREAPP2_1 feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); #elif NETSTANDARD2_0 // No ALPN support #else #error TFMs need to be updated #endif feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; return(new HttpsAdaptedConnection(sslStream)); }
/// <summary> /// Sets the <see cref="ListenerProvider" /> on the <see cref="MinimalServer" /> to one that provides /// <see cref="SslListener" />s with <see cref="TcpConnectionListener" />s, using the given /// <see cref="SslServerAuthenticationOptions" />. /// </summary> /// <param name="server">the server</param> /// <param name="serverAuthenticationOptions"> /// SSL Server Authentication Options to be passed to the /// <see cref="SslListener" /> constructor /// </param> public static void AddSsl(this MinimalServer server, SslServerAuthenticationOptions serverAuthenticationOptions) { server.ListenerProvider = endpoint => new SslListener(new TcpConnectionListener(endpoint), serverAuthenticationOptions); }
public SniOptions(SslServerAuthenticationOptions sslOptions, HttpProtocols httpProtocols, ClientCertificateMode clientCertificateMode) { SslOptions = sslOptions; HttpProtocols = httpProtocols; ClientCertificateMode = clientCertificateMode; }