From 7aeea23434b44f995148e4fb324dd24456d01031 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Thu, 5 May 2022 17:30:04 -0700 Subject: [PATCH 01/11] net fx draft --- .../SqlClient/SqlInternalConnectionTds.cs | 26 ++++---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 26 ++++++-- .../Data/Common/DbConnectionStringCommon.cs | 8 +++ .../Data/SqlClient/SqlConnectionString.cs | 24 ++++++- .../SqlClient/SqlConnectionStringBuilder.cs | 64 ++++++++++++++++++- 5 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 616c44a3f4..cacb7de65b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1877,7 +1877,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName); + serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -2047,7 +2047,7 @@ TimeoutTimer timeout long timeoutUnitInterval; string protocol = ConnectionOptions.NetworkLibrary; - ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost); + ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (null == ServerProvidedFailOverPartner) @@ -2150,7 +2150,7 @@ TimeoutTimer timeout _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); - currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName); + currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; @@ -2296,13 +2296,9 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt this, ignoreSniOpenTimeout, timeout.LegacyTimerExpire, - ConnectionOptions.Encrypt, - ConnectionOptions.TrustServerCertificate, - ConnectionOptions.IntegratedSecurity, + ConnectionOptions, withFailover, isFirstTransparentAttempt, - ConnectionOptions.Authentication, - ConnectionOptions.Certificate, _serverCallback, _clientCallback, _originalNetworkAddressInfo != null, @@ -3244,6 +3240,7 @@ internal sealed class ServerInfo internal string ResolvedServerName { get; private set; } // the resolved servername only internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution internal string UserProtocol { get; private set; } // the user specified protocol + internal string ServerSPN { get; private set; } // the server SPN // The original user-supplied server name from the connection string. // If connection string has no Data Source, the value is set to string.Empty. @@ -3264,10 +3261,16 @@ private set internal readonly string PreRoutingServerName; // Initialize server info from connection options, - internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) { } + internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { } + + // Initialize server info from connection options, but override DataSource and ServerSPN with given server name and server SPN + internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSPN) : this(userOptions, serverName) + { + ServerSPN = serverSPN; + } // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, string serverName) + private ServerInfo(SqlConnectionString userOptions, string serverName) { //----------------- // Preconditions @@ -3286,7 +3289,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName) // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) + internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName, string serverSPN) { //----------------- // Preconditions @@ -3307,6 +3310,7 @@ internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string UserProtocol = TdsEnums.TCP; SetDerivedNames(UserProtocol, UserServerName); ResolvedDatabaseName = userOptions.InitialCatalog; + ServerSPN = serverSPN; } internal void SetDerivedNames(string protocol, string serverName) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index e69df7dd19..a726fa2830 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -493,18 +493,20 @@ internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - bool encrypt, - bool trustServerCert, - bool integratedSecurity, + SqlConnectionString connectionOptions, bool withFailover, bool isFirstTransparentAttempt, - SqlAuthenticationMethod authType, - string certificate, ServerCertificateValidationCallback serverCallback, ClientCertificateRetrievalCallback clientCallback, bool useOriginalAddressInfo, bool disableTnir) { + bool encrypt = connectionOptions.Encrypt; + bool trustServerCert = connectionOptions.TrustServerCertificate; + bool integratedSecurity = connectionOptions.IntegratedSecurity; + SqlAuthenticationMethod authType = connectionOptions.Authentication; + string certificate = connectionOptions.Certificate; + if (_state != TdsParserState.Closed) { Debug.Fail("TdsParser.Connect called on non-closed connection!"); @@ -542,8 +544,18 @@ internal void Connect(ServerInfo serverInfo, if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { LoadSSPILibrary(); - // now allocate proper length of buffer - _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) + { + byte[] srvSPN = Encoding.Unicode.GetBytes(serverInfo.ServerSPN); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provider SPN length exceeded the buffer size."); + _sniSpnBuffer = srvSPN; + SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN); + } + else + { + // now allocate proper length of buffer + _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + } SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication"); } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index 0557ebaa75..1a779c02e8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs @@ -976,6 +976,8 @@ internal static class DbConnectionStringDefaults internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified; internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto; + internal const string ServerSPN = ""; + internal const string FailoverPartnerSPN = ""; } internal static class DbConnectionStringKeywords @@ -1029,6 +1031,8 @@ internal static class DbConnectionStringKeywords internal const string EnclaveAttestationUrl = "Enclave Attestation Url"; internal const string AttestationProtocol = "Attestation Protocol"; internal const string IPAddressPreference = "IP Address Preference"; + internal const string ServerSPN = "Server SPN"; + internal const string FailoverPartnerSPN = "Failover Partner SPN"; // common keywords (OleDb, OracleClient, SqlClient) internal const string DataSource = "Data Source"; @@ -1122,5 +1126,9 @@ internal static class DbConnectionStringSynonyms //internal const string WorkstationID = WSID; internal const string WSID = "wsid"; + + //internal const string server SPNs + internal const string ServerSPN = "ServerSPN"; + internal const string FailoverPartnerSPN = "FailoverPartnerSPN"; } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index f1a488c4b6..67caeb6621 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -59,6 +59,8 @@ internal static class DEFAULT internal static readonly SqlAuthenticationMethod Authentication = DbConnectionStringDefaults.Authentication; internal static readonly SqlConnectionAttestationProtocol AttestationProtocol = DbConnectionStringDefaults.AttestationProtocol; internal static readonly SqlConnectionIPAddressPreference IpAddressPreference = DbConnectionStringDefaults.IPAddressPreference; + internal const string ServerSPN = DbConnectionStringDefaults.ServerSPN; + internal const string FailoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; #if NETFRAMEWORK internal static readonly bool TransparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution; internal const bool Connection_Reset = DbConnectionStringDefaults.ConnectionReset; @@ -113,6 +115,8 @@ internal static class KEY internal const string Connect_Retry_Count = DbConnectionStringKeywords.ConnectRetryCount; internal const string Connect_Retry_Interval = DbConnectionStringKeywords.ConnectRetryInterval; internal const string Authentication = DbConnectionStringKeywords.Authentication; + internal const string Server_SPN = DbConnectionStringKeywords.ServerSPN; + internal const string Failover_Partner_SPN = DbConnectionStringKeywords.FailoverPartnerSPN; #if NETFRAMEWORK internal const string TransparentNetworkIPResolution = DbConnectionStringKeywords.TransparentNetworkIPResolution; #if ADONET_CERT_AUTH @@ -173,6 +177,9 @@ private static class SYNONYM internal const string User = DbConnectionStringSynonyms.User; // workstation id internal const string WSID = DbConnectionStringSynonyms.WSID; + // server SPNs + internal const string ServerSPN = DbConnectionStringSynonyms.ServerSPN; + internal const string FailoverPartnerSPN = DbConnectionStringSynonyms.FailoverPartnerSPN; #if NETFRAMEWORK internal const string TRANSPARENTNETWORKIPRESOLUTION = DbConnectionStringSynonyms.TRANSPARENTNETWORKIPRESOLUTION; @@ -212,9 +219,9 @@ internal static class TRANSACTIONBINDING } #if NETFRAMEWORK - internal const int SynonymCount = 29; + internal const int SynonymCount = 31; #else - internal const int SynonymCount = 26; + internal const int SynonymCount = 28; internal const int DeprecatedSynonymCount = 2; #endif // NETFRAMEWORK @@ -257,6 +264,8 @@ internal static class TRANSACTIONBINDING private readonly string _initialCatalog; private readonly string _password; private readonly string _userID; + private readonly string _serverSPN; + private readonly string _failoverPartnerSPN; private readonly string _workstationId; @@ -322,6 +331,8 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _enclaveAttestationUrl = ConvertValueToString(KEY.EnclaveAttestationUrl, DEFAULT.EnclaveAttestationUrl); _attestationProtocol = ConvertValueToAttestationProtocol(); _ipAddressPreference = ConvertValueToIPAddressPreference(); + _serverSPN = ConvertValueToString(KEY.Server_SPN, DEFAULT.ServerSPN); + _failoverPartnerSPN = ConvertValueToString(KEY.Failover_Partner_SPN, DEFAULT.FailoverPartnerSPN); // Temporary string - this value is stored internally as an enum. string typeSystemVersionString = ConvertValueToString(KEY.Type_System_Version, null); @@ -675,6 +686,8 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _columnEncryptionSetting = connectionOptions._columnEncryptionSetting; _enclaveAttestationUrl = connectionOptions._enclaveAttestationUrl; _attestationProtocol = connectionOptions._attestationProtocol; + _serverSPN = connectionOptions._serverSPN; + _failoverPartnerSPN = connectionOptions._failoverPartnerSPN; #if NETFRAMEWORK _connectionReset = connectionOptions._connectionReset; _contextConnection = connectionOptions._contextConnection; @@ -732,7 +745,8 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS internal string UserID => _userID; internal string WorkstationId => _workstationId; internal PoolBlockingPeriod PoolBlockingPeriod => _poolBlockingPeriod; - + internal string ServerSPN => _serverSPN; + internal string FailoverPartnerSPN => _failoverPartnerSPN; internal TypeSystem TypeSystemVersion => _typeSystemVersion; internal Version TypeSystemAssemblyVersion => _typeSystemAssemblyVersion; @@ -843,6 +857,8 @@ internal static Dictionary GetParseSynonyms() { KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval }, { KEY.Authentication, KEY.Authentication }, { KEY.IPAddressPreference, KEY.IPAddressPreference }, + { KEY.Server_SPN, KEY.Server_SPN }, + { KEY.Failover_Partner_SPN, KEY.Failover_Partner_SPN }, { SYNONYM.APP, KEY.Application_Name }, { SYNONYM.APPLICATIONINTENT, KEY.ApplicationIntent }, @@ -871,6 +887,8 @@ internal static Dictionary GetParseSynonyms() { SYNONYM.UID, KEY.User_ID }, { SYNONYM.User, KEY.User_ID }, { SYNONYM.WSID, KEY.Workstation_Id }, + { SYNONYM.ServerSPN, KEY.Server_SPN }, + { SYNONYM.FailoverPartnerSPN, KEY.Failover_Partner_SPN }, #if NETFRAMEWORK #if ADONET_CERT_AUTH { KEY.Certificate, KEY.Certificate }, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index b78c2e392b..b73ac0d532 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -65,6 +65,8 @@ private enum Keywords AttestationProtocol, CommandTimeout, IPAddressPreference, + ServerSPN, + FailoverPartnerSPN, #if NETFRAMEWORK ConnectionReset, NetworkLibrary, @@ -122,6 +124,8 @@ private enum Keywords private string _enclaveAttestationUrl = DbConnectionStringDefaults.EnclaveAttestationUrl; private SqlConnectionAttestationProtocol _attestationProtocol = DbConnectionStringDefaults.AttestationProtocol; private SqlConnectionIPAddressPreference _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference; + private string _serverSPN = DbConnectionStringDefaults.ServerSPN; + private string _failoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; #if NETFRAMEWORK private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset; @@ -176,11 +180,13 @@ private static string[] CreateValidKeywords() validKeywords[(int)Keywords.EnclaveAttestationUrl] = DbConnectionStringKeywords.EnclaveAttestationUrl; validKeywords[(int)Keywords.AttestationProtocol] = DbConnectionStringKeywords.AttestationProtocol; validKeywords[(int)Keywords.IPAddressPreference] = DbConnectionStringKeywords.IPAddressPreference; + validKeywords[(int)Keywords.ServerSPN] = DbConnectionStringKeywords.ServerSPN; + validKeywords[(int)Keywords.FailoverPartnerSPN] = DbConnectionStringKeywords.FailoverPartnerSPN; #if NETFRAMEWORK validKeywords[(int)Keywords.ConnectionReset] = DbConnectionStringKeywords.ConnectionReset; + validKeywords[(int)Keywords.NetworkLibrary] = DbConnectionStringKeywords.NetworkLibrary; validKeywords[(int)Keywords.ContextConnection] = DbConnectionStringKeywords.ContextConnection; validKeywords[(int)Keywords.TransparentNetworkIPResolution] = DbConnectionStringKeywords.TransparentNetworkIPResolution; - validKeywords[(int)Keywords.NetworkLibrary] = DbConnectionStringKeywords.NetworkLibrary; #if ADONET_CERT_AUTH validKeywords[(int)Keywords.Certificate] = DbConnectionStringKeywords.Certificate; #endif @@ -228,6 +234,8 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringKeywords.EnclaveAttestationUrl, Keywords.EnclaveAttestationUrl }, { DbConnectionStringKeywords.AttestationProtocol, Keywords.AttestationProtocol }, { DbConnectionStringKeywords.IPAddressPreference, Keywords.IPAddressPreference }, + { DbConnectionStringKeywords.ServerSPN, Keywords.ServerSPN }, + { DbConnectionStringKeywords.FailoverPartnerSPN, Keywords.FailoverPartnerSPN }, #if NETFRAMEWORK { DbConnectionStringKeywords.ConnectionReset, Keywords.ConnectionReset }, @@ -266,7 +274,9 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringSynonyms.PERSISTSECURITYINFO, Keywords.PersistSecurityInfo }, { DbConnectionStringSynonyms.UID, Keywords.UserID }, { DbConnectionStringSynonyms.User, Keywords.UserID }, - { DbConnectionStringSynonyms.WSID, Keywords.WorkstationID } + { DbConnectionStringSynonyms.WSID, Keywords.WorkstationID }, + { DbConnectionStringSynonyms.ServerSPN, Keywords.ServerSPN }, + { DbConnectionStringSynonyms.FailoverPartnerSPN, Keywords.FailoverPartnerSPN }, }; Debug.Assert((KeywordsCount + SqlConnectionString.SynonymCount) == pairs.Count, "initial expected size is incorrect"); return pairs; @@ -373,7 +383,10 @@ private object GetAt(Keywords index) return AttestationProtocol; case Keywords.IPAddressPreference: return IPAddressPreference; - + case Keywords.ServerSPN: + return ServerSPN; + case Keywords.FailoverPartnerSPN: + return FailoverPartnerSPN; #if NETFRAMEWORK #pragma warning disable 618 // Obsolete properties case Keywords.ConnectionReset: @@ -518,6 +531,12 @@ private void Reset(Keywords index) case Keywords.IPAddressPreference: _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference; break; + case Keywords.ServerSPN: + _serverSPN = DbConnectionStringDefaults.ServerSPN; + break; + case Keywords.FailoverPartnerSPN: + _failoverPartnerSPN = DbConnectionStringDefaults.FailoverPartnerSPN; + break; #if NETFRAMEWORK case Keywords.ConnectionReset: _connectionReset = DbConnectionStringDefaults.ConnectionReset; @@ -1010,6 +1029,12 @@ public override object this[string keyword] case Keywords.ConnectRetryInterval: ConnectRetryInterval = ConvertToInt32(value); break; + case Keywords.ServerSPN: + ServerSPN = ConvertToString(value); + break; + case Keywords.FailoverPartnerSPN: + FailoverPartnerSPN = ConvertToString(value); + break; #if NETFRAMEWORK #pragma warning disable 618 // Obsolete properties case Keywords.ConnectionReset: @@ -1165,6 +1190,23 @@ public string DataSource } } + /// + /// The SPN for the server. The default value is an empty string. An empty string causes SQL Server Native Client to use the default, provider-generated SPN. + /// + [DisplayName(DbConnectionStringKeywords.ServerSPN)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] + //[ResDescription(StringsHelper.ResourceNames.DbConnectionString_ServerSPN)] + [RefreshProperties(RefreshProperties.All)] + public string ServerSPN + { + get => _serverSPN; + set + { + SetValue(DbConnectionStringKeywords.ServerSPN, value); + _serverSPN = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.Encrypt)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Security)] @@ -1303,6 +1345,22 @@ public string FailoverPartner } } + /// + /// The SPN for the failover partner. The default value is an empty string. An empty string causes SQL Server Native Client to use the default, provider-generated SPN. + /// + [DisplayName(DbConnectionStringKeywords.FailoverPartnerSPN)] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] + [RefreshProperties(RefreshProperties.All)] + public string FailoverPartnerSPN + { + get => _failoverPartnerSPN; + set + { + SetValue(DbConnectionStringKeywords.FailoverPartnerSPN, value); + _failoverPartnerSPN = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.InitialCatalog)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] From f56de8f28a314d1fde0c7cb04c7aaf6cc205110a Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Mon, 16 May 2022 12:05:37 -0700 Subject: [PATCH 02/11] net fx --- .../netfx/ref/Microsoft.Data.SqlClient.cs | 8 ++++++++ .../netfx/src/Resources/Strings.Designer.cs | 20 ++++++++++++++++++- .../netfx/src/Resources/Strings.resx | 6 ++++++ .../SqlClient/SqlConnectionStringBuilder.cs | 11 ++++------ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index d5ac6b0611..26fa8c5f22 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -1015,6 +1015,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public string FailoverPartner { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string FailoverPartnerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Initial Catalog")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] @@ -1078,6 +1082,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Replication")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public bool Replication { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Server SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string ServerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Transaction Binding")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index bfaf7407be..641a423839 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace System { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -5694,6 +5694,15 @@ internal static string DbConnectionString_FailoverPartner { } } + /// + /// Looks up a localized string similar to Indicates the service principal name (SPN) of the failover partner to connect to.. + /// + internal static string DbConnectionString_FailoverPartnerSPN { + get { + return ResourceManager.GetString("DbConnectionString_FailoverPartnerSPN", resourceCulture); + } + } + /// /// Looks up a localized string similar to The UDL file to use when connecting to the Data Source.. /// @@ -5847,6 +5856,15 @@ internal static string DbConnectionString_Replication { } } + /// + /// Looks up a localized string similar to Indicates the service principal name (SPN) of the data source to connect to.. + /// + internal static string DbConnectionString_ServerSPN { + get { + return ResourceManager.GetString("DbConnectionString_ServerSPN", resourceCulture); + } + } + /// /// Looks up a localized string similar to Indicates binding behavior of connection to a System.Transactions Transaction when enlisted.. /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index acf6a6beff..ae500ff140 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -4617,4 +4617,10 @@ Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. + + Indicates the service principal name (SPN) of the failover partner to connect to. + + + Indicates the service principal name (SPN) of the data source to connect to. + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index b73ac0d532..dc519f2a3b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -1190,12 +1190,10 @@ public string DataSource } } - /// - /// The SPN for the server. The default value is an empty string. An empty string causes SQL Server Native Client to use the default, provider-generated SPN. - /// + /// [DisplayName(DbConnectionStringKeywords.ServerSPN)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] - //[ResDescription(StringsHelper.ResourceNames.DbConnectionString_ServerSPN)] + [ResDescription(StringsHelper.ResourceNames.DbConnectionString_ServerSPN)] [RefreshProperties(RefreshProperties.All)] public string ServerSPN { @@ -1345,11 +1343,10 @@ public string FailoverPartner } } - /// - /// The SPN for the failover partner. The default value is an empty string. An empty string causes SQL Server Native Client to use the default, provider-generated SPN. - /// + /// [DisplayName(DbConnectionStringKeywords.FailoverPartnerSPN)] [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] + [ResDescription(StringsHelper.ResourceNames.DbConnectionString_FailoverPartnerSPN)] [RefreshProperties(RefreshProperties.All)] public string FailoverPartnerSPN { From 0104fb5c50cc0032ce9d10a39064f5cff6783cea Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Mon, 16 May 2022 12:09:28 -0700 Subject: [PATCH 03/11] net core & doc --- .../SqlConnection.xml | 2 + .../SqlConnectionStringBuilder.xml | 38 +++++++++++++++++++ .../netcore/ref/Microsoft.Data.SqlClient.cs | 8 ++++ .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 12 ++++-- .../SqlClient/SqlInternalConnectionTds.cs | 25 +++++++----- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 16 ++++---- .../Data/SqlClient/TdsParserStateObject.cs | 2 +- .../SqlClient/TdsParserStateObjectManaged.cs | 4 +- .../SqlClient/TdsParserStateObjectNative.cs | 15 +++++++- .../netcore/src/Resources/StringsHelper.cs | 4 +- 10 files changed, 99 insertions(+), 27 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 00595e0afe..e734e4d5d1 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -540,6 +540,7 @@ End Module |Encrypt|'true'|When `true`, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `TrustServerCertificate` is false and `Encrypt` is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| |Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| |Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

The server name can be 128 characters or less.

If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| +|Failover Partner SPN

-or-

FailoverPartnerSPN|N/A|The SPN for the faileover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| |Initial Catalog

-or-

Database|N/A|The name of the database.

The database name can be 128 characters or less.| |Integrated Security

-or-

Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.

Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.

If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.

is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).| |IP Address Preference

-or-

IPAddressPreference|IPv4First|The IP address family preference when establishing TCP connections. If `Transparent Network IP Resolution` (in .NET Framework) or `Multi Subnet Failover` is set to true, this setting has no effect. Supported values include:

`IPAddressPreference=IPv4First`

`IPAddressPreference=IPv6First`

`IPAddressPreference=UsePlatformDefault`| @@ -555,6 +556,7 @@ End Module |Pool Blocking Period

-or-

PoolBlockingPeriod|Auto|Sets the blocking period behavior for a connection pool. See property for details.| |Pooling|'true'|When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool.

Connections are considered the same if they have the same connection string. Different connections have different connection strings.

The value of this key can be "true", "false", "yes", or "no".| |Replication|'false'|`true` if replication is supported using the connection.| +|Server SPN

-or-

ServerSPN|N/A|The SPN for the data source. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| |Transaction Binding|Implicit Unbind|Controls connection association with an enlisted `System.Transactions` transaction.

Possible values are:

`Transaction Binding=Implicit Unbind;`

`Transaction Binding=Explicit Unbind;`

Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The `System.Transactions.Transaction.Current` property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.

If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw .

Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit `SqlConnection.TransactionEnlist(null)` is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An `InvalidOperationException` is thrown if `Transaction.Current` is not the enlisted transaction or if the enlisted transaction is not active.| |Transparent Network IP Resolution

-or-

TransparentNetworkIPResolution|See description.|When the value of this key is set to `true`, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address.

If the `MultiSubnetFailover` key is set to `true`, `TransparentNetworkIPResolution` is ignored.

If the `Failover Partner` key is set, `TransparentNetworkIPResolution` is ignored.

The value of this key must be `true`, `false`, `yes`, or `no`.

A value of `yes` is treated the same as a value of `true`.

A value of `no` is treated the same as a value of `false`.

The default values are as follows:

  • `false` when:

    • Connecting to Azure SQL Database where the data source ends with:

      • .database.chinacloudapi.cn
      • .database.usgovcloudapi.net
      • .database.cloudapi.de
      • .database.windows.net
    • `Authentication` is 'Active Directory Password' or 'Active Directory Integrated'
  • `true` in all other cases.
| |Trust Server Certificate

-or-

TrustServerCertificate|'false'|When set to `true`, SSL is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 72cf7aec1e..5c1308bd9b 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -459,6 +459,25 @@ If you specify a failover partner and the primary server is not configured for d ]]> + + Gets or sets the service principal name (SPN) of the failover partner is supported using the connection. + + The value of the property, or if none has been supplied. + + + + [!NOTE] +> The provided value only applies with integrated security authentication mode otherwise will be ignored. + + ]]> + + + To be added. To be added. @@ -820,6 +839,25 @@ Database = AdventureWorks ]]> + + Gets or sets the service principal name (SPN) of the data source is supported using the connection. + + The value of the property, or if none has been supplied. + + + + [!NOTE] +> The provided value only applies with integrated security authentication mode otherwise will be ignored. + + ]]> + + + The key to locate in the . Indicates whether the specified key exists in this instance. diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 12e8094ff0..bbea3d73fa 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -1003,6 +1003,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public string FailoverPartner { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string FailoverPartnerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Initial Catalog")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] @@ -1061,6 +1065,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Replication")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public bool Replication { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Server SPN")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public string ServerSPN { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Transaction Binding")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index 501a68e401..ab6b36d1c9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -9,7 +9,6 @@ using System.Net.Security; using System.Net.Sockets; using System.Text; -using System.Text.RegularExpressions; namespace Microsoft.Data.SqlClient.SNI { @@ -135,6 +134,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// Timer expiration /// Instance name /// SPN + /// pre-defined SPN /// Flush packet cache /// Asynchronous connection /// Attempt parallel connects @@ -143,7 +143,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// Used for DNS Cache /// Used for DNS Cache /// SNI handle - internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, + internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, string serverSPN, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { instanceName = new byte[1]; @@ -185,7 +185,7 @@ internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ign { try { - spnBuffer = GetSqlServerSPNs(details); + spnBuffer = GetSqlServerSPNs(details, serverSPN); } catch (Exception e) { @@ -197,9 +197,13 @@ internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ign return sniHandle; } - private static byte[][] GetSqlServerSPNs(DataSource dataSource) + private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN) { Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName)); + if (!string.IsNullOrWhiteSpace(serverSPN)) + { + return new byte[1][] { Encoding.UTF8.GetBytes(serverSPN) }; + } string hostName = dataSource.ServerName; string postfix = null; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index dcbc62ef0d..9cc2a3564f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1549,7 +1549,7 @@ private void LoginNoFailover(ServerInfo serverInfo, throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName); + serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -1693,7 +1693,7 @@ TimeoutTimer timeout int sleepInterval = 100; //milliseconds to sleep (back off) between attempts. long timeoutUnitInterval; - ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost); + ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost, connectionOptions.FailoverPartnerSPN); ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (null == ServerProvidedFailOverPartner) @@ -1882,11 +1882,8 @@ private void AttemptOneLogin( this, ignoreSniOpenTimeout, timeout.LegacyTimerExpire, - ConnectionOptions.Encrypt, - ConnectionOptions.TrustServerCertificate, - ConnectionOptions.IntegratedSecurity, - withFailover, - ConnectionOptions.Authentication); + ConnectionOptions, + withFailover); _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); @@ -2776,6 +2773,7 @@ internal sealed class ServerInfo internal string ResolvedServerName { get; private set; } // the resolved servername only internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution internal string UserProtocol { get; private set; } // the user specified protocol + internal string ServerSPN { get; private set; } // the server SPN // The original user-supplied server name from the connection string. // If connection string has no Data Source, the value is set to string.Empty. @@ -2796,10 +2794,16 @@ private set internal readonly string PreRoutingServerName; // Initialize server info from connection options, - internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) { } + internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { } + + // Initialize server info from connection options, but override DataSource and ServerSPN with given server name and server SPN + internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSPN) : this(userOptions, serverName) + { + ServerSPN = serverSPN; + } // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, string serverName) + private ServerInfo(SqlConnectionString userOptions, string serverName) { //----------------- // Preconditions @@ -2818,7 +2822,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName) // Initialize server info from connection options, but override DataSource with given server name - internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) + internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName, string serverSPN) { //----------------- // Preconditions @@ -2839,6 +2843,7 @@ internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string UserProtocol = TdsEnums.TCP; SetDerivedNames(UserProtocol, UserServerName); ResolvedDatabaseName = userOptions.InitialCatalog; + ServerSPN = serverSPN; } internal void SetDerivedNames(string protocol, string serverName) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 5986bd3e59..abeab96fc5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -359,12 +359,14 @@ internal void Connect( SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, - bool encrypt, - bool trustServerCert, - bool integratedSecurity, - bool withFailover, - SqlAuthenticationMethod authType) + SqlConnectionString connectionOptions, + bool withFailover) { + bool encrypt = connectionOptions.Encrypt; + bool trustServerCert = connectionOptions.TrustServerCertificate; + bool integratedSecurity = connectionOptions.IntegratedSecurity; + SqlAuthenticationMethod authType = connectionOptions.Authentication; + if (_state != TdsParserState.Closed) { Debug.Fail("TdsParser.Connect called on non-closed connection!"); @@ -429,7 +431,7 @@ internal void Connect( // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, false, true, fParallel, - _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated); + _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, serverInfo.ServerSPN ,integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -506,7 +508,7 @@ internal void Connect( _physicalStateObj.SniContext = SniContext.Snix_Connect; _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel, - _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity); + _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, serverInfo.ServerSPN, integratedSecurity); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 7a095d0933..021b292a58 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -787,7 +787,7 @@ private void ResetCancelAndProcessAttention() } internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, - SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false); + SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, string serverSPN, bool isIntegratedSecurity = false); internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index 4c3ad107b4..00ccc8f5de 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -63,9 +63,9 @@ protected override uint SNIPacketGetData(PacketHandle packet, byte[] inBuff, ref } internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, - SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity) + SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, string serverSPN, bool isIntegratedSecurity) { - _sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity, + _sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, serverSPN, flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo); if (_sessionHandle == null) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 89df41b417..afd18c7fcc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Data.Common; using System.Net; +using System.Text; namespace Microsoft.Data.SqlClient { @@ -138,14 +139,24 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) } internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, - SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity) + SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, string serverSPN, bool isIntegratedSecurity) { // We assume that the loadSSPILibrary has been called already. now allocate proper length of buffer spnBuffer = new byte[1][]; if (isIntegratedSecurity) { // now allocate proper length of buffer - spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + if (!string.IsNullOrEmpty(serverSPN)) + { + byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provider SPN length exceeded the buffer size."); + spnBuffer[0] = srvSPN; + SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.",nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); + } + else + { + spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + } } SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs index 4b61d284ff..97e3472a7f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs @@ -103,8 +103,9 @@ internal class ResourceNames internal const string DbConnectionString_DataSource = @"Indicates the name of the data source to connect to."; internal const string DbConnectionString_Encrypt = @"When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed."; internal const string DbConnectionString_Enlist = @"Sessions in a Component Services (or MTS, if you are using Microsoft Windows NT) environment should automatically be enlisted in a global transaction where required."; - internal const string DbConnectionString_InitialCatalog = @"The name of the initial catalog or database in the data source."; internal const string DbConnectionString_FailoverPartner = @"The name or network address of the instance of SQL Server that acts as a failover partner."; + internal const string DbConnectionString_FailoverPartnerSPN = @"Indicate the service principal name (SPN) of the failover partner to connect to."; + internal const string DbConnectionString_InitialCatalog = @"The name of the initial catalog or database in the data source."; internal const string DbConnectionString_IntegratedSecurity = @"Whether the connection is to be a secure connection or not."; internal const string DbConnectionString_LoadBalanceTimeout = @"The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed."; internal const string DbConnectionString_MaxPoolSize = @"The maximum number of connections allowed in the pool."; @@ -116,6 +117,7 @@ internal class ResourceNames internal const string DbConnectionString_PersistSecurityInfo = @"When false, security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state."; internal const string DbConnectionString_Pooling = @"When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool."; internal const string DbConnectionString_Replication = @"Used by SQL Server in Replication."; + internal const string DbConnectionString_ServerSPN = @"Indicate the service principal name (SPN) of the data source to connect to."; internal const string DbConnectionString_TransactionBinding = @"Indicates binding behavior of connection to a System.Transactions Transaction when enlisted."; internal const string DbConnectionString_TrustServerCertificate = @"When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate."; internal const string DbConnectionString_TypeSystemVersion = @"Indicates which server type system the provider will expose through the DataReader."; From 42b5a59d3d622017b7987d4478c488e7a0bafba5 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Mon, 16 May 2022 12:11:03 -0700 Subject: [PATCH 04/11] tests --- .../SqlConnectionStringBuilderTest.cs | 4 ++++ .../FunctionalTests/SqlConnectionTest.cs | 10 ++++++++++ .../ManualTests/DataCommon/DataTestUtility.cs | 19 +++++++++++++++++++ .../IntegratedAuthenticationTest.cs | 19 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index d80875af80..2d85a0a312 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -82,6 +82,10 @@ public partial class SqlConnectionStringBuilderTest [InlineData("User Instance = true")] [InlineData("Workstation ID = myworkstation")] [InlineData("WSID = myworkstation")] + [InlineData("Server SPN = server1")] + [InlineData("ServerSPN = server2")] + [InlineData("Failover Partner SPN = server3")] + [InlineData("FailoverPartnerSPN = server4")] public void ConnectionStringTests(string connectionString) { ExecuteConnectionStringTests(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index 888abca555..75ecd4c44c 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -1031,5 +1031,15 @@ public void ConnectionString_IPAddressPreference_Invalid(string value) Assert.Contains("'ip address preference'", ex.Message, StringComparison.OrdinalIgnoreCase); Assert.Null(ex.ParamName); } + + [Theory] + [InlineData("Server SPN = server1")] + [InlineData("ServerSPN = server2")] + [InlineData("Failover Partner SPN = server3")] + [InlineData("FailoverPartnerSPN = server4")] + public void ConnectionString_ServerSPN_FailoverPartnerSPN(string value) + { + SqlConnection _ = new(value); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 4976d78111..8ac6278ac8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -18,6 +18,8 @@ using Microsoft.Identity.Client; using Microsoft.Data.SqlClient.TestUtilities; using Xunit; +using System.Net.NetworkInformation; +using System.Text; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -868,5 +870,22 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) } } } + + /// + /// Resolves the machine's fully qualified domain name if it is applicable. + /// + /// Returns FQDN if the client was domain joined otherwise the machine name. + public static string GetMachineFQDN() + { + IPGlobalProperties machineInfo = IPGlobalProperties.GetIPGlobalProperties(); + StringBuilder fqdn = new(); + fqdn.Append(machineInfo.HostName); + if (!string.IsNullOrEmpty(machineInfo.DomainName)) + { + fqdn.Append("."); + fqdn.Append(machineInfo.DomainName); + } + return fqdn.ToString(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs index b766502833..7a804dce91 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs @@ -30,6 +30,25 @@ public static void IntegratedAuthenticationTestWithOutConnectionPooling() TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); } + [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] + public static void IntegratedAuthenticationTest_InvalidServerSPN() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.IntegratedSecurity = true; + builder.ServerSPN = "InvalidServerSPN"; + SqlException ex = Assert.Throws(() => TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString)); + Assert.Contains("generate SSPI context.", ex.Message); + } + + [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] + public static void IntegratedAuthenticationTest_ServerSPN() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + builder.IntegratedSecurity = true; + builder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN()}"; + TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); + } + private static void TryOpenConnectionWithIntegratedAuthentication(string connectionString) { using (SqlConnection connection = new SqlConnection(connectionString)) From 86fd4911510eadb8a5d69302986bbb228483597f Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Mon, 16 May 2022 16:13:10 -0700 Subject: [PATCH 05/11] Amend --- .../src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs | 2 +- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index afd18c7fcc..fb6607bfe8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -149,7 +149,7 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni if (!string.IsNullOrEmpty(serverSPN)) { byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); - Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provider SPN length exceeded the buffer size."); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "Length of the provided SPN exceeded the buffer size."); spnBuffer[0] = srvSPN; SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.",nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index e5787ef1fb..ca5da7af9d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -547,7 +547,7 @@ internal void Connect(ServerInfo serverInfo, if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) { byte[] srvSPN = Encoding.Unicode.GetBytes(serverInfo.ServerSPN); - Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provider SPN length exceeded the buffer size."); + Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provided SPN length exceeded the buffer size."); _sniSpnBuffer = srvSPN; SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN); } From e4aea596341b7baf6db550539e30acb8d26e54f0 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Tue, 17 May 2022 09:54:29 -0700 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: David Engel --- doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml | 2 +- .../SqlConnectionStringBuilder.xml | 8 ++++---- .../netcore/src/Resources/StringsHelper.cs | 4 ++-- .../netfx/src/Resources/Strings.Designer.cs | 4 ++-- .../netfx/src/Resources/Strings.resx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index d5faf8a09a..d439a08ae5 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -540,7 +540,7 @@ End Module |Encrypt|'false' in v3.1 and older
'true' in v4.0 and newer|When `true`, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).

When `TrustServerCertificate` is false and `Encrypt` is true, the server name (or IP address) in a SQL Server SSL certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Accepted wildcards used by server certificates for server authentication](https://support.microsoft.com/kb/258858).| |Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| |Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

The server name can be 128 characters or less.

If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| -|Failover Partner SPN

-or-

FailoverPartnerSPN|N/A|The SPN for the faileover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| +|Failover Partner SPN

-or-

FailoverPartnerSPN|N/A|The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

(Only available in v5.0+)| |Initial Catalog

-or-

Database|N/A|The name of the database.

The database name can be 128 characters or less.| |Integrated Security

-or-

Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.

Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.

If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.

is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).| |IP Address Preference

-or-

IPAddressPreference|IPv4First|The IP address family preference when establishing TCP connections. If `Transparent Network IP Resolution` (in .NET Framework) or `Multi Subnet Failover` is set to true, this setting has no effect. Supported values include:

`IPAddressPreference=IPv4First`

`IPAddressPreference=IPv6First`

`IPAddressPreference=UsePlatformDefault`| diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 5c1308bd9b..6a37479d04 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -460,7 +460,7 @@ If you specify a failover partner and the primary server is not configured for d - Gets or sets the service principal name (SPN) of the failover partner is supported using the connection. + Gets or sets the service principal name (SPN) of the failover partner for the connection. The value of the property, or if none has been supplied. @@ -472,7 +472,7 @@ If you specify a failover partner and the primary server is not configured for d This property corresponds to the "FailoverPartnerSPN" and "Failover Partner SPN" keys within the connection string. > [!NOTE] -> The provided value only applies with integrated security authentication mode otherwise will be ignored. +> This property only applies when using Integrated Security mode, otherwise it is ignored. ]]> @@ -840,7 +840,7 @@ Database = AdventureWorks - Gets or sets the service principal name (SPN) of the data source is supported using the connection. + Gets or sets the service principal name (SPN) of the data source. The value of the property, or if none has been supplied. @@ -852,7 +852,7 @@ Database = AdventureWorks This property corresponds to the "ServerSPN" and "Server SPN" keys within the connection string. > [!NOTE] -> The provided value only applies with integrated security authentication mode otherwise will be ignored. +> This property only applies when using Integrated Security mode, otherwise it is ignored. ]]> diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs index 97e3472a7f..22f297e5aa 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs @@ -104,7 +104,7 @@ internal class ResourceNames internal const string DbConnectionString_Encrypt = @"When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed."; internal const string DbConnectionString_Enlist = @"Sessions in a Component Services (or MTS, if you are using Microsoft Windows NT) environment should automatically be enlisted in a global transaction where required."; internal const string DbConnectionString_FailoverPartner = @"The name or network address of the instance of SQL Server that acts as a failover partner."; - internal const string DbConnectionString_FailoverPartnerSPN = @"Indicate the service principal name (SPN) of the failover partner to connect to."; + internal const string DbConnectionString_FailoverPartnerSPN = @"The service principal name (SPN) of the failover partner."; internal const string DbConnectionString_InitialCatalog = @"The name of the initial catalog or database in the data source."; internal const string DbConnectionString_IntegratedSecurity = @"Whether the connection is to be a secure connection or not."; internal const string DbConnectionString_LoadBalanceTimeout = @"The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed."; @@ -117,7 +117,7 @@ internal class ResourceNames internal const string DbConnectionString_PersistSecurityInfo = @"When false, security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state."; internal const string DbConnectionString_Pooling = @"When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool."; internal const string DbConnectionString_Replication = @"Used by SQL Server in Replication."; - internal const string DbConnectionString_ServerSPN = @"Indicate the service principal name (SPN) of the data source to connect to."; + internal const string DbConnectionString_ServerSPN = @"The service principal name (SPN) of the server."; internal const string DbConnectionString_TransactionBinding = @"Indicates binding behavior of connection to a System.Transactions Transaction when enlisted."; internal const string DbConnectionString_TrustServerCertificate = @"When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate."; internal const string DbConnectionString_TypeSystemVersion = @"Indicates which server type system the provider will expose through the DataReader."; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index 57579f6ec0..bcad29f9ca 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -5695,7 +5695,7 @@ internal static string DbConnectionString_FailoverPartner { } /// - /// Looks up a localized string similar to Indicates the service principal name (SPN) of the failover partner to connect to.. + /// Looks up a localized string similar to The service principal name (SPN) of the failover partner.. /// internal static string DbConnectionString_FailoverPartnerSPN { get { @@ -5857,7 +5857,7 @@ internal static string DbConnectionString_Replication { } /// - /// Looks up a localized string similar to Indicates the service principal name (SPN) of the data source to connect to.. + /// Looks up a localized string similar to The service principal name (SPN) of the server.. /// internal static string DbConnectionString_ServerSPN { get { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx index 02f2b61522..1d02199bcc 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx @@ -4618,9 +4618,9 @@ Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command. - Indicates the service principal name (SPN) of the failover partner to connect to. + The service principal name (SPN) of the failover partner. - Indicates the service principal name (SPN) of the data source to connect to. + The service principal name (SPN) of the server. From 7693ec299041980ef5d12def898b2dfacecfb08e Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Tue, 17 May 2022 12:07:42 -0700 Subject: [PATCH 07/11] address comments --- .../src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs | 1 + .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index fb6607bfe8..9075fd06df 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -148,6 +148,7 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni // now allocate proper length of buffer if (!string.IsNullOrEmpty(serverSPN)) { + // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "Length of the provided SPN exceeded the buffer size."); spnBuffer[0] = srvSPN; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index ca5da7af9d..db69f17c79 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -546,6 +546,7 @@ internal void Connect(ServerInfo serverInfo, LoadSSPILibrary(); if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) { + // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. byte[] srvSPN = Encoding.Unicode.GetBytes(serverInfo.ServerSPN); Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provided SPN length exceeded the buffer size."); _sniSpnBuffer = srvSPN; From e9569d311c6027145f34a857deccab8c7e6fc1d0 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Tue, 17 May 2022 12:07:42 -0700 Subject: [PATCH 08/11] Unify encoding --- .../netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index ab6b36d1c9..f39fe00b93 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -68,7 +68,7 @@ internal static void GenSspiClientContext(SspiClientContextStatus sspiClientCont string[] serverSPNs = new string[serverName.Length]; for (int i = 0; i < serverName.Length; i++) { - serverSPNs[i] = Encoding.UTF8.GetString(serverName[i]); + serverSPNs[i] = Encoding.Unicode.GetString(serverName[i]); } SecurityStatusPal statusCode = NegotiateStreamPal.InitializeSecurityContext( credentialsHandle, @@ -202,7 +202,7 @@ private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName)); if (!string.IsNullOrWhiteSpace(serverSPN)) { - return new byte[1][] { Encoding.UTF8.GetBytes(serverSPN) }; + return new byte[1][] { Encoding.Unicode.GetBytes(serverSPN) }; } string hostName = dataSource.ServerName; @@ -251,12 +251,12 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr string serverSpnWithDefaultPort = serverSpn + $":{DefaultSqlServerPort}"; // Set both SPNs with and without Port as Port is optional for default instance SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPNs {0} and {1}", serverSpn, serverSpnWithDefaultPort); - return new byte[][] { Encoding.UTF8.GetBytes(serverSpn), Encoding.UTF8.GetBytes(serverSpnWithDefaultPort) }; + return new byte[][] { Encoding.Unicode.GetBytes(serverSpn), Encoding.Unicode.GetBytes(serverSpnWithDefaultPort) }; } // else Named Pipes do not need to valid port SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPN {0}", serverSpn); - return new byte[][] { Encoding.UTF8.GetBytes(serverSpn) }; + return new byte[][] { Encoding.Unicode.GetBytes(serverSpn) }; } /// From d32c51b4cd0cda030a71e521cdf0f33497dc787e Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Wed, 15 Jun 2022 10:55:29 -0700 Subject: [PATCH 09/11] Update SNI version --- tools/props/Versions.props | 4 ++-- tools/specs/Microsoft.Data.SqlClient.nuspec | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 1aa6b58178..f8ee837453 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -20,7 +20,7 @@ - 5.0.0-preview2.22084.1 + 5.0.0-preview3.22165.1 4.3.1 4.3.0 @@ -38,7 +38,7 @@ 5.0.0 - 5.0.0-preview2.22084.1 + 5.0.0-preview3.22165.1 5.0.0 5.0.0 5.0.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index ea863b6f83..b4412f3e75 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -28,7 +28,7 @@ When using NuGet 3.x this package requires at least version 3.4. sqlclient microsoft.data.sqlclient - + @@ -42,7 +42,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -61,7 +61,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -80,7 +80,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + From cba974cde84b3419b447bede93fadd19e30c9da3 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Wed, 15 Jun 2022 17:46:30 -0700 Subject: [PATCH 10/11] Address comments --- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 3 +-- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 4 ++-- .../src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs | 3 +-- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index eda2d17812..95c098cfc6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1911,8 +1911,7 @@ private void AttemptOneLogin( ignoreSniOpenTimeout, timeout.LegacyTimerExpire, ConnectionOptions, - withFailover, - ConnectionOptions.HostNameInCertificate); + withFailover); _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 1c9d6e37a1..73c065c27d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -360,13 +360,13 @@ internal void Connect( bool ignoreSniOpenTimeout, long timerExpire, SqlConnectionString connectionOptions, - bool withFailover, - string hostNameInCertificate) + bool withFailover) { SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; bool trustServerCert = connectionOptions.TrustServerCertificate; bool integratedSecurity = connectionOptions.IntegratedSecurity; SqlAuthenticationMethod authType = connectionOptions.Authentication; + string hostNameInCertificate = connectionOptions.HostNameInCertificate; if (_state != TdsParserState.Closed) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 42c0ee3e79..12065a2f2c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2304,8 +2304,7 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt _serverCallback, _clientCallback, _originalNetworkAddressInfo != null, - disableTnir, - ConnectionOptions.HostNameInCertificate); + disableTnir); timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index d77c17108c..664c6e56d5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -500,14 +500,14 @@ internal void Connect(ServerInfo serverInfo, ServerCertificateValidationCallback serverCallback, ClientCertificateRetrievalCallback clientCallback, bool useOriginalAddressInfo, - bool disableTnir, - string hostNameInCertificate) + bool disableTnir) { SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; bool trustServerCert = connectionOptions.TrustServerCertificate; bool integratedSecurity = connectionOptions.IntegratedSecurity; SqlAuthenticationMethod authType = connectionOptions.Authentication; string certificate = connectionOptions.Certificate; + string hostNameInCertificate = connectionOptions.HostNameInCertificate; if (_state != TdsParserState.Closed) { From 8b026b3f9684a98373a9d3a796aa58d8a4f414f0 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Thu, 16 Jun 2022 12:37:08 -0700 Subject: [PATCH 11/11] Inactivate a test --- .../IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs index 7a804dce91..6cd19714ae 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs @@ -30,6 +30,7 @@ public static void IntegratedAuthenticationTestWithOutConnectionPooling() TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString); } + [ActiveIssue(21707)] [ConditionalFact(nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] public static void IntegratedAuthenticationTest_InvalidServerSPN() {