The goal is a web application that exchanges information with an C# application that is installed on the user's pc. The client application is the websocket server and the browser is the websocket client.
In the end the websocket client in the user's browser is created persistently via Angular and the application is running on the pc and doing some things.
The C# library used is WebSocket-Sharp. The websocket client is normal javascript.
Obviously this connection happens only local so the client connects to localhost. As the website is secured via HTTPS the websocket has to be secured too. For this purpose the C# application creates a certificate when it starts up (it's just for testing purposes actually).
The connection doesn't works because the certificate is untrusted. All server checks for the client are disabled but the connection won't establish.
This is the part where the server is created
_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();
This is how the certificate is created with 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;
}
This behaviour is logical although it is strange as the cert check shouldn't be performed locally. Is there a possibility to bypass this problem? I already thought about installing the issuer certificate to the trusted certs but this is not an optimal solution.
The WebSocket (WS) protocol runs on TCP (like HTTP), and the WSS connection runs on TLS/SSL, which, in turn, runs on TCP. The WebSocket protocol is compatible with HTTP such that the WebSocket connection uses the same ports: the WebSocket default port is 80 and WebSocket Secure (WSS) uses port 443 by default.
You should strongly prefer the secure wss:// protocol over the insecure ws:// transport. Like HTTPS, WSS (WebSockets over SSL/TLS) is encrypted, thus protecting against man-in-the-middle attacks. A variety of attacks against WebSockets become impossible if the transport is secured.
If an encrypted WebSocket connection is used, then the use of Transport Layer Security (TLS) in the WebSocket Secure connection ensures that an HTTP CONNECT command is issued when the browser is configured to use an explicit proxy server.
Have you tried any of the answers to this question?
To summarize, it looks like there are a few options you could try:
Start Chrome with the --ignore-certificate-errors
argument specified.
Start an HTTP server on the same port that takes the same self-signed certificate, browse to it, and accept the certificate, after which you should be able to use the WebSocket connection.
Set the configuration option on Firefox network.websocket.allowInsecureFromHTTPS
to true
, then use the ws://
rather than the wss://
address.
If all this is is for testing and you have the possibility to control that sort of thing, then I think one or more of those should work. If you need your standard end user to be able to do this, I think you'll a need a different solution. As you've found, it doesn't matter if you set the server up to not care about the certificate, the client has to ultimately decide if it wants to accept the certificate or it won't accept the connection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With