SQL 服务器对数据使用加密导致 SECDoClientHandshake 错误

SQL Server Use Encryption for Data results in SECDoClientHandshake error

精简版

给出错误:

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error

背景

我正在尝试加密 SQL 服务器和 OLEDB 客户端(例如 ADO、SQLOLEDB)之间的通信。

我们从 Microsoft 那里注意到(除非您自己配置了一个),SQL 服务器会自动生成一个用于保护登录过程的自签名证书:

Using Encryption Without Validation

SQL Server always encrypts network packets associated with logging in. If no certificate has been provisioned on the server when it starts up, SQL Server generates a self-signed certificate which is used to encrypt login packets.

我们也可以选择一直加密

我们的客户可以要求我们一直使用加密 - 而不仅仅是登录过程。同样,来自微软:

Applications may also request encryption of all network traffic by using connection string keywords or connection properties. The keywords are:

  • "Encrypt": for ODBC and OLE DB when using a provider string with IDbInitialize::Initialize, or
  • "Use Encryption for Data" for ADO and OLE DB when using an initialization string with IDataInitialize.

这个Use Encryption for Data连接字符串关键字意味着我们要使用:

并且我们使用"Trust Server Certificate"来信任自签名证书

SQL服务器自动生成的自签名证书是自签名的。通常,客户端驱动程序会遍历证书的信任链,以查看证书是否有效。使用自动自签名证书,连接将失败。

但是还有一个关键字强制客户端接受服务器证书:Trust Server Certificate:

To enable encryption to be used when a certificate has not been provisioned on the server, applications may use the "TrustServerCertificate" keyword or its associated connection attribute to guarantee that encryption takes place. To guarantee encryption even when a server certificate has not been provisioned, an application may request encryption and "TrustServerCertificate".

When Trust Server Certificate is set to true, the transport layer will use SSL to encrypt the channel and bypass walking the certificate chain to validate trust.

所以我们有两个关键词:

函数代码示例

现在是一些可重现性最低的代码。

为了进一步说明我正在使用 OLEDB,我将给出一个直接使用 OLEDB 的代码示例(尽管对于 ADO 都是如此;它只是 OLDB 的简单包装):

String connectionString = 
      "Provider=SQLOLEDB;Data Source=hydrogen;"+
      "User ID=lgilmore;Password=squeegebeckenheim;";

//DAInitialize helper class parses the connection string 
IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);

//Ask DAInitialize to create the SQLOLEDB class for us and set it up 
IDBInitialize dataSource;
dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);

//Connect to SQL Server
dataSource.Initialize(); //actually opens the database connection

我们的初始代码示例还没有请求加密;所以我们能够连接也就不足为奇了。与 SQL 服务器的连接已顺利建立。 (我们可以使用 SQL Profiler 确认连接)。

代码示例 - 启用加密

我们将代码重构为接受连接字符串的函数:

String connectionString = 
      "Provider=SQLOLEDB;Data Source=hydrogen;"+
      "User ID=lgilmore;Password=squeegebeckenheim;"+
      "Use Encryption for Data=true"; //opt-in to encryption of data

IDbInitialize dataSource = ConnectToDataSource(connectionString);

使用我们的新辅助函数:

IDbInitialize ConnectToDataSource(String connectionString)
{
   //DAInitialize helper class parses the connection string 
   IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);

   //Ask DAInitialize to create the SQLOLEDB class for us and set it up 
   IDBInitialize dataSource;
   dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);

   //Connect to SQL Server
   dataSource.Initialize(); //actually opens the database connection
}

我们预计此代码在连接到数据库时会失败。确实如此:

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error

信任服务器证书也失败

我们现在将连接字符串更新为 Trust Server Certificate=true:

String connectionString = 
      "Provider=SQLOLEDB;Data Source=hydrogen;"+
      "User ID=lgilmore;Password=squeegebeckenheim;"+
      "Use Encryption for Data=true;"+ //opt-into encryption of data
      "Trust Server Certificate=true"; //trust the self-signed server cert

但它仍然失败并出现同样的错误:

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL Security error

SQLOLEDB 无法识别信任服务器证书

如何指定Trust Server Certificate有很多困惑。从直接初始化 OLEDB 提供程序,到使用 IDataInitialize,再到 SQL 服务器 Native Client (SQLNCLI),再到 ADO.net SqlClient,和Java ODBC,有变体:

以上四种变体我都试过了; none 有效。

询问提供商是否识别它

我使用 DataLinks class 规范化我的连接字符串:

String CanonicalizeConnectionString(String cs)
{
   IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_DataLinks);

   IDBInitialize datasource;
   dataInit.GetDataSource(nil, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);

   String result;
   dataInit.GetInitializationString(dataSource, true, out result);
   return result;
}

这给了我原来的和解析的连接字符串:

鉴于 Trust Server Certificate 已被放入无法识别属性的包罗万象中:

无法识别的事实意味着我可能不得不继续寻找正确的神奇语法。

我想通了。

向数据源询问它支持的所有属性:

String EnumerateAllProperties(String connectionString)
{
   IDataInitialize dataInit = (IDataInitialize)CreateComObject(CLSID_MSDAInitialize);

   IDBInitialize dataSource;
   dataInit.GetDataSource(null, CLSCTX_INPROC_SERVER, connectionString, IDBInitialize, ref (IUnknown)dataSource);

   String s = "";
   Int32 nSets;
   PDBPropInfoSet pi;
   POleStr desc;
   (dataSource as IDBProperties).GetPropertyInfo(0, null, ref nSets, out pi, ref desc);
   for (int i=0, i <nSets, i++)
   {
      s = s+CRLF+
            PropSetGuidToStr(pi.guidPropertySet);
      for (int j=0, j < pi[i].cPropertyInfos, j++)
      {
         s = s+CRLF+
             IntToStr(pi[i].rgPropertyInfos[j].dwPropertyID)+TAB+
             PWideChar(pi[i].rgPropertyInfos[j].pwszDescription)+TAB+
             VTypeToStr(pi[i].rgPropertyInfos[j].vtType);
      }

      s = s+CRLF;
   }

   return s;
}

我意识到 SQLOLEDB(SQL 服务器的 Microsoft OLE DB 提供程序)不支持 Trust Server Certificate:

SQLOLEDB - DBPROPSET_DBINIT 属性 设置

PropertyID Description Type SQLOLEDB
7 Integrated Security VT_BSTR Yes
9 Password VT_BSTR Yes
11 Persist Security Info VT_BOOL Yes
12 User ID VT_BSTR Yes
59 Data Source VT_BSTR Yes
60 Window Handle VT_I4 Yes
64 Prompt VT_I2 Yes
66 Connect Timeout VT_I4 Yes
160 Extended Properties VT_BSTR Yes
186 Locale Identifier VT_I4 Yes
233 Initial Catalog VT_BSTR Yes
248 OLE DB Services VT_I4 Yes
284 General Timeout VT_I4 Yes

SQLOLEDB - DBPROPSET_SQLSERVERDBINIT 属性 设置

PropertyID Description Type SQLOLEDB
4 Current Language VT_BSTR Yes
5 Network Address VT_BSTR Yes
6 Network Library VT_BSTR Yes
7 Use Procedure for Prepare VT_I4 Yes
8 Auto Translate VT_BOOL Yes
9 Packet Size VT_I4 Yes
10 Application Name VT_BSTR Yes
11 Workstation ID VT_BSTR Yes
12 Initial File Name VT_BSTR Yes
13 Use Encryption for Data VT_BOOL Yes
14 Replication server name connect option VT_BSTR Yes
15 Tag with column collation when possible VT_BOOL Yes

如果将其与 SQL Server Native Client 11.0

进行比较

SQL 服务器本机客户端 11.0

SQLNCLI11 - DBPROPSET_DBINIT 属性 设置

PropertyID Description Type SQLOLEDB SQLNCLI11
7 Integrated Security VT_BSTR Yes Yes
9 Password VT_BSTR Yes Yes
11 Persist Security Info VT_BOOL Yes Yes
12 User ID VT_BSTR Yes Yes
59 Data Source VT_BSTR Yes Yes
60 Window Handle VT_I4 Yes Yes
64 Prompt VT_I2 Yes Yes
66 Connect Timeout VT_I4 Yes Yes
160 Extended Properties VT_BSTR Yes Yes
186 Locale Identifier VT_I4 Yes Yes
200 Asynchronous Processing VT_I4 No Yes
233 Initial Catalog VT_BSTR Yes Yes
248 OLE DB Services VT_I4 Yes Yes
284 General Timeout VT_I4 Yes Yes

SQLNCLI11 - DBPROPSET_SQLSERVERDBINIT 属性 设置

PropertyID Description Type SQLOLEDB SQLNCLI11
4 Current Language VT_BSTR Yes Yes
5 Network Address VT_BSTR Yes Yes
6 Network Library VT_BSTR Yes Yes
7 Use Procedure for Prepare VT_I4 Yes Yes
8 Auto Translate VT_BOOL Yes Yes
9 Packet Size VT_I4 Yes Yes
10 Application Name VT_BSTR Yes Yes
11 Workstation ID VT_BSTR Yes Yes
12 Initial File Name VT_BSTR Yes Yes
13 Use Encryption for Data VT_BOOL Yes Yes
14 Replication server name connect option VT_BSTR Yes Yes
15 Tag with column collation when possible VT_BOOL Yes Yes
16 MARS Connection VT_BOOL No Yes
18 Failover Partner VT_BSTR No Yes
19 Old Password VT_BSTR No Yes
20 DataTypeCompatibility VT_UI2 No Yes
21 Trust Server Certificate VT_BOOL No Yes
22 Server SPN VT_BSTR No Yes
23 Failover Partner SPN VT_BSTR No Yes
24 Application Intent VT_BSTR No Yes

您可以看到 deprecated native client supports Trust Server Certificate, while the supported OLE DB 客户端没有:

| 21         | Trust Server Certificate                | VT_BOOL |   No      | Yes       |

这很不幸,因为 “无法修复” ODBC 驱动程序中的错误 (, 2, 3)