From 7ca0e98d037bcd7aa544ca8d019cb115ecce4813 Mon Sep 17 00:00:00 2001 From: Tim Rivinius Date: Thu, 17 Sep 2020 08:43:33 +0200 Subject: [PATCH 1/3] Added support for SSL/TLS in dotnet --- .../Pyrolite/Pyro/Config.cs | 4 + .../Pyrolite/Pyro/PyroProxy.cs | 73 +++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/Config.cs b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/Config.cs index 366a6b9..207d784 100644 --- a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/Config.cs +++ b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/Config.cs @@ -15,6 +15,10 @@ public static class Config { public static int NS_PORT = 9090; public static int NS_BCPORT = 9091; public static bool SERPENT_INDENT = false; + public static bool SSL = false; + public static string SSL_CACERTS = ""; + public static string SSL_CLIENTCERT = ""; // only clientcert option because it needs to be in pkcs12 format which integrates the key. PEM files are not supported! + public static string SSL_CLIENTCERTPASSWD = ""; public const int PROTOCOL_VERSION = 502; // Pyro5 public const string PYROLITE_VERSION="5.0"; diff --git a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs index 5bcdba5..6ad41e6 100644 --- a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs +++ b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs @@ -8,8 +8,10 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Net.Security; using System.Net.Sockets; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; using Razorvine.Pyro.Serializer; @@ -37,7 +39,7 @@ public class PyroProxy : DynamicObject, IDisposable { private ushort sequenceNr; private TcpClient sock; - private NetworkStream sock_stream; + private Stream sock_stream; public ISet pyroMethods = new HashSet(); // remote methods public ISet pyroAttrs = new HashSet(); // remote attributes @@ -83,7 +85,23 @@ protected void connect() { sock = new TcpClient(); sock.Connect(hostname,port); sock.NoDelay=true; - sock_stream=sock.GetStream(); + + if (Config.SSL) + { + var sslStream = new SslStream(sock.GetStream(), true, ValidateServerCertificate); + if (File.Exists(Config.SSL_CLIENTCERT)) + { + var clientCert = new X509CertificateCollection(); + clientCert.Add(new X509Certificate(Config.SSL_CLIENTCERT, Config.SSL_CLIENTCERTPASSWD)); + sslStream.AuthenticateAsClient(hostname, clientCert, System.Security.Authentication.SslProtocols.Default, false); + } else { + sslStream.AuthenticateAsClient(hostname); + } + sock_stream = sslStream; + } else { + sock_stream = sock.GetStream(); + } + sequenceNr = 0; _handshake(); @@ -97,6 +115,35 @@ protected void connect() { } } + /// + /// custom certificate validator to trust self signed CAs + /// + public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { + X509Certificate2Collection trustedCaCollection = new X509Certificate2Collection(); + + if (File.Exists(Config.SSL_CACERTS)) { + X509Certificate2 authority = new X509Certificate2(Config.SSL_CACERTS); + trustedCaCollection.Add(authority); + } else if (Directory.Exists(Config.SSL_CACERTS) && Directory.GetFiles(Config.SSL_CACERTS).Length > 0) { + foreach (var file in Directory.GetFiles(Config.SSL_CACERTS)) + { + X509Certificate2 authority = new X509Certificate2(Config.SSL_CACERTS); + trustedCaCollection.Add(authority); + } + } + + if (chain.ChainStatus.Any(status => status.Status != X509ChainStatusFlags.UntrustedRoot)) + return false; + + foreach (var element in chain.ChainElements) + { + if (element.ChainElementStatus.Any(status => status.Status == X509ChainStatusFlags.UntrustedRoot) && !trustedCaCollection.Contains(element.Certificate)) + return false; + } + + return true; + } + /// /// get metadata from server (methods, attrs, oneway, ...) and remember them in some attributes of the proxy /// @@ -182,7 +229,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } - + /// /// Call a method on the remote Pyro object this proxy is for. /// @@ -225,7 +272,7 @@ public void setattr(string attr, object value) { /// /// Returns a dict with annotations to be sent with each message. - /// Default behavior is to include the current correlation id (if it is set). + /// Default behavior is to include the current correlation id (if it is set). /// public virtual IDictionary annotations() { @@ -241,7 +288,7 @@ private object internal_call(string method, string actual_objectId, ushort flags lock(this) { connect(); unchecked { - sequenceNr++; // unchecked so this ushort wraps around 0-65535 instead of raising an OverflowException + sequenceNr++; // unchecked so this ushort wraps around 0-65535 instead of raising an OverflowException } } if(pyroAttrs.Contains(method)) { @@ -328,13 +375,13 @@ private static void _decompressMessageData(Message msg) { using(MemoryStream compressed=new MemoryStream(msg.data, 2, msg.data.Length-2, false)) { using(DeflateStream decompresser=new DeflateStream(compressed, CompressionMode.Decompress)) { MemoryStream bos = new MemoryStream(msg.data.Length); - var buffer = new byte[4096]; - int numRead; - while ((numRead = decompresser.Read(buffer, 0, buffer.Length)) != 0) { - bos.Write(buffer, 0, numRead); - } - msg.data=bos.ToArray(); - msg.flags ^= Message.FLAGS_COMPRESSED; + var buffer = new byte[4096]; + int numRead; + while ((numRead = decompresser.Read(buffer, 0, buffer.Length)) != 0) { + bos.Write(buffer, 0, numRead); + } + msg.data=bos.ToArray(); + msg.flags ^= Message.FLAGS_COMPRESSED; } } } @@ -414,7 +461,7 @@ protected void _handshake() { /// /// Process and validate the initial connection handshake response data received from the daemon. /// Simply return without error if everything is ok. - /// Throw an exception if something is wrong and the connection should not be made. + /// Throw an exception if something is wrong and the connection should not be made. /// public virtual void validateHandshake(object handshake_response) { From 97add60af0457a443b00de2b1f5afb53efd7c329 Mon Sep 17 00:00:00 2001 From: TimRivinius <39757615+TimRivinius@users.noreply.github.com> Date: Thu, 17 Sep 2020 09:18:09 +0200 Subject: [PATCH 2/3] undo formatting changes in PyroProxy.cs --- .../Pyrolite/Pyro/PyroProxy.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs index 6ad41e6..8a7de1f 100644 --- a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs +++ b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs @@ -229,7 +229,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } - + /// /// Call a method on the remote Pyro object this proxy is for. /// @@ -272,7 +272,7 @@ public void setattr(string attr, object value) { /// /// Returns a dict with annotations to be sent with each message. - /// Default behavior is to include the current correlation id (if it is set). + /// Default behavior is to include the current correlation id (if it is set). /// public virtual IDictionary annotations() { @@ -288,7 +288,7 @@ private object internal_call(string method, string actual_objectId, ushort flags lock(this) { connect(); unchecked { - sequenceNr++; // unchecked so this ushort wraps around 0-65535 instead of raising an OverflowException + sequenceNr++; // unchecked so this ushort wraps around 0-65535 instead of raising an OverflowException } } if(pyroAttrs.Contains(method)) { @@ -374,14 +374,14 @@ private static void _decompressMessageData(Message msg) { } using(MemoryStream compressed=new MemoryStream(msg.data, 2, msg.data.Length-2, false)) { using(DeflateStream decompresser=new DeflateStream(compressed, CompressionMode.Decompress)) { - MemoryStream bos = new MemoryStream(msg.data.Length); - var buffer = new byte[4096]; - int numRead; - while ((numRead = decompresser.Read(buffer, 0, buffer.Length)) != 0) { - bos.Write(buffer, 0, numRead); - } - msg.data=bos.ToArray(); - msg.flags ^= Message.FLAGS_COMPRESSED; + MemoryStream bos = new MemoryStream(msg.data.Length); + var buffer = new byte[4096]; + int numRead; + while ((numRead = decompresser.Read(buffer, 0, buffer.Length)) != 0) { + bos.Write(buffer, 0, numRead); + } + msg.data=bos.ToArray(); + msg.flags ^= Message.FLAGS_COMPRESSED; } } } @@ -461,7 +461,7 @@ protected void _handshake() { /// /// Process and validate the initial connection handshake response data received from the daemon. /// Simply return without error if everything is ok. - /// Throw an exception if something is wrong and the connection should not be made. + /// Throw an exception if something is wrong and the connection should not be made. /// public virtual void validateHandshake(object handshake_response) { From 08d82df87ed1396de393e2ed2dc800f01ff549ad Mon Sep 17 00:00:00 2001 From: TimRivinius <39757615+TimRivinius@users.noreply.github.com> Date: Thu, 17 Sep 2020 09:22:30 +0200 Subject: [PATCH 3/3] undo more formatting changes --- dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs index 8a7de1f..3f96d6f 100644 --- a/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs +++ b/dotnet/Razorvine.Pyrolite/Pyrolite/Pyro/PyroProxy.cs @@ -229,7 +229,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } - + /// /// Call a method on the remote Pyro object this proxy is for. /// @@ -374,7 +374,7 @@ private static void _decompressMessageData(Message msg) { } using(MemoryStream compressed=new MemoryStream(msg.data, 2, msg.data.Length-2, false)) { using(DeflateStream decompresser=new DeflateStream(compressed, CompressionMode.Decompress)) { - MemoryStream bos = new MemoryStream(msg.data.Length); + MemoryStream bos = new MemoryStream(msg.data.Length); var buffer = new byte[4096]; int numRead; while ((numRead = decompresser.Read(buffer, 0, buffer.Length)) != 0) {