SQL Server 对数据使用加密会导致 SECDoClientHandshake 错误

Ian*_*oyd 4 sql-server encryption sql-server-2008-r2

简洁版本

  • 连接字符串Provider=SQLOLEDB;Data Source=hydrogen;
    User ID=lgilmore;Password=squeegebeckenheim;
    Use Encryption for Data=true;
    Trust Server Certificate=true;

给出错误:

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL 安全错误

背景

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

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

使用加密而不验证

SQL Server 始终对与登录相关的网络数据包进行加密。如果服务器启动时未配置证书,SQL Server 会生成一个自签名证书,用于加密登录数据包。

我们还可以选择一直加密

我们的客户可以要求我们始终使用加密——而不仅仅是在登录过程中。再次,来自微软:

应用程序还可以使用连接字符串关键字或连接属性请求对所有网络流量进行加密。关键词是:

  • "Encrypt":对于 ODBC 和 OLE DB,当将提供程序字符串与IDbInitialize::Initialize一起使用时,或者
  • "Use Encryption for Data"对于 ADO 和 OLE DB,将初始化字符串与IDataInitialize一起使用时。

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

  • 不仅仅在登录过程中使用加密
  • 但也对数据使用加密

我们使用“信任服务器证书”来信任自签名证书

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

但是还有另一个关键字可以强制客户端接受服务器证书:信任服务器证书

为了在服务器上未配置证书时启用加密,应用程序可以使用"TrustServerCertificate"关键字或其关联的连接属性来保证进行加密。为了即使在未配置服务器证书的情况下也能保证加密,应用程序可能会请求加密和"TrustServerCertificate".

Trust Server Certificate设置为 true 时,传输层将使用 SSL 加密通道并绕过证书链来验证信任。

所以我们有两个关键词:

  • Use Encryption for Data=true选择加密
  • Trust Server Certificate=true信任自签名证书

功能代码示例

现在来一些最低限度可重现的代码。

  • 我正在使用 OLEDB(例如 ADO)。
  • 没有使用 ADO.NET。
  • 没有使用 ODBC。
  • 我没有使用SqlClient即我没有使用ADO.NET)
  • 我正在使用 SQLOLEDB(即 Microsoft OLE DB Provider for SQL Server)。
  • 没有使用 SQL Server Native Client(例如 SQLNCLI、SQLNCLI11)

为了进一步说明我正在使用 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
Run Code Online (Sandbox Code Playgroud)

我们的初始代码示例尚未请求加密;所以我们能够建立联系也就不足为奇了。与 SQL Server 的连接已顺利建立。(我们可以使用 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);
Run Code Online (Sandbox Code Playgroud)

使用我们的新辅助函数:

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
}
Run Code Online (Sandbox Code Playgroud)

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

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL 安全错误

信任服务器证书也失败

我们现在将连接字符串更新为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
Run Code Online (Sandbox Code Playgroud)

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

[DBNETLIB][ConnectionOpen (SECDoClientHandshake()).]SSL 安全错误

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

如何指定存在很多混乱Trust Server Certificate。在直接初始化 OLEDB 提供程序、使用IDataInitializeSQL Server Native Client (SQLNCLI)、ADO.net SqlClient和 Java ODBC 之间,存在一些变体:

  • Trust Server Certificate=true
  • Trust Server Certificate=yes
  • TrustServerCertificate=true
  • TrustServerCertificate=yes

我尝试了上述所有四种变体;但没有一个起作用。

询问提供商是否认可

我使用了该类DataLinks来规范化我的连接字符串:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 原来的: Provider=SQLOLEDB;Data Source=hydrogen;User ID=lgilmore;Password=squeegebeckenheim;Use Encryption for Data=true;Trust Server Certificate=true;
  • 规范化: Provider=SQLOLEDB.1;Password=squeegebeckenheim;User ID=lgilmore;Data Source=hydrogen;Extended Properties="Trust Server Certificate=true";Use Encryption for Data=True

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

  • Extended Properties="Trust Server Certificate=true"

它未被识别的事实意味着我可能必须继续寻找正确的神奇语法。

Ian*_*oyd 5

我想到了。

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

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;
}
Run Code Online (Sandbox Code Playgroud)

我意识到 SQLOLEDB(Microsoft OLE DB Provider for SQL Server)不支持Trust Server Certificate

SQLOLEDB -DBPROPSET_DBINIT属性集

属性ID 描述 类型 SQLOLEDB
7 综合安全 VT_BSTR 是的
9 密码 VT_BSTR 是的
11 保留安全信息 VT_布尔 是的
12 用户身份 VT_BSTR 是的
59 数据源 VT_BSTR 是的
60 窗把手 VT_I4 是的
64 迅速的 VT_I2 是的
66 连接超时 VT_I4 是的
160 扩展属性 VT_BSTR 是的
186 区域设置标识符 VT_I4 是的
233 初始目录 VT_BSTR 是的
248 OLE DB 服务 VT_I4 是的
第284章 一般超时 VT_I4 是的

SQLOLEDB -DBPROPSET_SQLSERVERDBINIT属性集

属性ID 描述 类型 SQLOLEDB
4 当前语言 VT_BSTR 是的
5 网络地址 VT_BSTR 是的
6 网络图书馆 VT_BSTR 是的
7 使用准备程序 VT_I4 是的
8 自动翻译 VT_布尔 是的
9 数据包大小 VT_I4 是的
10 应用名称 VT_BSTR 是的
11 工作站ID VT_BSTR 是的
12 初始文件名 VT_BSTR 是的
13 对数据使用加密 VT_布尔 是的
14 复制服务器名称连接选项 VT_BSTR 是的
15 尽可能使用列排序规则标记 VT_布尔 是的

如果将其与SQL Server Native Client 11.0进行比较

SQL Server 本机客户端 11.0

SQLNCLI11 -DBPROPSET_DBINIT属性集

属性ID 描述 类型 SQLOLEDB SQLNCLI11
7 综合安全 VT_BSTR 是的 是的
9 密码 VT_BSTR 是的 是的
11 保留安全信息 VT_布尔 是的 是的
12 用户身份 VT_BSTR 是的 是的
59 数据源 VT_BSTR 是的 是的
60 窗把手 VT_I4 是的 是的
64 迅速的 VT_I2 是的 是的
66 连接超时 VT_I4 是的 是的
160 扩展属性 VT_BSTR 是的 是的
186 区域设置标识符 VT_I4 是的 是的
200 异步处理 VT_I4 是的
233 初始目录 VT_BSTR 是的 是的
248 OLE DB 服务 VT_I4 是的 是的
第284章 一般超时 VT_I4 是的 是的

SQLNCLI11 -DBPROPSET_SQLSERVERDBINIT属性集

属性ID 描述 类型 SQLOLEDB SQLNCLI11
4 当前语言 VT_BSTR 是的 是的
5 网络地址 VT_BSTR 是的 是的
6 网络图书馆 VT_BSTR 是的 是的
7 使用准备程序 VT_I4 是的 是的
8 自动翻译 VT_布尔 是的 是的
9 数据包大小 VT_I4 是的 是的
10 应用名称 VT_BSTR 是的 是的
11 工作站ID VT_BSTR 是的 是的
12 初始文件名 VT_BSTR 是的 是的
13 对数据使用加密 VT_布尔 是的 是的
14 复制服务器名称连接选项 VT_BSTR 是的 是的
15 尽可能使用列排序规则标记 VT_布尔 是的 是的
16 火星连接 VT_布尔 是的
18 故障转移合作伙伴 VT_BSTR 是的
19 旧密码 VT_BSTR 是的
20 数据类型兼容性 VT_UI2 是的
21 信任服务器证书 VT_布尔 是的
22 服务器SPN VT_BSTR 是的
23 故障转移合作伙伴 SPN VT_BSTR 是的
24 申请意向 VT_BSTR 是的

您可以看到已弃用的本机客户端支持Trust Server Certificate,而受支持的OLE DB 客户端则不支持:

| 21         | Trust Server Certificate                | VT_BOOL |   No      | Yes       |
Run Code Online (Sandbox Code Playgroud)

考虑到ODBC 驱动程序中的“无法修复”错误(1、2、3 不幸的