-
Couldn't load subscription status.
- Fork 316
Prelogin handlers for connectivity #2601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
saurabh500
merged 52 commits into
dotnet:feat/sqlclientx
from
saurabh500:dev/preloginskeleton
Jun 28, 2024
Merged
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
0673c8c
Initial Prelogin
saurabh500 f804638
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelogin
saurabh500 0db2eec
Merge branch 'feat/sqlclientx' into dev/prelogin
saurabh500 0c3cfe8
Adding prelogin handler
saurabh500 33062b3
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelogin
saurabh500 59341ed
Prelogin handler
saurabh500 7e923be
Adding the ssl work in progress
saurabh500 09145d3
Prelogin WIP
saurabh500 bfd5568
Prelogin WIP
saurabh500 3b07335
Adding the prelogin handler changes for encryption
saurabh500 2221463
Fix object Id
saurabh500 48ff556
Clean up the handler
saurabh500 0d620bb
adding the prelogin handler sub handlers
saurabh500 0e2fa17
Adding the sub handlers for prelogin :
saurabh500 9d0f461
Simplify
saurabh500 26c17fb
Extract constants
saurabh500 9e79e57
Adding the prelogin handler comment
saurabh500 3462cf4
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 5084b3c
adding the GUID for the transport
saurabh500 ab9c24d
Adding the server info
saurabh500 df917a8
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelog…
saurabh500 f762935
Checkout unintended
saurabh500 a2a5e88
Refactor the handlers and move some methods to context.
saurabh500 3147398
Merge remote-tracking branch 'ssh/dev/preloginskeleton' into dev/prel…
saurabh500 96763a6
Usage of Binary Primitives
saurabh500 4a31ea5
adding the prelogin bug fixes
saurabh500 d6988d1
adding the refactored changes
saurabh500 120b4ac
Improve Exception handling
saurabh500 15bf3b3
Fix the handler class name casing
saurabh500 182e9c3
Add comments about SNI error and simplify code
saurabh500 f6b9bcb
Throw the errors
saurabh500 ded1ea3
add comments
saurabh500 b60e5e5
Adding tests
saurabh500 8cdc826
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 73ccde4
Preallocate prelogin write buffer
saurabh500 628fc13
Fix bad commit
saurabh500 44ba256
adding the comments on the context object
saurabh500 cb9db8b
Commit for Exceptions handling
saurabh500 40d03d4
adding the simplification of wirte
saurabh500 83f3477
Using only the offset
saurabh500 9bd855d
Move to binary primitives
saurabh500 a11a239
Fix the tests
saurabh500 79fd559
Adding the tests for Tds Prelogin handler
saurabh500 b7d81a8
Address CR comments
saurabh500 c5f07f3
address CR comments
saurabh500 62bb6ce
adding the csproj changes
saurabh500 81bae8b
Address CR comments
saurabh500 57dd183
Merge branch 'feat/sqlclientx' into dev/preloginskeleton
saurabh500 94bcacf
Remove not needed TODO
saurabh500 f0e9c54
Merge remote-tracking branch 'ssh/dev/preloginskeleton' into dev/prel…
saurabh500 36cc5f0
Move write to BinaryPrimitives
saurabh500 a736173
Merge remote-tracking branch 'origin/feat/sqlclientx' into dev/prelog…
saurabh500 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
...ta.SqlClient/netcore/src/Microsoft/Data/SqlClientX/Handlers/Connection/PreloginHandler.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| 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; | ||
| } | ||
| } | ||
| } | ||
| } | ||
122 changes: 122 additions & 0 deletions
122
...crosoft/Data/SqlClientX/Handlers/Connection/PreloginSubHandlers/PreloginHandlerContext.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||||||||||
saurabh500 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| { | ||||||||||
| return true; | ||||||||||
| } | ||||||||||
| else | ||||||||||
| { | ||||||||||
| return (InternalEncryptionOption == EncryptionOptions.ON && !TrustServerCertificate) | ||||||||||
| || (ConnectionContext.AccessTokenInBytes != null && !TrustServerCertificate); | ||||||||||
|
Comment on lines
+109
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nit: readability suggestion. |
||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// <summary> | ||||||||||
| /// Checks if the client needs encryption. | ||||||||||
| /// </summary> | ||||||||||
| /// <returns></returns> | ||||||||||
| public bool DoesClientNeedEncryption() => | ||||||||||
| InternalEncryptionOption is (EncryptionOptions.ON or EncryptionOptions.LOGIN); | ||||||||||
|
|
||||||||||
| } | ||||||||||
| } | ||||||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.