Skip to content

Commit

Permalink
Merged PR 3992: [5.1.3]
Browse files Browse the repository at this point in the history
  • Loading branch information
David-Engel committed Oct 18, 2023
1 parent 3fd439b commit 4deb800
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
int payloadOffset = 0;
int payloadLength = 0;
int option = payload[offset++];
bool serverSupportsEncryption = false;

while (option != (byte)PreLoginOptions.LASTOPT)
{
Expand All @@ -996,6 +997,13 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
break;

case (int)PreLoginOptions.ENCRYPT:
if (tlsFirst)
{
// Can skip/ignore this option if we are doing TDS 8.
offset += 4;
break;
}

payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];

Expand All @@ -1009,16 +1017,11 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
LOGIN
} */

// Any response other than NOT_SUP means the server supports encryption.
serverSupportsEncryption = serverOption != EncryptionOptions.NOT_SUP;

switch (_encryptionOption)
{
case (EncryptionOptions.ON):
if (serverOption == EncryptionOptions.NOT_SUP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}
break;
case (EncryptionOptions.OFF):
if (serverOption == EncryptionOptions.OFF)
{
Expand All @@ -1034,31 +1037,26 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
break;

case (EncryptionOptions.NOT_SUP):
if (!tlsFirst && serverOption == EncryptionOptions.REQ)
if (serverOption == EncryptionOptions.REQ)
{
// Server requires encryption, but client does not support it.
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

break;
default:
Debug.Fail("Invalid client encryption option detected");
// Any other client option needs encryption
if (serverOption == EncryptionOptions.NOT_SUP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}
break;
}

if (_encryptionOption == EncryptionOptions.ON ||
_encryptionOption == EncryptionOptions.LOGIN)
{
// Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server.
bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) ||
(_connHandler._accessTokenInBytes != null && !trustServerCert);
uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
| (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);

EnableSsl(info, encrypt, integratedSecurity, serverCert);
}

break;

case (int)PreLoginOptions.INSTANCE:
Expand Down Expand Up @@ -1139,6 +1137,25 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
}
}

if (_encryptionOption == EncryptionOptions.ON ||
_encryptionOption == EncryptionOptions.LOGIN)
{
if (!serverSupportsEncryption)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

// Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server.
bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) ||
(_connHandler._accessTokenInBytes != null && !trustServerCert);
uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
| (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);

EnableSsl(info, encrypt, integratedSecurity, serverCert);
}

return PreLoginHandshakeStatus.Successful;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
int payloadOffset = 0;
int payloadLength = 0;
int option = payload[offset++];
bool serverSupportsEncryption = false;
bool serverSupportsCTAIP = false;

while (option != (byte)PreLoginOptions.LASTOPT)
{
Expand All @@ -1415,29 +1417,33 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
break;

case (int)PreLoginOptions.ENCRYPT:
if (tlsFirst)
{
// Can skip/ignore this option if we are doing TDS 8.
offset += 4;
break;
}

payloadOffset = payload[offset++] << 8 | payload[offset++];
payloadLength = payload[offset++] << 8 | payload[offset++];

EncryptionOptions serverOption = (EncryptionOptions)payload[payloadOffset];
/* internal enum EncryptionOptions {
OFF,
ON,
NOT_SUP,
REQ,
LOGIN
} */
OFF,
ON,
NOT_SUP,
REQ,
LOGIN,
OPTIONS_MASK = 0x3f,
CTAIP = 0x40,
CLIENT_CERT = 0x80,
} */

// Any response other than NOT_SUP means the server supports encryption.
serverSupportsEncryption = (serverOption & EncryptionOptions.OPTIONS_MASK) != EncryptionOptions.NOT_SUP;

switch (_encryptionOption & EncryptionOptions.OPTIONS_MASK)
{
case (EncryptionOptions.ON):
if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.NOT_SUP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

break;

case (EncryptionOptions.OFF):
if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.OFF)
{
Expand All @@ -1453,8 +1459,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
break;

case (EncryptionOptions.NOT_SUP):
if (!tlsFirst && (serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ)
if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.REQ)
{
// Server requires encryption, but client does not support it.
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByClient(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
Expand All @@ -1463,37 +1470,20 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
break;

default:
Debug.Fail("Invalid client encryption option detected");
// Any other client option needs encryption
if ((serverOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.NOT_SUP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

break;
}

// Check if the server will accept CTAIP.
//
if ((_encryptionOption & EncryptionOptions.CTAIP) != 0 &&
(serverOption & EncryptionOptions.CTAIP) == 0)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.CTAIP_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.CTAIPNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

if ((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON ||
(_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN)
{

if (serverCallback != null)
{
trustServerCert = true;
}

// Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server.
bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert);

UInt32 info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
| (is2005OrLater && (_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0 ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);

EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename, serverCallback, clientCallback);
}
serverSupportsCTAIP = (serverOption & EncryptionOptions.CTAIP) != 0;

break;

Expand Down Expand Up @@ -1576,6 +1566,37 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake(
}
}

if ((_encryptionOption & EncryptionOptions.CTAIP) != 0 && !serverSupportsCTAIP)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.CTAIP_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.CTAIPNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

if ((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON ||
(_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN)
{
if (!serverSupportsEncryption)
{
_physicalStateObj.AddError(new SqlError(TdsEnums.ENCRYPTION_NOT_SUPPORTED, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.EncryptionNotSupportedByServer(), "", 0));
_physicalStateObj.Dispose();
ThrowExceptionAndWarning(_physicalStateObj);
}

if (serverCallback != null)
{
trustServerCert = true;
}

// Validate Certificate if Trust Server Certificate=false and Encryption forced (EncryptionOptions.ON) from Server.
bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((authType != SqlAuthenticationMethod.NotSpecified || _connHandler._accessTokenInBytes != null) && !trustServerCert);

uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0)
| (is2005OrLater && (_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0 ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0);

EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename, serverCallback, clientCallback);
}

return PreLoginHandshakeStatus.Successful;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ public void IntegratedAuthConnectionTest()
connection.Open();
}

/// <summary>
/// Runs a test where TDS Server doesn't send encryption info during pre-login response.
/// The driver is expected to fail when that happens, and terminate the connection during pre-login phase
/// when client enables encryption using Encrypt=true or uses default encryption setting.
/// </summary>
[Fact]
public async Task PreLoginEncryptionExcludedTest()
{
using TestTdsServer server = TestTdsServer.StartTestServer(false, false, 5, excludeEncryption: true);
SqlConnectionStringBuilder builder = new(server.ConnectionString)
{
IntegratedSecurity = true
};

using SqlConnection connection = new(builder.ConnectionString);
Exception ex = await Assert.ThrowsAsync<SqlException>(async () => await connection.OpenAsync());
Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArmProcess))]
[InlineData(40613)]
[InlineData(42108)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args)
Engine = engine;
}

public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "")
public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "")
{
TDSServerArguments args = new TDSServerArguments()
{
Expand All @@ -36,6 +36,10 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool
{
args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
}
if (excludeEncryption)
{
args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None;
}

TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args);
server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
Expand All @@ -45,14 +49,17 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool
server._endpoint.Start();

int port = server._endpoint.ServerEndPoint.Port;
server._connectionStringBuilder = new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
server._connectionStringBuilder = excludeEncryption
// Allow encryption to be set when encryption is to be excluded from pre-login response.
? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory }
: new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
server.ConnectionString = server._connectionStringBuilder.ConnectionString;
return server;
}

public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "")
public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "")
{
return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, methodName);
return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, excludeEncryption, methodName);
}

public void Dispose() => _endpoint?.Stop();
Expand Down
Loading

0 comments on commit 4deb800

Please sign in to comment.