无法让 C# SslStream 使用我的 X509 证书
Can't get C# SslStream to work with my X509 certs
我是安全和加密方面的新手,所以如果我犯了一个非常愚蠢的错误,请提前道歉。
我需要服务器和客户端使用 SslStream 通过安全连接进行通信。但是我的证书不起作用。我收到以下错误:System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.'
我的代码是文档中给出的 Microsoft 示例:https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=netframework-4.8ss
我试过了:
- 使用 makecert 自签名证书(如 post:SSLStream example - how do I get certificates that work?)
- 使用 OpenSSL 的证书
- 本站程序:http://www.reliablesoftware.com/DasBlog/PermaLink,guid,6507b2c6-473e-4ddc-9e66-8a161e5df6e9.aspx
- 使用 .pfx 文件而不是 .cer 文件(如 post:X509Certificate2 the server mode SSL must use a certificate with the associated private key),但出现以下异常:
Win32Exception: The certificate chain was issued by an authority that is not trusted.
除最后一个外,其他人都给出了 System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.'
异常。
这是否意味着自签名证书不起作用?我需要购买证书吗?
编辑:
这是我使用的代码。它是修改后的示例(很抱歉,如果我的编码很糟糕)并且是可执行的,模拟服务器和客户端并抛出异常:
class Program
{
static void Main(string[] args)
{
//Temporarily added the arguments here for you to see
args = new string[2] { @"C:\Users\jacke\Documents\CA\TempCert.cer", "FakeServerName" };
Console.WriteLine("Starting server in seperate thread...");
Task t = Task.Run(() => { Server.Initialize(args[0]); });
Task.Delay(500).Wait();
Client.RunClient(args[1]);
}
}
public static class Server
{
private static X509Certificate cert;
private static TcpListener server;
public static void Initialize(string certificate)
{
cert = X509Certificate.CreateFromCertFile(certificate);
server = new TcpListener(IPAddress.Any, 12321);
server.Start();
while (true)
{
Console.WriteLine("Waiting for a client to connect...");
TcpClient client = server.AcceptTcpClient();
ProcessClient(client);
}
}
private static void ProcessClient(TcpClient client)
{
SslStream sslStream = new SslStream(client.GetStream(), false);
try
{
sslStream.AuthenticateAsServer(cert, clientCertificateRequired: false, checkCertificateRevocation: true);
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
Console.WriteLine("Waiting for client message...");
string messageData = Helpers.ReadMessage(sslStream);
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
Console.WriteLine("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally
{
sslStream.Close();
client.Close();
}
}
}
public static class Client
{
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
public static void RunClient(string serverName)
{
TcpClient client = new TcpClient("localhost", 12321);
Console.WriteLine("Client connected.");
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
sslStream.Write(messsage);
string serverMessage = Helpers.ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
client.Close();
Console.WriteLine("Client closed.");
}
}
public static class Helpers
{
public static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
}
这是我创建证书的方式(如我上面链接的 post 中所述):
makecert -sv RootCATest.pvk -r -n "CN=FakeServerName" RootCATest.cer
makecert -ic RootCATest.cer -iv RootCATest.pvk -n "CN=FakeServerName" -sv
TempCert.pvk -pe -sky exchange TempCert.cer
cert2spc TempCert.cer TempCert.spc
pvkimprt -pfx TempCert.spc TempCert.pvk
我用上面的命令输入的附加信息:
- 当前 2 个命令要求输入密码时,我将其留空
- 我检查了导出私钥并将'A'设置为最后一个命令的密码
然后我在本地证书存储中导入 .pfx 文件(我之前也尝试过机器范围)并让程序选择正确的存储。它警告我 CA 颁发的所有证书都将受到信任,我应该联系 CA 以检查这确实是他们的证书,但我继续了。然后我 运行 代码(使用我刚刚创建的 'TempCert.cer' 文件)并得到错误。非常感谢任何建议!
如果我没记错的话,.cer
文件不包含私钥,并且我假设 通过从文件加载证书未检查证书存储。您可以导出 .pfx
并在导出中包含私钥。
我不知道为什么 .pvk
尝试失败了(我自己从未尝试过这种格式)- SslStream
绝对可以使用自签名证书;客户端只需忽略验证失败即可。
但是,如果您已经将它导入商店,您应该可以直接加载它:
using System.Security.Cryptography.X509Certificates;
// ...
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "FakeServerName", false);
return new X509Certificate2(certs[0]);
}
回应您评论中的错误:
The credentials supplied to the package were not recognized
我不知道我是否收到了那条消息,但我确实记得必须向用户授予应用程序 运行 进入的访问权限。您必须打开 正确的 证书工具:
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in
(运行 mmc,select file/add 管理单元,选择证书)
然后授予用户访问私钥的权限:
https://docs.secureauth.com/display/KBA/Grant+Permission+to+Use+Signing+Certificate+Private+Key
(右键单击证书,select 所有 tasks/manage 私钥)
我是安全和加密方面的新手,所以如果我犯了一个非常愚蠢的错误,请提前道歉。
我需要服务器和客户端使用 SslStream 通过安全连接进行通信。但是我的证书不起作用。我收到以下错误:System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.'
我的代码是文档中给出的 Microsoft 示例:https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=netframework-4.8ss
我试过了:
- 使用 makecert 自签名证书(如 post:SSLStream example - how do I get certificates that work?)
- 使用 OpenSSL 的证书
- 本站程序:http://www.reliablesoftware.com/DasBlog/PermaLink,guid,6507b2c6-473e-4ddc-9e66-8a161e5df6e9.aspx
- 使用 .pfx 文件而不是 .cer 文件(如 post:X509Certificate2 the server mode SSL must use a certificate with the associated private key),但出现以下异常:
Win32Exception: The certificate chain was issued by an authority that is not trusted.
除最后一个外,其他人都给出了 System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.'
异常。
这是否意味着自签名证书不起作用?我需要购买证书吗?
编辑: 这是我使用的代码。它是修改后的示例(很抱歉,如果我的编码很糟糕)并且是可执行的,模拟服务器和客户端并抛出异常:
class Program
{
static void Main(string[] args)
{
//Temporarily added the arguments here for you to see
args = new string[2] { @"C:\Users\jacke\Documents\CA\TempCert.cer", "FakeServerName" };
Console.WriteLine("Starting server in seperate thread...");
Task t = Task.Run(() => { Server.Initialize(args[0]); });
Task.Delay(500).Wait();
Client.RunClient(args[1]);
}
}
public static class Server
{
private static X509Certificate cert;
private static TcpListener server;
public static void Initialize(string certificate)
{
cert = X509Certificate.CreateFromCertFile(certificate);
server = new TcpListener(IPAddress.Any, 12321);
server.Start();
while (true)
{
Console.WriteLine("Waiting for a client to connect...");
TcpClient client = server.AcceptTcpClient();
ProcessClient(client);
}
}
private static void ProcessClient(TcpClient client)
{
SslStream sslStream = new SslStream(client.GetStream(), false);
try
{
sslStream.AuthenticateAsServer(cert, clientCertificateRequired: false, checkCertificateRevocation: true);
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
Console.WriteLine("Waiting for client message...");
string messageData = Helpers.ReadMessage(sslStream);
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
Console.WriteLine("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally
{
sslStream.Close();
client.Close();
}
}
}
public static class Client
{
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
public static void RunClient(string serverName)
{
TcpClient client = new TcpClient("localhost", 12321);
Console.WriteLine("Client connected.");
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
sslStream.Write(messsage);
string serverMessage = Helpers.ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
client.Close();
Console.WriteLine("Client closed.");
}
}
public static class Helpers
{
public static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
}
这是我创建证书的方式(如我上面链接的 post 中所述):
makecert -sv RootCATest.pvk -r -n "CN=FakeServerName" RootCATest.cer
makecert -ic RootCATest.cer -iv RootCATest.pvk -n "CN=FakeServerName" -sv
TempCert.pvk -pe -sky exchange TempCert.cer
cert2spc TempCert.cer TempCert.spc
pvkimprt -pfx TempCert.spc TempCert.pvk
我用上面的命令输入的附加信息:
- 当前 2 个命令要求输入密码时,我将其留空
- 我检查了导出私钥并将'A'设置为最后一个命令的密码
然后我在本地证书存储中导入 .pfx 文件(我之前也尝试过机器范围)并让程序选择正确的存储。它警告我 CA 颁发的所有证书都将受到信任,我应该联系 CA 以检查这确实是他们的证书,但我继续了。然后我 运行 代码(使用我刚刚创建的 'TempCert.cer' 文件)并得到错误。非常感谢任何建议!
如果我没记错的话,.cer
文件不包含私钥,并且我假设 通过从文件加载证书未检查证书存储。您可以导出 .pfx
并在导出中包含私钥。
我不知道为什么 .pvk
尝试失败了(我自己从未尝试过这种格式)- SslStream
绝对可以使用自签名证书;客户端只需忽略验证失败即可。
但是,如果您已经将它导入商店,您应该可以直接加载它:
using System.Security.Cryptography.X509Certificates;
// ...
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "FakeServerName", false);
return new X509Certificate2(certs[0]);
}
回应您评论中的错误:
The credentials supplied to the package were not recognized
我不知道我是否收到了那条消息,但我确实记得必须向用户授予应用程序 运行 进入的访问权限。您必须打开 正确的 证书工具:
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in (运行 mmc,select file/add 管理单元,选择证书)
然后授予用户访问私钥的权限:
https://docs.secureauth.com/display/KBA/Grant+Permission+to+Use+Signing+Certificate+Private+Key (右键单击证书,select 所有 tasks/manage 私钥)