WebSocket 安全连接自签名证书
WebSocket secure connection self signed certificate
目标是一个 Web 应用程序,它与安装在用户电脑上的 C# 应用程序交换信息。
客户端应用程序是websocket服务器,浏览器是websocket客户端。
最后,用户浏览器中的 websocket 客户端是通过 Angular 持久创建的,应用程序在 pc 上 运行 并做一些事情。
使用的 C# 库是 WebSocket-Sharp。 websocket客户端正常javascript.
显然此连接仅在本地发生,因此客户端连接到本地主机。
由于网站是通过 HTTPS 保护的,因此 websocket 也必须得到保护。为此,C# 应用程序在启动时会创建一个证书(实际上只是为了测试目的)。
连接无效,因为证书不受信任。客户端的所有服务器检查都已禁用,但连接不会建立。
这是创建服务器的部分
_server = new WebSocketServer($"wss://localhost:4649")
{
SslConfiguration =
{
ServerCertificate = Utils.Certificate.CreateSelfSignedCert(),
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
}
};
_server.AddWebSocketService<CommandsBehaviour>("/commands");
_server.AddWebSocketService<NotificationsBehaviour>("/notifications");
_server.Start();
这是使用 BouncyCastle 创建证书的方式
private static AsymmetricKeyParameter CreatePrivateKey(string subjectName = "CN=root")
{
const int keyStrength = 2048;
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = subjectDn;
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair.Private;
}
public static X509Certificate2 CreateSelfSignedCert(string subjectName = "CN=localhost", string issuerName = "CN=root")
{
const int keyStrength = 2048;
var issuerPrivKey = CreatePrivateKey();
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName[] { new GeneralName(GeneralName.DnsName, "localhost"), new GeneralName(GeneralName.DnsName, "127.0.0.1") }));
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage((new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Signature Algorithm
//const string signatureAlgorithm = "SHA512WITHRSA";
//certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// self sign certificate
var certificate = certificateGenerator.Generate(signatureFactory);
// corresponding private key
var info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
var x509 = new X509Certificate2(certificate.GetEncoded());
var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq); //new RsaPrivateKeyStructure(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
return x509;
}
这种行为是合乎逻辑的,尽管它很奇怪,因为不应在本地执行证书检查。
有没有可能绕过这个问题?我已经考虑过将颁发者证书安装到受信任的证书,但这不是最佳解决方案。
你试过这个question的任何答案吗?
总而言之,您似乎可以尝试以下几种选择:
使用指定的 --ignore-certificate-errors
参数启动 Chrome。
在采用相同自签名证书的同一端口上启动 HTTP 服务器,浏览并接受证书,之后您应该可以使用 WebSocket 连接。
将 Firefox network.websocket.allowInsecureFromHTTPS
上的配置选项设置为 true
,然后使用 ws://
而不是 wss://
地址。
如果所有这些都是为了测试,并且您有可能控制这类事情,那么我认为其中一个或多个应该可行。如果您需要您的标准最终用户能够执行此操作,我认为您需要一个不同的解决方案。正如您所发现的,如果您将服务器设置为不关心证书并不重要,客户端必须最终决定是要接受证书还是不接受连接。
@Kdawg 回答正确。
您不希望客户端浏览器只接受服务器端调整的不安全连接。接受未签名(或自签名)证书的所有行为都在客户端。
除了@Kdawg 的回答之外,我想补充一点,在 Windows 网络上,私人组织最常见的做法是:
分配 Windows 服务器作为证书颁发机构
使用 Windows CA 服务器签署定制证书
听起来很痛苦,确实如此。
如果我是你,我会制作一个标准的 public 刚刚签署的证书,并会 运行 关闭 SSL,直到它完成。
查看 Let's Encrypt 以获取适用于您的域的免费 SSL 证书。
我的最终解决方案是为子域创建一个有效证书,然后将 A/AAAA 记录更改为 localhost。这样连接就可以通过 HTTPS 得到信任。
目标是一个 Web 应用程序,它与安装在用户电脑上的 C# 应用程序交换信息。 客户端应用程序是websocket服务器,浏览器是websocket客户端。
最后,用户浏览器中的 websocket 客户端是通过 Angular 持久创建的,应用程序在 pc 上 运行 并做一些事情。
使用的 C# 库是 WebSocket-Sharp。 websocket客户端正常javascript.
显然此连接仅在本地发生,因此客户端连接到本地主机。 由于网站是通过 HTTPS 保护的,因此 websocket 也必须得到保护。为此,C# 应用程序在启动时会创建一个证书(实际上只是为了测试目的)。
连接无效,因为证书不受信任。客户端的所有服务器检查都已禁用,但连接不会建立。
这是创建服务器的部分
_server = new WebSocketServer($"wss://localhost:4649")
{
SslConfiguration =
{
ServerCertificate = Utils.Certificate.CreateSelfSignedCert(),
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
}
};
_server.AddWebSocketService<CommandsBehaviour>("/commands");
_server.AddWebSocketService<NotificationsBehaviour>("/notifications");
_server.Start();
这是使用 BouncyCastle 创建证书的方式
private static AsymmetricKeyParameter CreatePrivateKey(string subjectName = "CN=root")
{
const int keyStrength = 2048;
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = subjectDn;
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair.Private;
}
public static X509Certificate2 CreateSelfSignedCert(string subjectName = "CN=localhost", string issuerName = "CN=root")
{
const int keyStrength = 2048;
var issuerPrivKey = CreatePrivateKey();
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName[] { new GeneralName(GeneralName.DnsName, "localhost"), new GeneralName(GeneralName.DnsName, "127.0.0.1") }));
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage((new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Signature Algorithm
//const string signatureAlgorithm = "SHA512WITHRSA";
//certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// self sign certificate
var certificate = certificateGenerator.Generate(signatureFactory);
// corresponding private key
var info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
var x509 = new X509Certificate2(certificate.GetEncoded());
var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq); //new RsaPrivateKeyStructure(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
return x509;
}
这种行为是合乎逻辑的,尽管它很奇怪,因为不应在本地执行证书检查。 有没有可能绕过这个问题?我已经考虑过将颁发者证书安装到受信任的证书,但这不是最佳解决方案。
你试过这个question的任何答案吗?
总而言之,您似乎可以尝试以下几种选择:
使用指定的
--ignore-certificate-errors
参数启动 Chrome。在采用相同自签名证书的同一端口上启动 HTTP 服务器,浏览并接受证书,之后您应该可以使用 WebSocket 连接。
将 Firefox
network.websocket.allowInsecureFromHTTPS
上的配置选项设置为true
,然后使用ws://
而不是wss://
地址。
如果所有这些都是为了测试,并且您有可能控制这类事情,那么我认为其中一个或多个应该可行。如果您需要您的标准最终用户能够执行此操作,我认为您需要一个不同的解决方案。正如您所发现的,如果您将服务器设置为不关心证书并不重要,客户端必须最终决定是要接受证书还是不接受连接。
@Kdawg 回答正确。
您不希望客户端浏览器只接受服务器端调整的不安全连接。接受未签名(或自签名)证书的所有行为都在客户端。
除了@Kdawg 的回答之外,我想补充一点,在 Windows 网络上,私人组织最常见的做法是:
分配 Windows 服务器作为证书颁发机构
使用 Windows CA 服务器签署定制证书
听起来很痛苦,确实如此。
如果我是你,我会制作一个标准的 public 刚刚签署的证书,并会 运行 关闭 SSL,直到它完成。
查看 Let's Encrypt 以获取适用于您的域的免费 SSL 证书。
我的最终解决方案是为子域创建一个有效证书,然后将 A/AAAA 记录更改为 localhost。这样连接就可以通过 HTTPS 得到信任。