Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0673c8c
Initial Prelogin
saurabh500 Jun 20, 2024
f804638
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelogin
saurabh500 Jun 20, 2024
0db2eec
Merge branch 'feat/sqlclientx' into dev/prelogin
saurabh500 Jun 20, 2024
0c3cfe8
Adding prelogin handler
saurabh500 Jun 20, 2024
33062b3
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelogin
saurabh500 Jun 20, 2024
59341ed
Prelogin handler
saurabh500 Jun 20, 2024
7e923be
Adding the ssl work in progress
saurabh500 Jun 20, 2024
09145d3
Prelogin WIP
saurabh500 Jun 20, 2024
bfd5568
Prelogin WIP
saurabh500 Jun 20, 2024
3b07335
Adding the prelogin handler changes for encryption
saurabh500 Jun 21, 2024
2221463
Fix object Id
saurabh500 Jun 22, 2024
48ff556
Clean up the handler
saurabh500 Jun 22, 2024
0d620bb
adding the prelogin handler sub handlers
saurabh500 Jun 22, 2024
0e2fa17
Adding the sub handlers for prelogin :
saurabh500 Jun 22, 2024
9d0f461
Simplify
saurabh500 Jun 22, 2024
26c17fb
Extract constants
saurabh500 Jun 22, 2024
9e79e57
Adding the prelogin handler comment
saurabh500 Jun 22, 2024
3462cf4
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 Jun 22, 2024
5084b3c
adding the GUID for the transport
saurabh500 Jun 22, 2024
ab9c24d
Adding the server info
saurabh500 Jun 22, 2024
df917a8
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelog…
saurabh500 Jun 24, 2024
f762935
Checkout unintended
saurabh500 Jun 24, 2024
a2a5e88
Refactor the handlers and move some methods to context.
saurabh500 Jun 25, 2024
3147398
Merge remote-tracking branch 'ssh/dev/preloginskeleton' into dev/prel…
saurabh500 Jun 25, 2024
96763a6
Usage of Binary Primitives
saurabh500 Jun 26, 2024
4a31ea5
adding the prelogin bug fixes
saurabh500 Jun 26, 2024
d6988d1
adding the refactored changes
saurabh500 Jun 26, 2024
120b4ac
Improve Exception handling
saurabh500 Jun 26, 2024
15bf3b3
Fix the handler class name casing
saurabh500 Jun 26, 2024
182e9c3
Add comments about SNI error and simplify code
saurabh500 Jun 26, 2024
f6b9bcb
Throw the errors
saurabh500 Jun 26, 2024
ded1ea3
add comments
saurabh500 Jun 26, 2024
b60e5e5
Adding tests
saurabh500 Jun 26, 2024
8cdc826
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 Jun 26, 2024
73ccde4
Preallocate prelogin write buffer
saurabh500 Jun 27, 2024
628fc13
Fix bad commit
saurabh500 Jun 27, 2024
44ba256
adding the comments on the context object
saurabh500 Jun 27, 2024
cb9db8b
Commit for Exceptions handling
saurabh500 Jun 27, 2024
40d03d4
adding the simplification of wirte
saurabh500 Jun 27, 2024
83f3477
Using only the offset
saurabh500 Jun 27, 2024
9bd855d
Move to binary primitives
saurabh500 Jun 27, 2024
a11a239
Fix the tests
saurabh500 Jun 27, 2024
79fd559
Adding the tests for Tds Prelogin handler
saurabh500 Jun 27, 2024
b7d81a8
Address CR comments
saurabh500 Jun 27, 2024
c5f07f3
address CR comments
saurabh500 Jun 27, 2024
62bb6ce
adding the csproj changes
saurabh500 Jun 28, 2024
81bae8b
Address CR comments
saurabh500 Jun 28, 2024
57dd183
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 Jun 28, 2024
94bcacf
Remove not needed TODO
saurabh500 Jun 28, 2024
f0e9c54
Merge remote-tracking branch 'ssh/dev/preloginskeleton' into dev/prel…
saurabh500 Jun 28, 2024
36cc5f0
Move write to BinaryPrimitives
saurabh500 Jun 28, 2024
a736173
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelog…
saurabh500 Jun 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -920,11 +920,18 @@
</Compile>
<Compile Include="Microsoft\Data\SqlClientX\IO\TdsBufferAlloc.cs" />
<Compile Include="Microsoft\Data\SqlClientX\IO\TdsWriter.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginHandler.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\ConnectionHandlerContext.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginSubHandlers\PreloginHandlerContext.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginSubHandlers\PreloginPacketHandler.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginSubHandlers\Tds8TlsHandler.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginSubHandlers\Tds74TlsHandler.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\Connection\PreloginSubHandlers\TlsAuthenticator.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\DataSourceParsingHandler.cs" />
<Compile Include="Microsoft\Data\SqlClientX\UnpooledDataSource.cs" />
<Compile Include="Microsoft\Data\SqlClientX\SqlDataSource.cs" />
<Compile Include="Microsoft\Data\SqlClientX\SqlConnector.cs" />
<Compile Include="Microsoft\Data\SqlClientX\Handlers\SqlUtilsX.cs" />
<Compile Include="Microsoft\Data\SqlClientX\SqlDataSourceBuilder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Microsoft\Data\SqlClientX\IO\ITdsReadStream.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.IO;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.SqlClient.SNI;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClientX.Handlers.Connection.PreloginSubHandlers;

namespace Microsoft.Data.SqlClientX.Handlers.Connection
{
/// <summary>
/// Handler to send and receive the prelogin request.
/// This handler will send the prelogin based on the features requested in the connection string.
/// It will consume the prelogin handshake and pass the control to the next handler.
/// </summary>
internal class PreloginHandler : IHandler<ConnectionHandlerContext>
{
/// <summary>
/// The Helper object to perform TLS authentication.
/// </summary>
private readonly TlsAuthenticator _tlsAuthenticator;

/// <summary>
/// A Factory to create sub handlers for the prelogin.
/// </summary>
private readonly PreloginSubHandlerBuilder _subHandlerChainBuilder;

/// <summary>
/// Paraemter-less constructor which creates an authenticator for TLS.
/// </summary>
public PreloginHandler() : this(new TlsAuthenticator(), new PreloginSubHandlerBuilder())
{
}

/// <summary>
/// Constructs Prelogin handler with an authenticator
/// </summary>
/// <param name="tlsAuthenticator">The Tls authenticator to use.</param>
/// <param name="subHandlerChainBuilder">Chain builder.</param>
public PreloginHandler(TlsAuthenticator tlsAuthenticator, PreloginSubHandlerBuilder subHandlerChainBuilder)
{
_tlsAuthenticator = tlsAuthenticator;
_subHandlerChainBuilder = subHandlerChainBuilder;
}

/// <inheritdoc />
public IHandler<ConnectionHandlerContext> NextHandler { get; set; }

/// <inheritdoc />
public async ValueTask Handle(ConnectionHandlerContext connectionContext, bool isAsync, CancellationToken ct)
{
PreloginHandlerContext context = new PreloginHandlerContext(connectionContext);

InitializeSslStream(context);

IHandler<PreloginHandlerContext> firstHandler = _subHandlerChainBuilder.CreateChain(context, _tlsAuthenticator);

await firstHandler.Handle(context, isAsync, ct).ConfigureAwait(false);

if (NextHandler is not null)
{
await NextHandler.Handle(connectionContext, isAsync, ct).ConfigureAwait(false);
}
}

/// <summary>
/// Initializes the SSL required for the TLS handshake.
/// In case of Tds7.4, the SslOverTdsStream is created as well.
/// </summary>
/// <param name="preloginContext"></param>
void InitializeSslStream(PreloginHandlerContext preloginContext)
{
// Create the streams
// If tls first then create a sslStream with the underlying stream as the transport stream.
// if this is not tlsfirst then ssl over tds stream with transport stream as the underlying stream.
Stream transportStream = preloginContext.ConnectionContext.ConnectionStream;

// baseStream is the underlying stream for the SslStream.
Stream baseStream = transportStream;
if (!preloginContext.IsTlsFirst)
{
SslOverTdsStream sslOVerTdsStream;
baseStream = sslOVerTdsStream
= preloginContext.ConnectionContext.SslOverTdsStream
= new SslOverTdsStream(transportStream, preloginContext.ConnectionContext.ConnectionId);
}

SslStream sslStream =
preloginContext.ConnectionContext.SslStream
= new SslStream(baseStream, true, ValidateServerCertificate);

Stream preloginStream = preloginContext.IsTlsFirst ? (Stream)sslStream : (Stream)preloginContext.ConnectionContext.ConnectionStream;

preloginContext.ConnectionContext.TdsStream = new IO.TdsStream(new IO.TdsWriteStream(preloginStream), new IO.TdsReadStream(preloginStream));

bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
Guid connectionId = preloginContext.ConnectionContext.ConnectionId;
if (!preloginContext.ShouldValidateCertificate())
{
SqlClientEventSource.Log.TrySNITraceEvent(nameof(PreloginHandler), EventType.INFO, "Connection Id {0}, Certificate will not be validated.", args0: connectionId);
return true;
}

SqlClientEventSource.Log.TrySNITraceEvent(nameof(PreloginHandler), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: connectionId);

return SNICommon.ValidateSslServerCertificate(connectionId,
Copy link
Member

@cheenamalhotra cheenamalhotra Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not critical at the moment, but these dependencies on Managed SNI can make things complicated for both SqlClient and SqlClientX, so I would recommend adding a comment in such API docs that they are shared APIs, and should be handled carefully.

preloginContext.ConnectionContext.DataSource.ServerName,
preloginContext.HostNameInCertificate,
certificate, preloginContext.ServerCertificateFilename,
sslPolicyErrors);
}
}

/// <summary>
/// A builder to build the prelogin handler chain.
/// </summary>
internal class PreloginSubHandlerBuilder
{
/// <summary>
/// Creates a chain for prelogin based on the context and supplied authenticator.
/// </summary>
/// <param name="context"></param>
/// <param name="authenticator"></param>
/// <returns></returns>
public virtual IHandler<PreloginHandlerContext> CreateChain(PreloginHandlerContext context, TlsAuthenticator authenticator)
{
PreloginPacketHandler preloginPacketHandler = new();
IHandler<PreloginHandlerContext> firstHandler = preloginPacketHandler;
if (context.IsTlsFirst)
{
IHandler<PreloginHandlerContext> tlsHandler = firstHandler = new Tds8TlsHandler(authenticator);
tlsHandler.NextHandler = preloginPacketHandler;
}
else
{
Tds74TlsHandler tlsEndHandler = new(authenticator);
preloginPacketHandler.NextHandler = tlsEndHandler;
}
return firstHandler;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Data.SqlClient;

namespace Microsoft.Data.SqlClientX.Handlers.Connection.PreloginSubHandlers
{
/// <summary>
/// Handler context for Prelogin.
/// </summary>
internal class PreloginHandlerContext : HandlerRequest
{
/// <summary>
/// Encryption options for the connection.
/// </summary>
public SqlConnectionEncryptOption ConnectionEncryptionOption { get; private set; }

/// <summary>
/// If the client should do TLS first.
/// </summary>
public bool IsTlsFirst => (ConnectionEncryptionOption == SqlConnectionEncryptOption.Strict);

/// <summary>
/// If the client should trust the server certificate.
/// </summary>
private bool TrustServerCertificate { get; set; }

/// <summary>
/// Is integrated security enabled?
/// </summary>
public bool IntegratedSecurity { get; private set; }

/// <summary>
/// The Sqlauthentication method being used.
/// </summary>
public SqlAuthenticationMethod AuthType { get; private set; }

/// <summary>
/// The hostname in certificate to validate during TLS handshake.
/// </summary>
public string HostNameInCertificate { get; private set; }

/// <summary>
/// The filename with the server certificate to be used for validating the server certificate.
/// </summary>
public string ServerCertificateFilename { get; private set; }

/// <summary>
/// The server information.
/// </summary>
public ServerInfo ServerInfo { get; private set; }

/// <summary>
/// The encryption option for the client, who state is maintained internally.
/// </summary>
public EncryptionOptions InternalEncryptionOption { get; set; } = EncryptionOptions.OFF;

/// <summary>
/// The original connection context.
/// </summary>
public ConnectionHandlerContext ConnectionContext { get; private set; }

/// <summary>
/// Does the server support encryption?
/// </summary>
public bool ServerSupportsEncryption { get; internal set; }

/// <summary>
/// The Prelogin handshake status.
/// </summary>
public PreLoginHandshakeStatus HandshakeStatus { get; internal set; }

/// <summary>
/// The SNI context in which the operation is being performed.
/// This is used for exception reporting.
/// </summary>
public static SniContext SniContext => SniContext.Snix_PreLogin;

/// <summary>
/// Constructs the Prelogin context from the connection context.
/// </summary>
/// <param name="connectionContext"></param>
public PreloginHandlerContext(ConnectionHandlerContext connectionContext)
{
ConnectionContext = connectionContext;
SqlConnectionString connectionOptions = connectionContext.ConnectionString;
ConnectionEncryptionOption = connectionOptions.Encrypt;
TrustServerCertificate = connectionOptions.TrustServerCertificate;
IntegratedSecurity = connectionOptions.IntegratedSecurity;
AuthType = connectionOptions.Authentication;
HostNameInCertificate = connectionOptions.HostNameInCertificate;
ServerCertificateFilename = connectionOptions.ServerCertificate;
ServerInfo = connectionContext.ServerInfo;
}

/// <summary>
/// Checks if the client should validate the server certificate.
/// </summary>
/// <returns></returns>
public bool ShouldValidateCertificate()
{
if (IsTlsFirst)
{
return true;
}
else
{
return (InternalEncryptionOption == EncryptionOptions.ON && !TrustServerCertificate)
|| (ConnectionContext.AccessTokenInBytes != null && !TrustServerCertificate);
Comment on lines +109 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (InternalEncryptionOption == EncryptionOptions.ON && !TrustServerCertificate)
|| (ConnectionContext.AccessTokenInBytes != null && !TrustServerCertificate);
return !TrustServerCertificate && (InternalEncryptionOption == EncryptionOptions.ON
|| ConnectionContext.AccessTokenInBytes);

Nit: readability suggestion.

}
}

/// <summary>
/// Checks if the client needs encryption.
/// </summary>
/// <returns></returns>
public bool DoesClientNeedEncryption() =>
InternalEncryptionOption is (EncryptionOptions.ON or EncryptionOptions.LOGIN);

}
}
Loading