diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml
index 7e007f9180..304eb234a3 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' in 4.0 and above
'false' in 3.x and below|Recognized values are:
versions 1 - 4: `true`/`yes` and `false`/`no`
versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](/sql/connect/ado-net/connection-string-syntax).
When `Encrypt` is true or strict and `TrustServerCertificate` is false, the server name (or IP address) in a server's 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 failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.
(Only available in v5.0+)|
|Host Name In Certificate
-or-
HostNameInCertificate|N/A|Available starting in version 5.0.
The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation.|
|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`).|
@@ -556,6 +557,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`, TLS 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 a73dfdaa9c..d1c0d6a360 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 for the connection.
+
+ The value of the property, or if none has been supplied.
+
+
+
+ [!NOTE]
+> This property only applies when using Integrated Security mode, otherwise it is 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.
+
+ The value of the property, or if none has been supplied.
+
+
+
+ [!NOTE]
+> This property only applies when using Integrated Security mode, otherwise it is 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 ca6ac9d845..deb4ad0bf9 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -1037,6 +1037,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)]
@@ -1095,6 +1099,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 885ea0c806..d94874f908 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
{
@@ -69,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,
@@ -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
@@ -151,6 +151,7 @@ internal static SNIHandle CreateConnectionHandle(
long timerExpire,
out byte[] instanceName,
ref byte[][] spnBuffer,
+ string serverSPN,
bool flushCache,
bool async,
bool parallel,
@@ -200,7 +201,7 @@ internal static SNIHandle CreateConnectionHandle(
{
try
{
- spnBuffer = GetSqlServerSPNs(details);
+ spnBuffer = GetSqlServerSPNs(details, serverSPN);
}
catch (Exception e)
{
@@ -212,9 +213,13 @@ internal static SNIHandle CreateConnectionHandle(
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.Unicode.GetBytes(serverSPN) };
+ }
string hostName = dataSource.ServerName;
string postfix = null;
@@ -262,12 +267,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) };
}
///
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 082e24fb96..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
@@ -1552,7 +1552,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;
@@ -1696,7 +1696,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)
@@ -1910,12 +1910,8 @@ private void AttemptOneLogin(
this,
ignoreSniOpenTimeout,
timeout.LegacyTimerExpire,
- ConnectionOptions.Encrypt,
- ConnectionOptions.TrustServerCertificate,
- ConnectionOptions.IntegratedSecurity,
- withFailover,
- ConnectionOptions.Authentication,
- ConnectionOptions.HostNameInCertificate);
+ ConnectionOptions,
+ withFailover);
_timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
_timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
@@ -2797,6 +2793,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.
@@ -2817,10 +2814,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
@@ -2839,7 +2842,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
@@ -2860,6 +2863,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 8471782e7c..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
@@ -359,13 +359,15 @@ internal void Connect(
SqlInternalConnectionTds connHandler,
bool ignoreSniOpenTimeout,
long timerExpire,
- SqlConnectionEncryptOption encrypt,
- bool trustServerCert,
- bool integratedSecurity,
- bool withFailover,
- SqlAuthenticationMethod authType,
- string hostNameInCertificate)
+ SqlConnectionString connectionOptions,
+ 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)
{
Debug.Fail("TdsParser.Connect called on non-closed connection!");
@@ -436,7 +438,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, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject,
+ false, true, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, serverInfo.ServerSPN ,
integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, encrypt == SqlConnectionEncryptOption.Strict,
hostNameInCertificate);
@@ -516,7 +518,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, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, integratedSecurity);
+ _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, 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 1fe6a5a9fc..21f4e9b61b 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
@@ -798,6 +798,7 @@ internal abstract void CreatePhysicalSNIHandle(
SqlConnectionIPAddressPreference iPAddressPreference,
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
+ string serverSPN,
bool isIntegratedSecurity = false,
bool tlsFirst = false,
string hostNameInCertificate = "");
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 e57ffdfb94..f4523f87b4 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
@@ -87,13 +87,15 @@ internal override void CreatePhysicalSNIHandle(
SqlConnectionIPAddressPreference iPAddressPreference,
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
+ string serverSPN,
bool isIntegratedSecurity,
bool tlsFirst,
string hostNameInCertificate)
{
- SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer,
+ SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, serverSPN,
flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst,
hostNameInCertificate);
+
if (sessionHandle is not null)
{
_sessionHandle = sessionHandle;
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 7b31d2e5b4..99bbc9bf53 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
{
@@ -149,6 +150,7 @@ internal override void CreatePhysicalSNIHandle(
SqlConnectionIPAddressPreference ipPreference,
string cachedFQDN,
ref SQLDNSInfo pendingDNSInfo,
+ string serverSPN,
bool isIntegratedSecurity,
bool tlsFirst,
string hostNameInCertificate)
@@ -158,7 +160,18 @@ internal override void CreatePhysicalSNIHandle(
if (isIntegratedSecurity)
{
// now allocate proper length of buffer
- spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength];
+ 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;
+ 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 067426eee3..f4bfdbe67d 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/StringsHelper.cs
@@ -104,8 +104,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 = @"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.";
internal const string DbConnectionString_MaxPoolSize = @"The maximum number of connections allowed in the pool.";
@@ -117,6 +118,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 = @"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/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 4a203d13bd..29cd583c94 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -1045,6 +1045,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)]
@@ -1108,6 +1112,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/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index a50188950f..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
@@ -1879,7 +1879,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;
@@ -2049,7 +2049,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)
@@ -2152,7 +2152,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;
@@ -2298,18 +2298,13 @@ 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,
- disableTnir,
- ConnectionOptions.HostNameInCertificate);
+ disableTnir);
timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
@@ -3250,6 +3245,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.
@@ -3270,10 +3266,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
@@ -3292,7 +3294,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
@@ -3313,6 +3315,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 9a2543d574..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
@@ -494,19 +494,21 @@ internal void Connect(ServerInfo serverInfo,
SqlInternalConnectionTds connHandler,
bool ignoreSniOpenTimeout,
long timerExpire,
- SqlConnectionEncryptOption encrypt,
- bool trustServerCert,
- bool integratedSecurity,
+ SqlConnectionString connectionOptions,
bool withFailover,
bool isFirstTransparentAttempt,
- SqlAuthenticationMethod authType,
- string certificate,
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)
{
Debug.Fail("TdsParser.Connect called on non-closed connection!");
@@ -544,8 +546,19 @@ 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))
+ {
+ // 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;
+ 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 98d1fc1d51..ee6fafa0f0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
@@ -991,6 +991,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
@@ -1045,6 +1047,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";
@@ -1141,5 +1145,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 141732ba3f..dd68546330 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -60,6 +60,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;
@@ -115,6 +117,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
@@ -177,6 +181,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;
@@ -216,9 +223,9 @@ internal static class TRANSACTIONBINDING
}
#if NETFRAMEWORK
- internal const int SynonymCount = 30;
+ internal const int SynonymCount = 32;
#else
- internal const int SynonymCount = 27;
+ internal const int SynonymCount = 29;
internal const int DeprecatedSynonymCount = 2;
#endif // NETFRAMEWORK
@@ -262,6 +269,8 @@ internal static class TRANSACTIONBINDING
private readonly string _password;
private readonly string _userID;
private readonly string _hostNameInCertificate;
+ private readonly string _serverSPN;
+ private readonly string _failoverPartnerSPN;
private readonly string _workstationId;
@@ -328,6 +337,8 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
_attestationProtocol = ConvertValueToAttestationProtocol();
_ipAddressPreference = ConvertValueToIPAddressPreference();
_hostNameInCertificate = ConvertValueToString(KEY.HostNameInCertificate, DEFAULT.HostNameInCertificate);
+ _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);
@@ -681,6 +692,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;
@@ -739,7 +752,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;
@@ -851,6 +865,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 },
@@ -880,6 +896,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 da7e732f37..28a8fbc6fe 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
@@ -66,6 +66,8 @@ private enum Keywords
AttestationProtocol,
CommandTimeout,
IPAddressPreference,
+ ServerSPN,
+ FailoverPartnerSPN,
#if NETFRAMEWORK
ConnectionReset,
NetworkLibrary,
@@ -124,6 +126,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;
@@ -179,11 +183,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
@@ -232,6 +238,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 },
@@ -271,7 +279,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;
@@ -384,7 +394,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:
@@ -532,6 +545,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;
@@ -1032,6 +1051,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:
@@ -1187,6 +1212,21 @@ public string DataSource
}
}
+ ///
+ [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)]
@@ -1340,6 +1380,21 @@ public string FailoverPartner
}
}
+ ///
+ [DisplayName(DbConnectionStringKeywords.FailoverPartnerSPN)]
+ [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)]
+ [ResDescription(StringsHelper.ResourceNames.DbConnectionString_FailoverPartnerSPN)]
+ [RefreshProperties(RefreshProperties.All)]
+ public string FailoverPartnerSPN
+ {
+ get => _failoverPartnerSPN;
+ set
+ {
+ SetValue(DbConnectionStringKeywords.FailoverPartnerSPN, value);
+ _failoverPartnerSPN = value;
+ }
+ }
+
///
[DisplayName(DbConnectionStringKeywords.InitialCatalog)]
[ResCategory(StringsHelper.ResourceNames.DataCategory_Source)]
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
index 1962684b43..65e0a5d32f 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
@@ -87,6 +87,10 @@ public partial class SqlConnectionStringBuilderTest
[InlineData("WSID = myworkstation")]
[InlineData("Host Name In Certificate = tds.test.com")]
[InlineData("HostNameInCertificate = tds.test.com")]
+ [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 ca0ff8ae22..1ce20f9735 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
@@ -1040,6 +1040,16 @@ public void ConnectionString_IPAddressPreference_Invalid(string value)
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);
+ }
+
[Fact]
public void ConnectionRetryForNonAzureEndpoints()
{
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index 9e5b10cdd7..5fed0099a7 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.Data.SqlClient.TestUtilities;
using Microsoft.Identity.Client;
using Xunit;
+using System.Net.NetworkInformation;
+using System.Text;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -906,5 +908,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..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,26 @@ public static void IntegratedAuthenticationTestWithOutConnectionPooling()
TryOpenConnectionWithIntegratedAuthentication(builder.ConnectionString);
}
+ [ActiveIssue(21707)]
+ [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))