【问题标题】:WebSocket secure connection self signed certificateWebSocket 安全连接自签名证书
【发布时间】:2017-12-18 18:04:26
【问题描述】:

目标是与安装在用户 PC 上的 C# 应用程序交换信息的 Web 应用程序。 客户端应用程序是 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;

}

这种行为是合乎逻辑的,尽管它很奇怪,因为不应该在本地执行证书检查。 有没有可能绕过这个问题?我已经考虑过将颁发者证书安装到受信任的证书,但这不是最佳解决方案。

【问题讨论】:

  • the cert check shouldn't be performed locally 嗯?
  • @SLaks 我的意思是当与 localhost 建立连接时不应强制使用证书
  • 安全 localhost 连接的一种简单方法是将子域路由到 localhost,然后为该域选择 ssl 证书。像魅力一样工作。

标签: c# websocket websocket-sharp


【解决方案1】:

你试过这个question的任何答案吗?

总而言之,您可以尝试以下几个选项:

  • 使用指定的 --ignore-certificate-errors 参数启动 Chrome。

  • 在相同的端口上启动一个 HTTP 服务器,使用相同的自签名证书,浏览到它并接受证书,之后您应该能够使用 WebSocket 连接。

    李>
  • 将 Firefox 上的配置选项network.websocket.allowInsecureFromHTTPS 设置为true,然后使用ws:// 而不是wss:// 地址。

如果这一切都是为了测试并且您有可能控制这类事情,那么我认为其中一个或多个应该可以工作。如果您需要您的标准最终用户能够做到这一点,我认为您将需要一个不同的解决方案。正如您所发现的,如果您将服务器设置为不关心证书并不重要,客户端必须最终决定是要接受证书还是不接受连接。

【讨论】:

  • 您对这个问题的替代解决方案有什么建议吗?我考虑过 WebRTC,但 C# 没有好的实现。是否有其他选项可以允许这样的结构:img.klemm.one/EZRYx?解决方案可能是交换服务器和客户端,以便浏览器成为服务器。但我不确定这是否可能。
  • 我不是这方面的专家,但我认为您可能过快地驳回了@Fabien 的回答。因为 Let's Encrypt 是受信任的 CA (letsencrypt.org/2015/10/19/lets-encrypt-is-trusted.html),所以您不需要将证书添加到受信任的证书中,因为它已经是受信任的。我认为你应该能够使用 https/wss 而无需做任何额外的事情。
  • 我从未拒绝过他的回答。核心问题是 websocket 的连接总是发生在 localhost 上。无法获得 localhost 的证书。本地主机在这里不是一个测试的东西,它旨在成为最终的解决方案。 Web 应用程序应该能够与客户端 PC 上运行的程序进行通信。
  • @chris579 好点。您在问题中提到了“仅用于测试目的”,所以我想我假设您将在生产中采用不同的方式来执行此操作。一个想法:如果您的客户端应用程序被安装,在安装期间,您可以生成自签名证书并以编程方式将其添加到受信任的证书中。
  • 已经考虑过添加这样的证书。因为这个证书将适用于所有 localhost 端口,这会不会有点冒险?以及如何以编程方式添加这样的证书?
【解决方案2】:

@Kdawg 的答案是正确的。

您不希望客户端浏览器只接受服务器端调整的不安全连接。接受未签名(或自签名)证书的所有行为都在客户端。

除了@Kdawg 的回答之外,我想补充一点,在 Windows 网络上,私人组织最常见的做法是:

  1. 分配一个 Windows Server 作为证书颁发机构

  2. 将证书颁发机构的公共根证书添加到 Windows 主机(通过GPO)或manually

  3. 使用 Windows CA 服务器签署自定义证书

听起来很痛苦,确实如此。

如果我是你,我会制作一个标准的公开签名证书,并在完成之前关闭 SSL。

查看Let's Encrypt 以获得您域的免费 SSL 证书。

【讨论】:

  • 问题本身不是域的 SSL 证书丢失,而是应该实现的通信结构。应该是这样的:img.klemm.one/EZRYx。关闭 SSL 不是一个选项,将证书添加到受信任的证书也不是一个选项。是否有可用于此的 websockets 的替代品?我考虑过使用原始 tcp,但客户端 javascript 无法做到这一点。
【解决方案3】:

我的最终解决方案是为子域创建有效证书,然后将 A/AAAA 记录更改为 localhost。这样,通过 HTTPS 就可以信任连接。

【讨论】:

    猜你喜欢
    • 2021-08-06
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-12
    • 1970-01-01
    相关资源
    最近更新 更多