/* Copyright (c) 2013, 2014 Paolo Patierno All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. Contributors: Paolo Patierno - initial API and implementation and/or initial documentation */ #if SSL #if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 using Microsoft.SPOT.Net.Security; #else using System.Security.Authentication; #if NETCOREAPP using System.Net.Security; #endif #endif #endif using System.Net.Sockets; using System.Net; using System.Security.Cryptography.X509Certificates; using System; //using System.Security.Authentication; //using System.Net.Security; namespace uPLibrary.Networking.M2Mqtt { /// /// Channel to communicate over the network /// public class MqttNetworkChannel : IMqttNetworkChannel { #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) private readonly RemoteCertificateValidationCallback userCertificateValidationCallback; private readonly LocalCertificateSelectionCallback userCertificateSelectionCallback; #endif // remote host information // socket for communication private Socket socket; // using SSL private readonly Boolean secure; // CA certificate (on client) private readonly X509Certificate caCert; // Server certificate (on broker) private readonly X509Certificate serverCert; // client certificate (on client) private readonly X509Certificate clientCert; // SSL/TLS protocol version private readonly MqttSslProtocols sslProtocol; /// /// Remote host name /// public String RemoteHostName { get; } /// /// Remote IP address /// public IPAddress RemoteIpAddress { get; } /// /// Remote port /// public Int32 RemotePort { get; } #if SSL // SSL stream private SslStream sslStream; #if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 private NetworkStream netStream; #endif #endif /// /// Data available on the channel /// #if SSL #if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 public Boolean DataAvailable => this.secure ? this.sslStream.DataAvailable : this.socket.Available > 0; #else public Boolean DataAvailable => this.secure ? this.netStream.DataAvailable : this.socket.Available > 0; #endif #else public Boolean DataAvailable => this.socket.Available > 0; #endif /// /// Constructor /// /// Socket opened with the client public MqttNetworkChannel(Socket socket) #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) : this(socket, false, null, MqttSslProtocols.None, null, null) #else : this(socket, false, null, MqttSslProtocols.None) #endif { } /// /// Constructor /// /// Socket opened with the client /// Secure connection (SSL/TLS) /// Server X509 certificate for secure connection /// SSL/TLS protocol version #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication public MqttNetworkChannel(Socket socket, Boolean secure, X509Certificate serverCert, MqttSslProtocols sslProtocol, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback) #else public MqttNetworkChannel(Socket socket, bool secure, X509Certificate serverCert, MqttSslProtocols sslProtocol) #endif { this.socket = socket; this.secure = secure; this.serverCert = serverCert; this.sslProtocol = sslProtocol; #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) this.userCertificateValidationCallback = userCertificateValidationCallback; this.userCertificateSelectionCallback = userCertificateSelectionCallback; #endif } /// /// Constructor /// /// Remote Host name /// Remote port public MqttNetworkChannel(String remoteHostName, Int32 remotePort) #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) : this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None, null, null) #else : this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None) #endif { } /// /// Constructor /// /// Remote Host name /// Remote port /// Using SSL /// CA certificate /// Client certificate /// SSL/TLS protocol version #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) /// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication public MqttNetworkChannel(String remoteHostName, Int32 remotePort, Boolean secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback) #else public MqttNetworkChannel(string remoteHostName, int remotePort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol) #endif { IPAddress remoteIpAddress = null; try { // check if remoteHostName is a valid IP address and get it remoteIpAddress = IPAddress.Parse(remoteHostName); } catch { } // in this case the parameter remoteHostName isn't a valid IP address if (remoteIpAddress == null) { IPHostEntry hostEntry = Dns.GetHostEntry(remoteHostName); if (hostEntry != null && hostEntry.AddressList.Length > 0) { // check for the first address not null // it seems that with .Net Micro Framework, the IPV6 addresses aren't supported and return "null" Int32 i = 0; while (hostEntry.AddressList[i] == null) { i++; } remoteIpAddress = hostEntry.AddressList[i]; } else { throw new Exception("No address found for the remote host name"); } } this.RemoteHostName = remoteHostName; this.RemoteIpAddress = remoteIpAddress; this.RemotePort = remotePort; this.secure = secure; this.caCert = caCert; this.clientCert = clientCert; this.sslProtocol = sslProtocol; #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK) this.userCertificateValidationCallback = userCertificateValidationCallback; this.userCertificateSelectionCallback = userCertificateSelectionCallback; #endif } /// /// Connect to remote server /// public void Connect() { this.socket = new Socket(IPAddressUtility.GetAddressFamily(this.RemoteIpAddress), SocketType.Stream, ProtocolType.Tcp); // try connection to the broker this.socket.Connect(new IPEndPoint(this.RemoteIpAddress, this.RemotePort)); #if SSL // secure channel requested if (this.secure) { // create SSL stream #if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 this.sslStream = new SslStream(this.socket); #else this.netStream = new NetworkStream(this.socket); this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback); #endif // server authentication (SSL/TLS handshake) #if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 this.sslStream.AuthenticateAsClient(this.remoteHostName, this.clientCert, new X509Certificate[] { this.caCert }, SslVerification.CertificateRequired, MqttSslUtility.ToSslPlatformEnum(this.sslProtocol)); #else X509CertificateCollection clientCertificates = null; // check if there is a client certificate to add to the collection, otherwise it's null (as empty) if (this.clientCert != null) { clientCertificates = new X509CertificateCollection(new X509Certificate[] { this.clientCert }); } this.sslStream.AuthenticateAsClient(this.RemoteHostName, clientCertificates, MqttSslUtility.ToSslPlatformEnum(this.sslProtocol), false); #endif } #endif } /// /// Send data on the network channel /// /// Data buffer to send /// Number of byte sent public Int32 Send(Byte[] buffer) { #if SSL if (this.secure) { this.sslStream.Write(buffer, 0, buffer.Length); this.sslStream.Flush(); return buffer.Length; } else { return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); } #else return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None); #endif } /// /// Receive data from the network /// /// Data buffer for receiving data /// Number of bytes received public Int32 Receive(Byte[] buffer) { #if SSL if (this.secure) { // read all data needed (until fill buffer) Int32 idx = 0, read = 0; while (idx < buffer.Length) { // fixed scenario with socket closed gracefully by peer/broker and // Read return 0. Avoid infinite loop. read = this.sslStream.Read(buffer, idx, buffer.Length - idx); if (read == 0) { return 0; } idx += read; } return buffer.Length; } else { // read all data needed (until fill buffer) Int32 idx = 0, read = 0; while (idx < buffer.Length) { // fixed scenario with socket closed gracefully by peer/broker and // Read return 0. Avoid infinite loop. read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); if (read == 0) { return 0; } idx += read; } return buffer.Length; } #else // read all data needed (until fill buffer) int idx = 0, read = 0; while (idx < buffer.Length) { // fixed scenario with socket closed gracefully by peer/broker and // Read return 0. Avoid infinite loop. read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None); if (read == 0) return 0; idx += read; } return buffer.Length; #endif } /// /// Receive data from the network channel with a specified timeout /// /// Data buffer for receiving data /// Timeout on receiving (in milliseconds) /// Number of bytes received public Int32 Receive(Byte[] buffer, Int32 timeout) => // check data availability (timeout is in microseconds) this.socket.Poll(timeout * 1000, SelectMode.SelectRead) ? this.Receive(buffer) : 0; /// /// Close the network channel /// public void Close() { #if SSL if (this.secure) { #if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 this.netStream.Close(); #endif this.sslStream.Close(); } this.socket.Close(); #else this.socket.Close(); #endif } /// /// Accept connection from a remote client /// public void Accept() { #if SSL // secure channel requested if (this.secure) { #if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3) this.netStream = new NetworkStream(this.socket); this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback); this.sslStream.AuthenticateAsServer(this.serverCert, false, MqttSslUtility.ToSslPlatformEnum(this.sslProtocol), false); #endif } return; #else return; #endif } } /// /// IPAddress Utility class /// public static class IPAddressUtility { /// /// Return AddressFamily for the IP address /// /// IP address to check /// Address family public static AddressFamily GetAddressFamily(IPAddress ipAddress) => #if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 ipAddress.AddressFamily; #else return (ipAddress.ToString().IndexOf(':') != -1) ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; #endif } /// /// MQTT SSL utility class /// public static class MqttSslUtility { #if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 && !COMPACT_FRAMEWORK public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) => mqttSslProtocol switch { MqttSslProtocols.None => SslProtocols.None, //MqttSslProtocols.SSLv3 => SslProtocols.Ssl3, MqttSslProtocols.TLSv1_0 => SslProtocols.Tls, MqttSslProtocols.TLSv1_1 => SslProtocols.Tls11, MqttSslProtocols.TLSv1_2 => SslProtocols.Tls12, _ => throw new ArgumentException("SSL/TLS protocol version not supported"), }; #elif MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) { switch (mqttSslProtocol) { case MqttSslProtocols.None: return SslProtocols.None; case MqttSslProtocols.SSLv3: return SslProtocols.SSLv3; case MqttSslProtocols.TLSv1_0: return SslProtocols.TLSv1; case MqttSslProtocols.TLSv1_1: case MqttSslProtocols.TLSv1_2: default: throw new ArgumentException("SSL/TLS protocol version not supported"); } } #endif } }