如何将C#控制台应用部署到启用FIPS的RHEL主机并解决SQL连接问题
我有一个名为SqlTest的C#控制台应用,仅用于连接SQL Server并读取一列数据,已简化代码。该应用在本地运行正常,但部署到启用FIPS的RHEL(8.9 Ootpa)主机后,在尝试连接SQL Server时失败。应用能正常启动并输出“Query data example:”,说明已开始运行。
我对将C#应用部署到Linux不太熟悉,希望只是遗漏了几个步骤,或者需要针对启用FIPS的Linux主机添加配置,以让应用能连接SQL Server。
部署步骤
在Visual Studio Community 2022(版本17.7.4)中打开终端:
View -> Terminal
在终端中运行命令:
dotnet publish -c release -r rhel.8-x64 --self-contained
命令执行完成后,会在release文件夹中生成publish文件夹,将该文件夹复制到RHEL主机。
在RHEL主机中,进入publish文件夹,运行SqlTest应用:
./SqlTest
Program.cs代码
using Microsoft.Data.SqlClient; namespace sqltest { class Program { public partial class MySqlData { public decimal my_id { get; set; } } static void Main(string[] args) { try { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.UserID = "my_user"; builder.Password = "Super_Secret_Pass123"; builder.DataSource = "myhost.com\\myhost_dev"; builder.InitialCatalog = "DEV"; builder.TrustServerCertificate = true; builder.Encrypt = true; builder.MultiSubnetFailover = true; builder.IntegratedSecurity = false; String sqlQuery = "SELECT my_id FROM my_table"; using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) { Console.WriteLine("\nQuery data example:"); Console.WriteLine("=========================================\n"); connection.Open(); using (SqlCommand command = new SqlCommand(sqlQuery, connection)) { using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) { MySqlData my_data = new MySqlData(); my_data.my_id = reader.GetDecimal(0); Console.WriteLine("{0}", my_data.my_id); } } } } } catch (SqlException e) { Console.WriteLine(e.ToString()); } } } }
运行报错信息
Microsoft.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 35 - An internal exception was caught) ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL. ---> Interop+Crypto+OpenSslCryptographicException: error:141A3066:SSL routines:tls_process_ske_dhe:bad dh value --- End of inner exception stack trace --- at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan
1 input, Byte[]& sendBuf, Int32& sendCount) at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions) --- End of inner exception stack trace --- at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm) at System.Net.Security.SslStream.AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions) at System.Net.Security.SslStream.AuthenticateAsClient(String targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, Boolean checkCertificateRevocation) at Microsoft.Data.SqlClient.SNI.SNITCPHandle.EnableSsl(UInt32 options)
at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) at Microsoft.Data.SqlClient.TdsParser.EnableSsl(UInt32 info, SqlConnectionEncryptOption encrypt, Boolean integratedSecurity, String serverCertificateFilename) at Microsoft.Data.SqlClient.TdsParser.ConsumePreLoginHandshake(SqlConnectionEncryptOption encrypt, Boolean trustServerCert, Boolean integratedSecurity, Boolean& marsCapable, Boolean& fedAuthRequired, Boolean tlsFirst, String serverCert) at Microsoft.Data.SqlClient.TdsParser.Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, TimeoutTimer timeout, SqlConnectionString connectionOptions, Boolean withFailover) at Microsoft.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) at Microsoft.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) at Microsoft.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, DbConnectionPool pool, Func3 accessTokenCallback) at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) at Microsoft.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) at Microsoft.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at Microsoft.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides) at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) at sqltest.Program.Main(String[] args) in C:\Users\my_user\source\repos\SqlTest\SqlTest\Program.cs:line 35
ClientConnectionId:568dade4-7a67-4126-bddf-95897f9f61a1 Error Number:-2146893019,State:0,Class:20
从报错信息里的error:141A3066:SSL routines:tls_process_ske_dhe:bad dh value可以看出,问题出在FIPS模式下,SQL Server使用的DH密钥不符合FIPS标准要求。以下是几种可行的解决方式:
1. 强制使用TLS 1.2+并禁用DH密钥交换
在代码中修改连接字符串构建器,指定使用TLS 1.2或更高版本:
builder.SslProtocol = System.Security.Authentication.SslProtocols.Tls12;
或者直接在连接字符串中追加参数:;SslProtocol=Tls12
同时在RHEL主机上配置OpenSSL优先使用符合FIPS的加密套件:
- 编辑
/etc/crypto-policies/back-ends/opensslcnf.config,排除DH相关套件,仅保留FIPS批准的加密算法 - 执行
update-crypto-policies --set FIPS使配置生效
2. 配置SQL Server使用符合FIPS的加密算法
如果有权限修改SQL Server配置,调整其TLS加密套件为FIPS 140-2/140-3批准的类型:
- 禁用DH密钥交换,改用ECDHE等符合标准的密钥交换算法
- 通过SQL Server配置管理器(Windows)或配置文件(Linux版SQL Server)完成设置
3. 调整.NET应用的FIPS兼容设置
在应用的runtimeconfig.json中添加FIPS兼容配置:
{ "runtimeOptions": { "configProperties": { "System.Net.Security.UseSystemDefaultTlsSettings": true, "System.Security.Cryptography.UseFipsAlgorithms": true } } }
或者在RHEL主机上设置环境变量后再运行应用:
export DOTNET_SYSTEM_CRYPTOGRAPHY_USEFIPSALGORITHMS=1 ./SqlTest
4. 升级Microsoft.Data.SqlClient版本
旧版本的Microsoft.Data.SqlClient在FIPS环境下可能存在兼容性问题,升级到最新稳定版(如5.x及以上):
- 修改项目的
csproj文件中的包版本:
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" />
- 重新执行发布命令并部署到RHEL主机
内容的提问来源于stack exchange,提问作者RyanWorks

