我正在使用 SQL Server 2022 的标准版。它有一个设置强制严格加密的附加选项。我在服务器端设置了相同的内容。
在客户端计算机上,使用 SSMS,我能够连接并访问数据库用户表。但我无法从 C++ 应用程序执行相同操作。以下是我正在尝试的示例代码:
auto pDatabase = new CDatabase();
CString connString = L"DRIVER={ODBC Driver 18 for SQL Server};Network=DBMSSOCN;DATABASE=TESTTDE;Encrypt=strict;TrustServerCertificate=no;HostNameInCertificate=10.100.200.300;Mars_Connection=yes;SERVER=10.100.200.300\\TDE,2144;UID=Supervisor;PWD=password;";
auto reply = pDatabase->OpenEx(connString, CDatabase::noOdbcDialog);
CStringArray userNameList;
CString strUserName;
CStringW strUserNameW;
CString SQLString = L"select * from TESTTDE.dbo.UserTable;";
CRecordset userRecords(pDatabase);
userRecords.Open(CRecordset::forwardOnly, SQLString, CRecordset::readOnly);
while (!userRecords.IsEOF())
{
userRecords.GetFieldValue(L"Name", strUserNameW);
strUserName = CW2A(strUserNameW.Trim());
userNameList.Add(strUserName.Trim());
userRecords.MoveNext();
}
userRecords.Close();
userRecords.Open
生成异常,我无法访问数据库。有人能解释一下吗?我可以尝试什么来解决这个问题?
我使用的是自签名证书。如果我使用强制加密而不在服务器上启用强制严格加密,它就可以正常工作。
客户端异常:
传入的表格数据流 (TDS) 协议流不正确。流意外结束。状态:28000,本机:4002,来源:[Microsoft][ODBC Driver 18 for SQL Server][SQL Server]
服务器日志:
SQL Server 或端点配置为仅接受严格(TDS 8.0 及以上)连接。连接已关闭。
有什么可以阻止使用严格加密的自签名证书吗?对于服务器端的相同设置,我能够从 SSMS 在客户端运行查询并检索表数据。只有从示例应用程序我才能连接但不能运行查询。
一些额外的观察:使用示例应用程序中的低级 SQL API,即使启用了强制严格加密,我也能够从服务器获取数据。相同的代码添加如下:
SQLHANDLE env;
SQLHANDLE dbc;
SQLHANDLE stmt;
SQLRETURN ret;
SQLWCHAR* connStr = (SQLWCHAR*)L"Driver={ODBC Driver 18 for SQL Server};Server=10.100.200.300\\TDE;Database=TESTTDE;Uid=Supervisor;Pwd=password;Encrypt=strict;";
// Allocate environment handle
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
if (ret == SQL_ERROR) {
std::wcerr << L"Error allocating environment handle." << std::endl;
return;
}
// Set the ODBC version environment attribute
ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
if (ret == SQL_ERROR) {
checkDiagnostic(env, SQL_HANDLE_ENV);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return;
}
// Allocate connection handle
ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
if (ret == SQL_ERROR) {
checkDiagnostic(env, SQL_HANDLE_ENV);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return;
}
// Connect to the data source
ret = SQLDriverConnectW(dbc, NULL, connStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
if (ret == SQL_ERROR) {
checkDiagnostic(dbc, SQL_HANDLE_DBC);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return;
}
// Allocate statement handle
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
if (ret == SQL_ERROR) {
checkDiagnostic(dbc, SQL_HANDLE_DBC);
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
return;
}
// Execute a query
SQLWCHAR* query = (SQLWCHAR*)L"SELECT * FROM UserTable";
ret = SQLExecDirectW(stmt, query, SQL_NTS);
if (ret == SQL_ERROR) {
checkDiagnostic(stmt, SQL_HANDLE_STMT);
}
else
{
SQLWCHAR name[256];
while (SQLFetch(stmt) == SQL_SUCCESS) {
ret = SQLGetData(stmt, 1, SQL_C_WCHAR, name, sizeof(name), NULL);
if (SQL_SUCCEEDED(ret)) {
AfxMessageBox((LPCTSTR)name);
}
}
}
// Clean up
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
SQLDisconnect(dbc);
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
我怀疑 MFC 类 API CRecordset::Open() 在支持 TDS 8.0 方面存在一些问题。
您似乎对 TLS 和证书如何与 SQL Server 协同工作有点困惑。
Encrypt=no
意味着不强制加密,但如果服务器强制加密,则接受加密。Encrypt=yes
或者mandatory
意味着即使服务器不强制加密,也要强制加密。TrustServerCertificate=yes
表示忽略证书中的 SAN 名称。这显然是不安全的,而且并不比根本不加密好多少。如果没有此选项,您连接的名称必须与证书中的 SAN 匹配,在您的情况下,出于某种原因,SAN 是 IP 地址。
Encrypt=strict
表示强制执行 SAN 名称匹配(或提供HostNameInCertificate
不同的名称匹配)。它还强制执行 TDS 8.0 并且仅适用于 SQL2022+。TrustServerCertificate=yes
使用此选项会忽略。您可以提供ServerCertificate=pathOfCertificate
实际的证书来匹配,但这可能非常麻烦。Force encryption
表示所有连接都必须加密。客户端是否信任此服务器的证书取决于他们自己的连接字符串,如上所述。Force Strict Encryption
意味着所有连接都必须使用strict
如上所述的加密,因此这些客户端不能使用TrustServerCertificate=yes
。因此,就您而言,您有一个自签名证书。这意味着您必须使用
TrustServerCertificate=yes
,并且您可以使用Encrypt=yes
或no
,但不能strict
。服务器端可以使用Force encryption
但不能Force Strict Encryption
。或者,您可以将自签名证书导出到客户端(您只能使用自己的自签名证书执行此操作,而不能使用自动证书)。然后,您仍然使用
strict
加密,并使用ServerCertificate=pathOfCertificate
在客户端上提供其路径。当许多客户端需要更新时,这可能变得非常不切实际。最好使用由公共 CA 或受信任的私人 CA 正确签名的证书。然后,您可以强制
strict
加密。使用 DNS 名称而不是 IP 地址进行连接,因为没有哪个称职的 CA 会为您提供基于 IP 的证书。有关更多信息,请参阅此处、此处和此处的文档。
此外,您正在使用实例名称和端口号进行连接。这是非标准的:实例名称将被忽略,并且只会使用端口号。只能使用其中一个。
我认为问题出在您的连接字符串上,其中显示。据我所知,
Encrypt=strict
该属性的唯一有效值Encrypt
是no
、yes
、false
和:true
yes
的直接链接。(不要与 混淆,这是 SQL Server 的另一个功能。)Always Encrypted
true
或。yes
Encrypt=yes
我的猜测是,您会尝试
Encrypt=yes
并且服务器会根据您配置服务器的方式确定要应用哪种类型的加密(例如严格)。请注意,以上内容仅适用于版本 17 及更早版本的 ODBC 驱动程序。
strict
在版本 18+ 上有效。最后,我找到了一个可能的解决方案。正如查询中提到的,CRecordset::Open() 导致了这个问题。我刚刚查看了支持的参数,发现第三个参数可能有一个值executeDirect。相同的注释提到“直接执行 SQL 而不是准备执行”。我尝试使用相同的方法,并能够使用 CRecordset 获取数据。userRecords.Open(CRecordset::forwardOnly, SQLString, CRecordset::executeDirect);。如果我能够获得一些 Microsoft 文档来确认,我将关闭此查询并将其标记为答案。