在C# .NET Core中,我使用SignalR创建了客户端和服务器端,想使用https通信,因此会使用到SSL证书,我遇到的问题是,如果使用pfx格式的证书,那么客户端和服务器端可以正常连接,如果使用der格式证书+Pem加密格式的私钥,客户端就无法连接到服务器,我的代码如下:
Server端Program.cs中
using SignalRService.Common;
using SignalRService.Hubs;
using SignalRService.Providers;
using SignalRService.Services;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWindowsService(options =>
{
options.ServiceName = "SignalR Server Service";
});
builder.WebHost.UseKestrel(options =>
{
options.ListenAnyIP(12316, config =>
{
var certificateMgr = new CertificateMgr();
var certificate = certificateMgr.GetCertificate();
if (certificate != null)
{
config.UseHttps(certificate);
}
});
});
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "AllowAll", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
builder.Services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
});
builder.Services
.AddSingleton<IUserIdProvider, UserIdProvider>()
.AddHostedService<ServerService>();
var app = builder.Build();
app.UseRouting();
app.UseCors("AllowAll");
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ProxyHub>("proxyHub").RequireCors("AllowAll");
});
await app.RunAsync();
在CertificateMgr.cs文件中解析证书和私钥
```c#
namespace SignalRService.Common
{
public class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string pwd) => password = pwd;
public char[] GetPassword() => password.ToCharArray();
}
public class CertificateMgr
{
public string _certPath = "C:\\Working Folder\\certificateFile.der";
public string _keyPath = "C:\\Working Folder\\privateKey.pem";
public X509Certificate2? GetCertificate()
{
string fileExtension = Path.GetExtension(_certPath);
if (string.Equals(fileExtension, ".pfx", StringComparison.OrdinalIgnoreCase))
{
return new X509Certificate2(_certPath, "123123");
}
else
{
StreamReader sr = new StreamReader(_keyPath);
string privateKeyPass = "123123";
var pf = new PasswordFinder(privateKeyPass);
PemReader pr = new PemReader(sr, pf);
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
X509Certificate2 certificate = new X509Certificate2(_certPath);
certificate = certificate.CopyWithPrivateKey(rsa);
return certificate;
}
}
}
}
}
在客户端的Client.cs文件中建立连接
using SignalRClient.Common;
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Data.SqlTypes;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace SignalRClient.Connections
{
public interface IProxyClient
{
Task Initialize();
Task HubConnection_StartAsync();
Task HubConnection_StopAsync();
}
public class SignalRClient : IProxyClient
{
private HubConnection? _hubConnection;
public Task Initialize()
{
Console.WriteLine($"Initialize");
return Task.CompletedTask;
}
public async Task HubConnection_StartAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl("https:10.224.10.10:12316/proxyHub", (options) =>
{
options.HttpMessageHandlerFactory = (handler) =>
{
if (handler is HttpClientHandler clientHandler)
{
clientHandler.ServerCertificateCustomValidationCallback += CheckCertificateCallback;
}
return handler;
};
})
.WithAutomaticReconnect(new RandomRetryPolicy())
.Build();
_hubConnection.Closed += HubConnection_Closed;
_hubConnection.Reconnecting += HubConnection_Reconnecting;
_hubConnection.Reconnected += HubConnection_Reconnected;
_hubConnection.On<string>("MessageToClient", HubConnection_Received);
try
{
await _hubConnection.StartAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private bool CheckCertificateCallback(HttpRequestMessage message, X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors errors)
{
return true;
}
public async Task HubConnection_StopAsync()
{
if (_hubConnection != null)
{
await _hubConnection.StopAsync();
_hubConnection = null;
}
}
public async Task SendMessageToServer(string message)
{
if (_hubConnection != null
&& _hubConnection.State == HubConnectionState.Connected)
{
await _hubConnection.InvokeAsync("MessageFromClient", message);
}
}
private Task HubConnection_Closed(Exception? exception)
{
Console.WriteLine($"Closed {exception?.Message}");
return Task.CompletedTask;
}
private Task HubConnection_Reconnecting(Exception? exception)
{
Console.WriteLine($"Reconnecting {exception?.Message}");
return Task.CompletedTask;
}
private Task HubConnection_Reconnected(string? arg)
{
Console.WriteLine($"{arg}");
return Task.CompletedTask;
}
private void HubConnection_Received(string message)
{
DateTime dateTime = DateTime.UtcNow;
Console.WriteLine($"Received {message}: " + dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
}
}
}
在运行的时候,如果使用pfx格式的证书,那么客户端可以正常连接到服务器端,如果把pfx格式证书转换为der格式的证书和pem格式的加密私钥,那么客户端就无法建立连接,在运行到StartAsync函数后报下面错误,想请教原因
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
at System.Net.Security.SslStream.<FillHandshakeBufferAsync>g__InternalFillHandshakeBufferAsync|189_0[TIOAdapter](TIOAdapter adap, ValueTask`1 task, Int32 minSize)
at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](TIOAdapter adapter)
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.Internal.AccessTokenHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.Internal.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
我的私钥格式如下
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,5BF69C9B6974DB708CE3C6A133642F05
keh2HGnXDyAO4rLB67aT5meMVMlOjf3/me5JwfixMk3FV9WI5GOb3w+weX+J7WRL
GDvFvHGWezYHNQi1OfiKZQbYQi5POuir2LplhLCuDHPae/8HVhoUXcyH6n4VIE1N
R/DtqUNHw3sAP5vbdllHDSlsDm5kLrdOh2KsFtKthyDHP6iMTj6OZZ3EuskX3gxy
fUHNlA9boU0nDaQcDJqV0FPx8p29/vKkHrcc4LFh0JpEUh/gEd5p9dHeAq1HoyO4
D+BpBBNnugCm5ZcrEBUNA2qqqWgXNL3XVfiLJ05s+VUsfUilNssoPyi5dW7fA5fd
uX7NE7rG5jnJ8EX7jvLwcfeG4aQE3hRQwLsXSMDGek0Hnlj3qKYU3LpAXH+XguvV
TRJNK+SN3jrdR6ZdICHQAdT9q6uo/wlffjDLn+h7vQWtYYzdlomEY/Pl7ErV8Rq6
P7vr91hLK9/VluenSvcrQhI+a2ZzrFlkxgWfPV5QzC1IaZSW50PoXwp2HcYtvt5+
mdvZx+0nwrF5TxlcKk4zTyxTpe6pweWFvq+8EGfIz+pIPm3ayO9VKvTvQ1xR627j
cvOmycCUQYrBpIld9uurn3o/Lp+Wjcdoqk7kih1cwq5Xbcnv/P+yeiSXmNYUSfaY
t+BlsDzcwWDf3GWL+tjxdukiR60PmAji9oSBMNmzrvluV/LIuSV/7abtAGCYIawz
xJkSrmLbfLYH2i6Xij8AlkQm7wT3MbEHVlDOeXi2DXAokdrFBc1/0HeawciowAYz
tVp8EdQWUtvkAArQbS82rUU2ST2uzL/OfDPLCXBrbdVL1j8+jCMBwwUSxmqPQR3M
8y3lKR+NBlubu/Y54k3aLSk7Wvyp6y7G5qHpTT4x2uHpibJD3G7qU/wUVKd7NT4R
X8aqWC/ceCdlSQoBR2hUDIbmXxrDvkQyKLR8VcYCnhjE6bAaCf3tmlJ/fGwI8nqN
gwF822IKaXDGtyhve2dcB60GoSXZLDKynAU4QpYO59etwgskucEEf2Edgda/SoGn
vWwGMNHFoZp290W9MgV+Q+bAfKuurT0LurOK06UfCPK3B2rOGsuaXAmfX37avNZL
zs7CfL+aaIRaEdSBDUDxLulIrYGIIBdSM/sh7RUR+lx4R11Y0qeMEf6nORygj1jG
bpMUdGstHYKF2ChM2KrTAng/ly1xqaKvvWFRUXSuFvZGjYDYOVtszPBNJ+XedZ5h
JrOhYb9FhoWX+LnGjUtQmZCUZjzhXt8IXydnNvRb5pf2DPWtEmbm//X51vRkyTkh
MhxA6VgFXav9Q2C1qSDWivFRznUOiN446/qH44EpAPKHJWMlu9EJwVk78/Rp0Ohj
8YBzYmOTbBthkGk2SOAmayHJX6sxjObEh/AIuHiXOVLRJTqdkt7MtlG6mtzYwquI
LJsdTbhqYTzyMw1KjhUNpvGXaKfLgH5SPKsNtVJs75Cu0T1Je9IHPV8BL2kJQssq
z2hlIN//D8bMHu78x36P9MbNBIbeCzVa9JerFESIuauyhxk42/sQqWnzSHkV9zsN
49TkuyseyCn46kM+B+f56ak7CznM+DXlLi2aJUr04WWOTUWXWuInj+LuFJ+vD930
-----END RSA PRIVATE KEY-----
我使用下面方法,客户端也不能实现SignalR连接
var privateKey = File.ReadAllText(_keyPath);
byte[] privateKeyBytes = Convert.FromBase64String(privateKey);
privateKey = Encoding.UTF8.GetString(privateKeyBytes);
rsa.ImportEncryptedPkcs8PrivateKey(
Encoding.UTF8.GetBytes(_certPassword),
Encoding.UTF8.GetBytes(privateKey), out _);
X509Certificate2 certificate = new X509Certificate2(_certPath);
certificate = certificate.CopyWithPrivateKey(rsa);
return certificate;