diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4be638a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/src/S7CommPlusDriver/Properties/PublishProfiles diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/S7CommPlusDriver.slnx/FileContentIndex/dd4427cc-38fe-4a63-a882-9426dd5ac3be.vsidx b/.vs/S7CommPlusDriver.slnx/FileContentIndex/dd4427cc-38fe-4a63-a882-9426dd5ac3be.vsidx new file mode 100644 index 0000000..56e70fc Binary files /dev/null and b/.vs/S7CommPlusDriver.slnx/FileContentIndex/dd4427cc-38fe-4a63-a882-9426dd5ac3be.vsidx differ diff --git a/.vs/S7CommPlusDriver.slnx/v18/.wsuo b/.vs/S7CommPlusDriver.slnx/v18/.wsuo new file mode 100644 index 0000000..1987275 Binary files /dev/null and b/.vs/S7CommPlusDriver.slnx/v18/.wsuo differ diff --git a/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.backup.json b/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.backup.json new file mode 100644 index 0000000..da41b02 --- /dev/null +++ b/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.backup.json @@ -0,0 +1,12 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\Admin\\source\\repos\\trupa\\S7CommPlusDriver\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [] + } + ] +} \ No newline at end of file diff --git a/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.json b/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.json new file mode 100644 index 0000000..da41b02 --- /dev/null +++ b/.vs/S7CommPlusDriver.slnx/v18/DocumentLayout.json @@ -0,0 +1,12 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\Admin\\source\\repos\\trupa\\S7CommPlusDriver\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [] + } + ] +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..08213a0 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,8 @@ +{ + "ExpandedNodes": [ + "", + "\\src" + ], + "SelectedNode": "\\src\\S7CommPlusDriver.slnx", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..d9c4276 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/src/S7CommPlusDriver/Net/S7Client.cs b/src/S7CommPlusDriver/Net/S7Client.cs index 28fa468..22a61ab 100644 --- a/src/S7CommPlusDriver/Net/S7Client.cs +++ b/src/S7CommPlusDriver/Net/S7Client.cs @@ -1,147 +1,157 @@ -#region License -/****************************************************************************** - * S7CommPlusDriver - * - * Based on Snap7 (Sharp7.cs) by Davide Nardella licensed under LGPL - * - /****************************************************************************/ -#endregion - -using OpenSsl; -using System; -using System.IO; -using System.Threading; - -namespace S7CommPlusDriver -{ - // Teilweise basierend auf Snap7 (Sharp7.cs) von Davide Nardella - // | Sharp7 is free software: you can redistribute it and/or modify | - // | it under the terms of the Lesser GNU General Public License as published by | - // | the Free Software Foundation, either version 3 of the License, or | - // | (at your option) any later version. | - public class S7Client : OpenSSLConnector.IConnectorCallback - { - //TODO: better API, maybe a Callback +#region License +/****************************************************************************** + * S7CommPlusDriver + * + * Based on Snap7 (Sharp7.cs) by Davide Nardella licensed under LGPL + * + /****************************************************************************/ +#endregion + +using OpenSsl; +using System; +using System.IO; +using System.Threading; + +namespace S7CommPlusDriver +{ + // Teilweise basierend auf Snap7 (Sharp7.cs) von Davide Nardella + // | Sharp7 is free software: you can redistribute it and/or modify | + // | it under the terms of the Lesser GNU General Public License as published by | + // | the Free Software Foundation, either version 3 of the License, or | + // | (at your option) any later version. | + public class S7Client : OpenSSLConnector.IConnectorCallback + { + //TODO: better API, maybe a Callback public static bool WriteSslKeyToFile; public static string WriteSslKeyPath; - #region [Constants and TypeDefs] - - public int _LastError = 0; - - #endregion - - #region [S7 Telegrams] - - // ISO Connection Request telegram (contains also ISO Header and COTP Header) - byte[] ISO_CR = { - // TPKT (RFC1006 Header) - 0x03, // RFC 1006 ID (3) - 0x00, // Reserved, always 0 - 0x00, // High part of packet lenght (entire frame, payload and TPDU included) - 0x24, // Low part of packet lenght (entire frame, payload and TPDU included) - // COTP (ISO 8073 Header) - 0x1f, // PDU Size Length - 0xE0, // CR - Connection Request ID - 0x00, // Dst Reference HI - 0x00, // Dst Reference LO - 0x00, // Src Reference HI - 0x01, // Src Reference LO - 0x00, // Class + Options Flags - 0xC0, // PDU Max Length ID - 0x01, // PDU Max Length HI - 0x0A, // PDU Max Length LO - 0xC1, // Src TSAP Identifier - 0x02, // Src TSAP Length (2 bytes) - 0x01, // Src TSAP HI (will be overwritten) - 0x00, // Src TSAP LO (will be overwritten) - 0xC2, // Dst TSAP Identifier - 0x10, // Dst TSAP Length (16 bytes) - // Ab hier TSAP ID (String) - // SIMATIC-ROOT-HMI - }; - - // TPKT + ISO COTP Header (Connection Oriented Transport Protocol) - byte[] TPKT_ISO = { // 7 bytes - 0x03,0x00, - 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) - 0x02,0xf0,0x80 // COTP (see above for info) - }; - - #endregion - - #region S7commPlus - - bool m_SslActive = false; - Thread m_runThread; - bool m_runThread_DoStop; - IntPtr m_ptr_ssl_method; - IntPtr m_ptr_ctx; - OpenSSLConnector m_sslconn; - - DateTime m_DateTimeStarted; - Native.SSL_CTX_keylog_cb_func m_keylog_cb; - - // OpenSSL möchte Daten auf den Socket aussenden. - public void WriteData(byte[] pData, int dataLength) - { - // SSL fordert Daten zum Absenden an - // TODO: Was ist, wenn SSL Daten verschicken möchte, die größer als eine TPDU sind? - // Bei großen Zertifikaten oder ähnlichem? Fragmentierung hier? - // Console.WriteLine("S7Client - OpenSSL WriteData: dataLength=" + dataLength); - byte[] sendData = new byte[dataLength]; - Array.Copy(pData, sendData, dataLength); - SendIsoPacket(sendData); - } - - // OpenSSL meldet fertige Daten (decrypted) zum einlesen - public void OnDataAvailable() - { - // Netzwerk meldet eintreffende Daten - byte[] buf = new byte[8192]; - int bytesRead = m_sslconn.Receive(ref buf, buf.Length); - // Console.WriteLine("S7Client - OpenSSL OnDataAvailable: bytesRead=" + bytesRead); - byte[] readData = new byte[bytesRead]; - Array.Copy(buf, readData, bytesRead); - OnDataReceived?.Invoke(readData, bytesRead); - } - - // OpenSSL Key Callback Funktion. Gibt die ausgehandelden privaten Schlüssel aus. Kann beispielsweise - // in eine Wireshark Aufzeichnung eingefügt werden um dort die TLS Kommunikation zu entschlüsseln. - public void SSL_CTX_keylog_cb(IntPtr ssl, string line) - { - string filename = "key_" + m_DateTimeStarted.ToString("yyyyMMdd_HHmmss") + ".log"; + #region [Constants and TypeDefs] + + public int _LastError = 0; + + #endregion + + #region [S7 Telegrams] + + // ISO Connection Request telegram (contains also ISO Header and COTP Header) + byte[] ISO_CR = { + // TPKT (RFC1006 Header) + 0x03, // RFC 1006 ID (3) + 0x00, // Reserved, always 0 + 0x00, // High part of packet lenght (entire frame, payload and TPDU included) + 0x24, // Low part of packet lenght (entire frame, payload and TPDU included) + // COTP (ISO 8073 Header) + 0x1f, // PDU Size Length + 0xE0, // CR - Connection Request ID + 0x00, // Dst Reference HI + 0x00, // Dst Reference LO + 0x00, // Src Reference HI + 0x01, // Src Reference LO + 0x00, // Class + Options Flags + 0xC0, // PDU Max Length ID + 0x01, // PDU Max Length HI + 0x0A, // PDU Max Length LO + 0xC1, // Src TSAP Identifier + 0x02, // Src TSAP Length (2 bytes) + 0x01, // Src TSAP HI (will be overwritten) + 0x00, // Src TSAP LO (will be overwritten) + 0xC2, // Dst TSAP Identifier + 0x10, // Dst TSAP Length (16 bytes) + // Ab hier TSAP ID (String) + // SIMATIC-ROOT-HMI + }; + + // TPKT + ISO COTP Header (Connection Oriented Transport Protocol) + byte[] TPKT_ISO = { // 7 bytes + 0x03,0x00, + 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) + 0x02,0xf0,0x80 // COTP (see above for info) + }; + + #endregion + + #region S7commPlus + + bool m_SslActive = false; + Thread m_runThread; + bool m_runThread_DoStop; + IntPtr m_ptr_ssl_method; + IntPtr m_ptr_ctx; + OpenSSLConnector m_sslconn; + + DateTime m_DateTimeStarted; + Native.SSL_CTX_keylog_cb_func m_keylog_cb; + + // OpenSSL möchte Daten auf den Socket aussenden. + public void WriteData(byte[] pData, int dataLength) + { + // SSL requests data for sending. + // We must fragment data that is larger than one TPDU (usually 1024 bytes). + const int maxTpduPayload = 900; // Safe value below 1024 to account for headers + int offset = 0; + + while (offset < dataLength) + { + int chunkLength = Math.Min(maxTpduPayload, dataLength - offset); + byte[] chunk = new byte[chunkLength]; + Array.Copy(pData, offset, chunk, 0, chunkLength); + + // If this is the last piece of the original data, set the 'isLast' flag + bool isLast = (offset + chunkLength) == dataLength; + SendIsoFragment(chunk, isLast); + + offset += chunkLength; + } + } + + // OpenSSL meldet fertige Daten (decrypted) zum einlesen + public void OnDataAvailable() + { + // Netzwerk meldet eintreffende Daten + byte[] buf = new byte[8192]; + int bytesRead = m_sslconn.Receive(ref buf, buf.Length); + // Console.WriteLine("S7Client - OpenSSL OnDataAvailable: bytesRead=" + bytesRead); + byte[] readData = new byte[bytesRead]; + Array.Copy(buf, readData, bytesRead); + OnDataReceived?.Invoke(readData, bytesRead); + } + + // OpenSSL Key Callback Funktion. Gibt die ausgehandelden privaten Schlüssel aus. Kann beispielsweise + // in eine Wireshark Aufzeichnung eingefügt werden um dort die TLS Kommunikation zu entschlüsseln. + public void SSL_CTX_keylog_cb(IntPtr ssl, string line) + { + string filename = "key_" + m_DateTimeStarted.ToString("yyyyMMdd_HHmmss") + ".log"; if (WriteSslKeyPath != null) - filename = Path.Combine(WriteSslKeyPath, filename); - StreamWriter file = new StreamWriter(filename, append: true); - file.WriteLine(line); - file.Close(); - } - - // Startet OpenSSL und aktiviert ab jetzt TLS - public int SslActivate() - { - int ret; - try - { - ret = Native.OPENSSL_init_ssl(0, IntPtr.Zero); // returns 1 on success or 0 on error - if (ret != 1) - { - return S7Consts.errOpenSSL; - } - m_ptr_ssl_method = Native.ExpectNonNull(Native.TLS_client_method()); - m_ptr_ctx = Native.ExpectNonNull(Native.SSL_CTX_new(m_ptr_ssl_method)); - // TLS 1.3 forcieren, da wegen TLS on IsoOnTCP bekannt sein muss, um wie viele Bytes sich die verschlüsselten - // Daten verlängern um die Pakete auf S7CommPlus-Ebene entsprechend zu fragmentieren. - // Die Verlängerung geschieht z.B. durch Padding und HMAC. Bei TLS 1.3 existiert mit GCM kein Padding und verlängert sich immer - // um 16 Bytes. Da auch TLS_CHACHA20_POLY1305_SHA256 zu den TLS 1.3 CipherSuite zählt, explizit die anderen setzen. - Native.SSL_CTX_ctrl(m_ptr_ctx, Native.SSL_CTRL_SET_MIN_PROTO_VERSION, Native.TLS1_3_VERSION, IntPtr.Zero); - ret = Native.SSL_CTX_set_ciphersuites(m_ptr_ctx, "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"); - if (ret != 1) - { - return S7Consts.errOpenSSL; - } - m_sslconn = new OpenSSLConnector(m_ptr_ctx, this); + filename = Path.Combine(WriteSslKeyPath, filename); + StreamWriter file = new StreamWriter(filename, append: true); + file.WriteLine(line); + file.Close(); + } + + // Startet OpenSSL und aktiviert ab jetzt TLS + public int SslActivate() + { + int ret; + try + { + ret = Native.OPENSSL_init_ssl(0, IntPtr.Zero); // returns 1 on success or 0 on error + if (ret != 1) + { + return S7Consts.errOpenSSL; + } + m_ptr_ssl_method = Native.ExpectNonNull(Native.TLS_client_method()); + m_ptr_ctx = Native.ExpectNonNull(Native.SSL_CTX_new(m_ptr_ssl_method)); + // TLS 1.3 forcieren, da wegen TLS on IsoOnTCP bekannt sein muss, um wie viele Bytes sich die verschlüsselten + // Daten verlängern um die Pakete auf S7CommPlus-Ebene entsprechend zu fragmentieren. + // Die Verlängerung geschieht z.B. durch Padding und HMAC. Bei TLS 1.3 existiert mit GCM kein Padding und verlängert sich immer + // um 16 Bytes. Da auch TLS_CHACHA20_POLY1305_SHA256 zu den TLS 1.3 CipherSuite zählt, explizit die anderen setzen. + Native.SSL_CTX_ctrl(m_ptr_ctx, Native.SSL_CTRL_SET_MIN_PROTO_VERSION, Native.TLS1_3_VERSION, IntPtr.Zero); + ret = Native.SSL_CTX_set_ciphersuites(m_ptr_ctx, "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"); + if (ret != 1) + { + return S7Consts.errOpenSSL; + } + m_sslconn = new OpenSSLConnector(m_ptr_ctx, this); m_sslconn.ExpectConnect(); // Keylog callback setzen @@ -149,573 +159,599 @@ public int SslActivate() { m_keylog_cb = new Native.SSL_CTX_keylog_cb_func(SSL_CTX_keylog_cb); Native.SSL_CTX_set_keylog_callback(m_ptr_ctx, m_keylog_cb); - } - - m_SslActive = true; - } - catch - { - return S7Consts.errOpenSSL; - } - return 0; - } - - // Deaktiviert TLS - public void SslDeactivate() - { - m_SslActive = false; - // TODO: Ist hier etwas zu OpenSSL-Ressourcen explizit freizugeben? - } - #endregion - - private void StartThread() - { - m_runThread_DoStop = false; - m_runThread = new Thread(RunThread); - m_runThread.Start(); - } - - // Der Task der kontinuierlich ausgeführt wird - private void RunThread() - { - int Length; - while (!m_runThread_DoStop) - { - // Versuchen zu lesen - _LastError = 0; - Length = RecvIsoPacket(); - // TODO: Hier nur den Payload zurückgeben - if (Length > 0) { - byte[] Buffer = new byte[Length - TPKT_ISO.Length]; - Array.Copy(PDU, TPKT_ISO.Length, Buffer, 0, Length - TPKT_ISO.Length); - int Size = Length - TPKT_ISO.Length; - if (m_SslActive) - { - // Durch SSL eingelesene Daten an SSL weiterleiten - m_sslconn.ReadCompleted(Buffer, Size); - } else { - // Wenn etwas gelesen werden konnte, Client benachrichtigen - OnDataReceived?.Invoke(Buffer, Size); - } - } - } - } - - public _OnDataReceived OnDataReceived; - public delegate void _OnDataReceived(byte[] PDU, int len); - - #region [Internals] - - // Defaults - private static int ISOTCP = 102; // ISOTCP Port - private static int MinPduSizeToRequest = 240; - private static int MaxPduSizeToRequest = 960; - private static int DefaultTimeout = 2000; - private static int IsoHSize = 7; // TPKT+COTP Header Size - - // Properties - private int _PDULength = 0; - private int _PduSizeRequested = 480; - private int _PLCPort = ISOTCP; - private int _RecvTimeout = DefaultTimeout; - private int _SendTimeout = DefaultTimeout; - private int _ConnTimeout = DefaultTimeout; - - // Privates - private string IPAddress; - private byte LocalTSAP_HI; - private byte LocalTSAP_LO; - private byte[] RemoteTSAP_S; - private byte LastPDUType; - private byte[] PDU = new byte[2048]; - private MsgSocket Socket = null; - private int Time_ms = 0; - - private void CreateSocket() - { - try - { - Socket = new MsgSocket(); - Socket.ConnectTimeout = _ConnTimeout; - Socket.ReadTimeout = _RecvTimeout; - Socket.WriteTimeout = _SendTimeout; - } - catch - { - } - } - - private int TCPConnect() - { - if (_LastError == 0) - try - { - _LastError = Socket.Connect(IPAddress, _PLCPort); - } - catch - { - _LastError = S7Consts.errTCPConnectionFailed; - } - return _LastError; - } - - private void RecvPacket(byte[] Buffer, int Start, int Size) - { - if (Connected) - _LastError = Socket.Receive(Buffer, Start, Size); - else - _LastError = S7Consts.errTCPNotConnected; - } - - private void SendPacket(byte[] Buffer, int Len) - { - _LastError = Socket.Send(Buffer, Len); - } - - private void SendPacket(byte[] Buffer) - { - if (Connected) - SendPacket(Buffer, Buffer.Length); - else - _LastError = S7Consts.errTCPNotConnected; - } - - public void Send(byte[] Buffer) - { - if (m_SslActive) - { - m_sslconn.Write(Buffer, Buffer.Length); - } - else - { - SendIsoPacket(Buffer); - } - } - - private int SendIsoPacket(byte[] Buffer) - { - // Packt die zu sendenden Daten in den Iso-Header ein. - int Size = Buffer.Length; - _LastError = 0; - - Array.Copy(TPKT_ISO, 0, PDU, 0, TPKT_ISO.Length); - SetWordAt(PDU, 2, (ushort)(Size + TPKT_ISO.Length)); - try - { - Array.Copy(Buffer, 0, PDU, TPKT_ISO.Length, Size); - } - catch - { - return S7Consts.errIsoInvalidPDU; - } - SendPacket(PDU, TPKT_ISO.Length + Size); - - return _LastError; - } - - private UInt16 GetWordAt(byte[] Buffer, int Pos) - { - return (UInt16)((Buffer[Pos] << 8) | Buffer[Pos + 1]); - } - - private void SetWordAt(byte[] Buffer, int Pos, UInt16 Value) - { - Buffer[Pos] = (byte)(Value >> 8); - Buffer[Pos + 1] = (byte)(Value & 0x00FF); - } - - private int RecvIsoPacket() - { - Boolean Done = false; - int Size = 0; - while ((_LastError == 0) && !Done) - { - // Get TPKT (4 bytes) - RecvPacket(PDU, 0, 4); - if (_LastError == 0) - { - Size = GetWordAt(PDU, 2); - // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) - if (Size == IsoHSize) - RecvPacket(PDU, 4, 3); // Skip remaining 3 bytes and Done is still false - else - { - // TODO: Größe korrekt prüfen - //if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) - // _LastError = S7Consts.errIsoInvalidPDU; - //else - Done = true; // a valid Length !=7 && >16 && <247 - } - } - } - if (_LastError == 0) - { - RecvPacket(PDU, 4, 3); // Skip remaining 3 COTP bytes - LastPDUType = PDU[5]; // Stores PDU Type, we need it - // Receives the S7 Payload - RecvPacket(PDU, 7, Size - IsoHSize); - } - if (_LastError == 0) - return Size; - else - return 0; - } - - private int ISOConnect() - { - int Size; - byte[] isocon = new byte[ISO_CR.Length + RemoteTSAP_S.Length]; - ISO_CR[16] = LocalTSAP_HI; - ISO_CR[17] = LocalTSAP_LO; - - ISO_CR[3] = (byte)(20 + RemoteTSAP_S.Length); - ISO_CR[4] = (byte)(15 + RemoteTSAP_S.Length); - ISO_CR[19] = (byte)RemoteTSAP_S.Length; - - Array.Copy(ISO_CR, isocon, 20); - Array.Copy(RemoteTSAP_S, 0, isocon, 20, RemoteTSAP_S.Length); - - // Sends the connection request telegram - SendPacket(isocon); - if (_LastError == 0) - { - // Gets the reply (if any) - Size = RecvIsoPacket(); - if (_LastError == 0) - { - if (Size == 36) - { - if (LastPDUType != (byte)0xD0) // 0xD0 = CC Connection confirm - _LastError = S7Consts.errIsoConnect; - } - else - _LastError = S7Consts.errIsoInvalidPDU; - } - } - return _LastError; - } - - public byte[] getOMSExporterSecret() - { - if (m_sslconn == null) return null; - return m_sslconn.getOMSExporterSecret(); - } - - #endregion - - #region [Class Control] - - public S7Client() - { - m_DateTimeStarted = DateTime.Now; - CreateSocket(); - } - - ~S7Client() - { - Disconnect(); - } - - public int Connect() - { - _LastError = 0; - Time_ms = 0; - int Elapsed = Environment.TickCount; - if (!Connected) - { - TCPConnect(); // First stage : TCP Connection - if (_LastError == 0) - { - ISOConnect(); // Second stage : ISOTCP (ISO 8073) Connection - if (_LastError == 0) - { - // _LastError = S7P_InitSSLRequest(); // Third stage : Init SSL Request - StartThread(); - } - } - } - if (_LastError != 0) - Disconnect(); - else - Time_ms = Environment.TickCount - Elapsed; - - return _LastError; - } - - public int SetConnectionParams(string Address, ushort LocalTSAP, byte[] RemoteTSAP) - { - int LocTSAP = LocalTSAP & 0x0000FFFF; - IPAddress = Address; - LocalTSAP_HI = (byte)(LocTSAP >> 8); - LocalTSAP_LO = (byte)(LocTSAP & 0x00FF); - - RemoteTSAP_S = new byte[RemoteTSAP.Length]; - Array.Copy(RemoteTSAP, RemoteTSAP_S, RemoteTSAP.Length); - - return 0; - } - - public int Disconnect() - { - m_runThread_DoStop = true; - m_runThread?.Join(); - - Socket.Close(); - - return 0; - } - - public int GetParam(Int32 ParamNumber, ref int Value) - { - int Result = 0; - switch (ParamNumber) - { - case S7Consts.p_u16_RemotePort: - { - Value = PLCPort; - break; - } - case S7Consts.p_i32_PingTimeout: - { - Value = ConnTimeout; - break; - } - case S7Consts.p_i32_SendTimeout: - { - Value = SendTimeout; - break; - } - case S7Consts.p_i32_RecvTimeout: - { - Value = RecvTimeout; - break; - } - case S7Consts.p_i32_PDURequest: - { - Value = PduSizeRequested; - break; - } - default: - { - Result = S7Consts.errCliInvalidParamNumber; - break; - } - } - return Result; - } - - // Set Properties for compatibility with Snap7.net.cs - public int SetParam(Int32 ParamNumber, ref int Value) - { - int Result = 0; - switch (ParamNumber) - { - case S7Consts.p_u16_RemotePort: - { - PLCPort = Value; - break; - } - case S7Consts.p_i32_PingTimeout: - { - ConnTimeout = Value; - break; - } - case S7Consts.p_i32_SendTimeout: - { - SendTimeout = Value; - break; - } - case S7Consts.p_i32_RecvTimeout: - { - RecvTimeout = Value; - break; - } - case S7Consts.p_i32_PDURequest: - { - PduSizeRequested = Value; - break; - } - default: - { - Result = S7Consts.errCliInvalidParamNumber; - break; - } - } - return Result; - } - - #endregion - - #region [Info Functions / Properties] - - public static string ErrorText(int Error) - { - switch (Error) - { - case 0: return "OK"; - case S7Consts.errTCPSocketCreation: return "SYS : Error creating the Socket"; - case S7Consts.errTCPConnectionTimeout: return "TCP : Connection Timeout"; - case S7Consts.errTCPConnectionFailed: return "TCP : Connection Error"; - case S7Consts.errTCPReceiveTimeout: return "TCP : Data receive Timeout"; - case S7Consts.errTCPDataReceive: return "TCP : Error receiving Data"; - case S7Consts.errTCPSendTimeout: return "TCP : Data send Timeout"; - case S7Consts.errTCPDataSend: return "TCP : Error sending Data"; - case S7Consts.errTCPConnectionReset: return "TCP : Connection reset by the Peer"; - case S7Consts.errTCPNotConnected: return "CLI : Client not connected"; - case S7Consts.errTCPUnreachableHost: return "TCP : Unreachable host"; - case S7Consts.errIsoConnect: return "ISO : Connection Error"; - case S7Consts.errIsoInvalidPDU: return "ISO : Invalid PDU received"; - case S7Consts.errIsoInvalidDataSize: return "ISO : Invalid Buffer passed to Send/Receive"; - case S7Consts.errCliNegotiatingPDU: return "CLI : Error in PDU negotiation"; - case S7Consts.errCliInvalidParams: return "CLI : invalid param(s) supplied"; - case S7Consts.errCliJobPending: return "CLI : Job pending"; - case S7Consts.errCliTooManyItems: return "CLI : too may items (>20) in multi read/write"; - case S7Consts.errCliInvalidWordLen: return "CLI : invalid WordLength"; - case S7Consts.errCliPartialDataWritten: return "CLI : Partial data written"; - case S7Consts.errCliSizeOverPDU: return "CPU : total data exceeds the PDU size"; - case S7Consts.errCliInvalidPlcAnswer: return "CLI : invalid CPU answer"; - case S7Consts.errCliAddressOutOfRange: return "CPU : Address out of range"; - case S7Consts.errCliInvalidTransportSize: return "CPU : Invalid Transport size"; - case S7Consts.errCliWriteDataSizeMismatch: return "CPU : Data size mismatch"; - case S7Consts.errCliItemNotAvailable: return "CPU : Item not available"; - case S7Consts.errCliInvalidValue: return "CPU : Invalid value supplied"; - case S7Consts.errCliCannotStartPLC: return "CPU : Cannot start PLC"; - case S7Consts.errCliAlreadyRun: return "CPU : PLC already RUN"; - case S7Consts.errCliCannotStopPLC: return "CPU : Cannot stop PLC"; - case S7Consts.errCliCannotCopyRamToRom: return "CPU : Cannot copy RAM to ROM"; - case S7Consts.errCliCannotCompress: return "CPU : Cannot compress"; - case S7Consts.errCliAlreadyStop: return "CPU : PLC already STOP"; - case S7Consts.errCliFunNotAvailable: return "CPU : Function not available"; - case S7Consts.errCliUploadSequenceFailed: return "CPU : Upload sequence failed"; - case S7Consts.errCliInvalidDataSizeRecvd: return "CLI : Invalid data size received"; - case S7Consts.errCliInvalidBlockType: return "CLI : Invalid block type"; - case S7Consts.errCliInvalidBlockNumber: return "CLI : Invalid block number"; - case S7Consts.errCliInvalidBlockSize: return "CLI : Invalid block size"; - case S7Consts.errCliNeedPassword: return "CPU : Function not authorized for current protection level"; - case S7Consts.errCliInvalidPassword: return "CPU : Invalid password"; - case S7Consts.errCliAccessDenied: return "CPU : Access denied"; - case S7Consts.errCliNoPasswordToSetOrClear: return "CPU : No password to set or clear"; - case S7Consts.errCliJobTimeout: return "CLI : Job Timeout"; - case S7Consts.errCliFunctionRefused: return "CLI : function refused by CPU (Unknown error)"; - case S7Consts.errCliPartialDataRead: return "CLI : Partial data read"; - case S7Consts.errCliBufferTooSmall: return "CLI : The buffer supplied is too small to accomplish the operation"; - case S7Consts.errCliDestroying: return "CLI : Cannot perform (destroying)"; - case S7Consts.errCliInvalidParamNumber: return "CLI : Invalid Param Number"; - case S7Consts.errCliCannotChangeParam: return "CLI : Cannot change this param now"; - case S7Consts.errCliFunctionNotImplemented: return "CLI : Function not implemented"; - case S7Consts.errCliFirmwareNotSupported: return "CLI : Firmware not supported"; - case S7Consts.errCliDeviceNotSupported: return "CLI : Device type not supported"; - default: return "CLI : Unknown error (0x" + Convert.ToString(Error, 16) + ")"; - }; - } - - public int LastError() - { - return _LastError; - } - - public int RequestedPduLength() - { - return _PduSizeRequested; - } - - public int NegotiatedPduLength() - { - return _PDULength; - } - - public int ExecTime() - { - return Time_ms; - } - - public int ExecutionTime - { - get - { - return Time_ms; - } - } - - public int PduSizeNegotiated - { - get - { - return _PDULength; - } - } - - public int PduSizeRequested - { - get - { - return _PduSizeRequested; - } - set - { - if (value < MinPduSizeToRequest) - value = MinPduSizeToRequest; - if (value > MaxPduSizeToRequest) - value = MaxPduSizeToRequest; - _PduSizeRequested = value; - } - } - - public int PLCPort - { - get - { - return _PLCPort; - } - set - { - _PLCPort = value; - } - } - - public int ConnTimeout - { - get - { - return _ConnTimeout; - } - set - { - _ConnTimeout = value; - } - } - - public int RecvTimeout - { - get - { - return _RecvTimeout; - } - set - { - _RecvTimeout = value; - } - } - - public int SendTimeout - { - get - { - return _SendTimeout; - } - set - { - _SendTimeout = value; - } - } - - public bool Connected - { - get - { - return (Socket != null) && (Socket.Connected); - } - } - #endregion - } -} + } + + m_SslActive = true; + } + catch + { + return S7Consts.errOpenSSL; + } + return 0; + } + + // Deaktiviert TLS + public void SslDeactivate() + { + m_SslActive = false; + // TODO: Ist hier etwas zu OpenSSL-Ressourcen explizit freizugeben? + } + #endregion + + private void StartThread() + { + m_runThread_DoStop = false; + m_runThread = new Thread(RunThread); + m_runThread.Start(); + } + + // Der Task der kontinuierlich ausgeführt wird + private void RunThread() + { + int Length; + while (!m_runThread_DoStop) + { + // Versuchen zu lesen + _LastError = 0; + Length = RecvIsoPacket(); + // TODO: Hier nur den Payload zurückgeben + if (Length > 0) { + byte[] Buffer = new byte[Length - TPKT_ISO.Length]; + Array.Copy(PDU, TPKT_ISO.Length, Buffer, 0, Length - TPKT_ISO.Length); + int Size = Length - TPKT_ISO.Length; + if (m_SslActive) + { + // Durch SSL eingelesene Daten an SSL weiterleiten + m_sslconn.ReadCompleted(Buffer, Size); + } else { + // Wenn etwas gelesen werden konnte, Client benachrichtigen + OnDataReceived?.Invoke(Buffer, Size); + } + } + } + } + + public _OnDataReceived OnDataReceived; + public delegate void _OnDataReceived(byte[] PDU, int len); + + #region [Internals] + + // Defaults + private static int ISOTCP = 102; // ISOTCP Port + private static int MinPduSizeToRequest = 240; + private static int MaxPduSizeToRequest = 960; + private static int DefaultTimeout = 2000; + private static int IsoHSize = 7; // TPKT+COTP Header Size + + // Properties + private int _PDULength = 0; + private int _PduSizeRequested = 480; + private int _PLCPort = ISOTCP; + private int _RecvTimeout = DefaultTimeout; + private int _SendTimeout = DefaultTimeout; + private int _ConnTimeout = DefaultTimeout; + + // Privates + private string IPAddress; + private byte LocalTSAP_HI; + private byte LocalTSAP_LO; + private byte[] RemoteTSAP_S; + private byte LastPDUType; + private byte[] PDU = new byte[2048]; + private MsgSocket Socket = null; + private int Time_ms = 0; + + private void CreateSocket() + { + try + { + Socket = new MsgSocket(); + Socket.ConnectTimeout = _ConnTimeout; + Socket.ReadTimeout = _RecvTimeout; + Socket.WriteTimeout = _SendTimeout; + } + catch + { + } + } + + private int TCPConnect() + { + if (_LastError == 0) + try + { + _LastError = Socket.Connect(IPAddress, _PLCPort); + } + catch + { + _LastError = S7Consts.errTCPConnectionFailed; + } + return _LastError; + } + + private void RecvPacket(byte[] Buffer, int Start, int Size) + { + if (Connected) + _LastError = Socket.Receive(Buffer, Start, Size); + else + _LastError = S7Consts.errTCPNotConnected; + } + + // Sends a fragment of data with a TPKT+COTP header. If isLast=true, the End of Transmission flag is set in the COTP header. + private void SendIsoFragment(byte[] buffer, bool isLast) + { + int size = buffer.Length; + // TPKT (4 bytes) + COTP (3 bytes) = 7 bytes header + byte[] frame = new byte[size + 7]; + + // --- TPKT Header --- + frame[0] = 0x03; // Version + frame[1] = 0x00; // Reserved + ushort totalLen = (ushort)(size + 7); + frame[2] = (byte)(totalLen >> 8); + frame[3] = (byte)(totalLen & 0xFF); + + // --- COTP Header (DT Data) --- + frame[4] = 0x02; // Length + frame[5] = 0xf0; // PDU Type (Data) + // 0x80 = End of transmission (Last Unit), 0x00 = More fragments coming + frame[6] = (byte)(isLast ? 0x80 : 0x00); + + Array.Copy(buffer, 0, frame, 7, size); + + // Use your existing SendPacket method to push to the MsgSocket + SendPacket(frame, frame.Length); + } + + private void SendPacket(byte[] Buffer, int Len) + { + _LastError = Socket.Send(Buffer, Len); + } + + private void SendPacket(byte[] Buffer) + { + if (Connected) + SendPacket(Buffer, Buffer.Length); + else + _LastError = S7Consts.errTCPNotConnected; + } + + public void Send(byte[] Buffer) + { + if (m_SslActive) + { + m_sslconn.Write(Buffer, Buffer.Length); + } + else + { + SendIsoPacket(Buffer); + } + } + + private int SendIsoPacket(byte[] Buffer) + { + // Packt die zu sendenden Daten in den Iso-Header ein. + int Size = Buffer.Length; + _LastError = 0; + + Array.Copy(TPKT_ISO, 0, PDU, 0, TPKT_ISO.Length); + SetWordAt(PDU, 2, (ushort)(Size + TPKT_ISO.Length)); + try + { + Array.Copy(Buffer, 0, PDU, TPKT_ISO.Length, Size); + } + catch + { + return S7Consts.errIsoInvalidPDU; + } + SendPacket(PDU, TPKT_ISO.Length + Size); + + return _LastError; + } + + private UInt16 GetWordAt(byte[] Buffer, int Pos) + { + return (UInt16)((Buffer[Pos] << 8) | Buffer[Pos + 1]); + } + + private void SetWordAt(byte[] Buffer, int Pos, UInt16 Value) + { + Buffer[Pos] = (byte)(Value >> 8); + Buffer[Pos + 1] = (byte)(Value & 0x00FF); + } + + private int RecvIsoPacket() + { + Boolean Done = false; + int Size = 0; + while ((_LastError == 0) && !Done) + { + // Get TPKT (4 bytes) + RecvPacket(PDU, 0, 4); + if (_LastError == 0) + { + Size = GetWordAt(PDU, 2); + // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) + if (Size == IsoHSize) + RecvPacket(PDU, 4, 3); // Skip remaining 3 bytes and Done is still false + else + { + // TODO: Größe korrekt prüfen + //if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) + // _LastError = S7Consts.errIsoInvalidPDU; + //else + Done = true; // a valid Length !=7 && >16 && <247 + } + } + } + if (_LastError == 0) + { + RecvPacket(PDU, 4, 3); // Skip remaining 3 COTP bytes + LastPDUType = PDU[5]; // Stores PDU Type, we need it + // Receives the S7 Payload + RecvPacket(PDU, 7, Size - IsoHSize); + } + if (_LastError == 0) + return Size; + else + return 0; + } + + private int ISOConnect() + { + int Size; + byte[] isocon = new byte[ISO_CR.Length + RemoteTSAP_S.Length]; + ISO_CR[16] = LocalTSAP_HI; + ISO_CR[17] = LocalTSAP_LO; + + ISO_CR[3] = (byte)(20 + RemoteTSAP_S.Length); + ISO_CR[4] = (byte)(15 + RemoteTSAP_S.Length); + ISO_CR[19] = (byte)RemoteTSAP_S.Length; + + Array.Copy(ISO_CR, isocon, 20); + Array.Copy(RemoteTSAP_S, 0, isocon, 20, RemoteTSAP_S.Length); + + // Sends the connection request telegram + SendPacket(isocon); + if (_LastError == 0) + { + // Gets the reply (if any) + Size = RecvIsoPacket(); + if (_LastError == 0) + { + if (Size == 36) + { + if (LastPDUType != (byte)0xD0) // 0xD0 = CC Connection confirm + _LastError = S7Consts.errIsoConnect; + } + else + _LastError = S7Consts.errIsoInvalidPDU; + } + } + return _LastError; + } + + public byte[] getOMSExporterSecret() + { + if (m_sslconn == null) return null; + return m_sslconn.getOMSExporterSecret(); + } + + #endregion + + #region [Class Control] + + public S7Client() + { + m_DateTimeStarted = DateTime.Now; + CreateSocket(); + } + + ~S7Client() + { + Disconnect(); + } + + public int Connect() + { + _LastError = 0; + Time_ms = 0; + int Elapsed = Environment.TickCount; + if (!Connected) + { + TCPConnect(); // First stage : TCP Connection + if (_LastError == 0) + { + ISOConnect(); // Second stage : ISOTCP (ISO 8073) Connection + if (_LastError == 0) + { + // _LastError = S7P_InitSSLRequest(); // Third stage : Init SSL Request + StartThread(); + } + } + } + if (_LastError != 0) + Disconnect(); + else + Time_ms = Environment.TickCount - Elapsed; + + return _LastError; + } + + public int SetConnectionParams(string Address, ushort LocalTSAP, byte[] RemoteTSAP) + { + int LocTSAP = LocalTSAP & 0x0000FFFF; + IPAddress = Address; + LocalTSAP_HI = (byte)(LocTSAP >> 8); + LocalTSAP_LO = (byte)(LocTSAP & 0x00FF); + + RemoteTSAP_S = new byte[RemoteTSAP.Length]; + Array.Copy(RemoteTSAP, RemoteTSAP_S, RemoteTSAP.Length); + + return 0; + } + + public int Disconnect() + { + m_runThread_DoStop = true; + m_runThread?.Join(); + + Socket.Close(); + + return 0; + } + + public int GetParam(Int32 ParamNumber, ref int Value) + { + int Result = 0; + switch (ParamNumber) + { + case S7Consts.p_u16_RemotePort: + { + Value = PLCPort; + break; + } + case S7Consts.p_i32_PingTimeout: + { + Value = ConnTimeout; + break; + } + case S7Consts.p_i32_SendTimeout: + { + Value = SendTimeout; + break; + } + case S7Consts.p_i32_RecvTimeout: + { + Value = RecvTimeout; + break; + } + case S7Consts.p_i32_PDURequest: + { + Value = PduSizeRequested; + break; + } + default: + { + Result = S7Consts.errCliInvalidParamNumber; + break; + } + } + return Result; + } + + // Set Properties for compatibility with Snap7.net.cs + public int SetParam(Int32 ParamNumber, ref int Value) + { + int Result = 0; + switch (ParamNumber) + { + case S7Consts.p_u16_RemotePort: + { + PLCPort = Value; + break; + } + case S7Consts.p_i32_PingTimeout: + { + ConnTimeout = Value; + break; + } + case S7Consts.p_i32_SendTimeout: + { + SendTimeout = Value; + break; + } + case S7Consts.p_i32_RecvTimeout: + { + RecvTimeout = Value; + break; + } + case S7Consts.p_i32_PDURequest: + { + PduSizeRequested = Value; + break; + } + default: + { + Result = S7Consts.errCliInvalidParamNumber; + break; + } + } + return Result; + } + + #endregion + + #region [Info Functions / Properties] + + public static string ErrorText(int Error) + { + switch (Error) + { + case 0: return "OK"; + case S7Consts.errTCPSocketCreation: return "SYS : Error creating the Socket"; + case S7Consts.errTCPConnectionTimeout: return "TCP : Connection Timeout"; + case S7Consts.errTCPConnectionFailed: return "TCP : Connection Error"; + case S7Consts.errTCPReceiveTimeout: return "TCP : Data receive Timeout"; + case S7Consts.errTCPDataReceive: return "TCP : Error receiving Data"; + case S7Consts.errTCPSendTimeout: return "TCP : Data send Timeout"; + case S7Consts.errTCPDataSend: return "TCP : Error sending Data"; + case S7Consts.errTCPConnectionReset: return "TCP : Connection reset by the Peer"; + case S7Consts.errTCPNotConnected: return "CLI : Client not connected"; + case S7Consts.errTCPUnreachableHost: return "TCP : Unreachable host"; + case S7Consts.errIsoConnect: return "ISO : Connection Error"; + case S7Consts.errIsoInvalidPDU: return "ISO : Invalid PDU received"; + case S7Consts.errIsoInvalidDataSize: return "ISO : Invalid Buffer passed to Send/Receive"; + case S7Consts.errCliNegotiatingPDU: return "CLI : Error in PDU negotiation"; + case S7Consts.errCliInvalidParams: return "CLI : invalid param(s) supplied"; + case S7Consts.errCliJobPending: return "CLI : Job pending"; + case S7Consts.errCliTooManyItems: return "CLI : too may items (>20) in multi read/write"; + case S7Consts.errCliInvalidWordLen: return "CLI : invalid WordLength"; + case S7Consts.errCliPartialDataWritten: return "CLI : Partial data written"; + case S7Consts.errCliSizeOverPDU: return "CPU : total data exceeds the PDU size"; + case S7Consts.errCliInvalidPlcAnswer: return "CLI : invalid CPU answer"; + case S7Consts.errCliAddressOutOfRange: return "CPU : Address out of range"; + case S7Consts.errCliInvalidTransportSize: return "CPU : Invalid Transport size"; + case S7Consts.errCliWriteDataSizeMismatch: return "CPU : Data size mismatch"; + case S7Consts.errCliItemNotAvailable: return "CPU : Item not available"; + case S7Consts.errCliInvalidValue: return "CPU : Invalid value supplied"; + case S7Consts.errCliCannotStartPLC: return "CPU : Cannot start PLC"; + case S7Consts.errCliAlreadyRun: return "CPU : PLC already RUN"; + case S7Consts.errCliCannotStopPLC: return "CPU : Cannot stop PLC"; + case S7Consts.errCliCannotCopyRamToRom: return "CPU : Cannot copy RAM to ROM"; + case S7Consts.errCliCannotCompress: return "CPU : Cannot compress"; + case S7Consts.errCliAlreadyStop: return "CPU : PLC already STOP"; + case S7Consts.errCliFunNotAvailable: return "CPU : Function not available"; + case S7Consts.errCliUploadSequenceFailed: return "CPU : Upload sequence failed"; + case S7Consts.errCliInvalidDataSizeRecvd: return "CLI : Invalid data size received"; + case S7Consts.errCliInvalidBlockType: return "CLI : Invalid block type"; + case S7Consts.errCliInvalidBlockNumber: return "CLI : Invalid block number"; + case S7Consts.errCliInvalidBlockSize: return "CLI : Invalid block size"; + case S7Consts.errCliNeedPassword: return "CPU : Function not authorized for current protection level"; + case S7Consts.errCliInvalidPassword: return "CPU : Invalid password"; + case S7Consts.errCliAccessDenied: return "CPU : Access denied"; + case S7Consts.errCliNoPasswordToSetOrClear: return "CPU : No password to set or clear"; + case S7Consts.errCliJobTimeout: return "CLI : Job Timeout"; + case S7Consts.errCliFunctionRefused: return "CLI : function refused by CPU (Unknown error)"; + case S7Consts.errCliPartialDataRead: return "CLI : Partial data read"; + case S7Consts.errCliBufferTooSmall: return "CLI : The buffer supplied is too small to accomplish the operation"; + case S7Consts.errCliDestroying: return "CLI : Cannot perform (destroying)"; + case S7Consts.errCliInvalidParamNumber: return "CLI : Invalid Param Number"; + case S7Consts.errCliCannotChangeParam: return "CLI : Cannot change this param now"; + case S7Consts.errCliFunctionNotImplemented: return "CLI : Function not implemented"; + case S7Consts.errCliFirmwareNotSupported: return "CLI : Firmware not supported"; + case S7Consts.errCliDeviceNotSupported: return "CLI : Device type not supported"; + default: return "CLI : Unknown error (0x" + Convert.ToString(Error, 16) + ")"; + }; + } + + public int LastError() + { + return _LastError; + } + + public int RequestedPduLength() + { + return _PduSizeRequested; + } + + public int NegotiatedPduLength() + { + return _PDULength; + } + + public int ExecTime() + { + return Time_ms; + } + + public int ExecutionTime + { + get + { + return Time_ms; + } + } + + public int PduSizeNegotiated + { + get + { + return _PDULength; + } + } + + public int PduSizeRequested + { + get + { + return _PduSizeRequested; + } + set + { + if (value < MinPduSizeToRequest) + value = MinPduSizeToRequest; + if (value > MaxPduSizeToRequest) + value = MaxPduSizeToRequest; + _PduSizeRequested = value; + } + } + + public int PLCPort + { + get + { + return _PLCPort; + } + set + { + _PLCPort = value; + } + } + + public int ConnTimeout + { + get + { + return _ConnTimeout; + } + set + { + _ConnTimeout = value; + } + } + + public int RecvTimeout + { + get + { + return _RecvTimeout; + } + set + { + _RecvTimeout = value; + } + } + + public int SendTimeout + { + get + { + return _SendTimeout; + } + set + { + _SendTimeout = value; + } + } + + public bool Connected + { + get + { + return (Socket != null) && (Socket.Connected); + } + } + #endregion + } +} diff --git a/src/S7CommPlusDriver/OpenSSL/Native.cs b/src/S7CommPlusDriver/OpenSSL/Native.cs index 3d85084..a689054 100644 --- a/src/S7CommPlusDriver/OpenSSL/Native.cs +++ b/src/S7CommPlusDriver/OpenSSL/Native.cs @@ -1,375 +1,420 @@ -#region License -/****************************************************************************** - * S7CommPlusDriver - * - * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de - * - * This file is part of S7CommPlusDriver. - * - * S7CommPlusDriver is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - /****************************************************************************/ -#endregion - -using System; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; - -namespace OpenSsl -{ - - - public class Native +#region License +/****************************************************************************** + * S7CommPlusDriver + * + * Copyright (C) 2023 Thomas Wiens, th.wiens@gmx.de + * + * This file is part of S7CommPlusDriver. + * + * S7CommPlusDriver is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + /****************************************************************************/ +#endregion + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace OpenSsl +{ + + + public class Native { - const string DLLNAME = "libcrypto-3"; + const string DLLNAME = "libcrypto-3"; const string SSLDLLNAME = "libssl-3"; static Native() { NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver); } + private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) { - if (libraryName == DLLNAME) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.X86) - return NativeLibrary.Load(Path.Combine("runtimes", "win-x86", "native", "libcrypto-3.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.X64) - return NativeLibrary.Load(Path.Combine("runtimes", "win-x64", "native", "libcrypto-3-x64.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return NativeLibrary.Load(Path.Combine("runtimes", "win-arm64", "native", "libcrypto-3-arm64.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return NativeLibrary.Load("libcrypto.3.dylib", assembly, searchPath); - return IntPtr.Zero; - } + Console.WriteLine($"[OpenSSL-Debug] Attempting to resolve: {libraryName}"); + string targetLib = null; - if(libraryName == SSLDLLNAME) + if (libraryName == DLLNAME || libraryName == SSLDLLNAME) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.X86) - return NativeLibrary.Load(Path.Combine("runtimes", "win-x86", "native", "libssl-3.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.X64) - return NativeLibrary.Load(Path.Combine("runtimes", "win-x64", "native", "libssl-3-x64.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return NativeLibrary.Load(Path.Combine("runtimes", "win-arm64", "native", "libssl-3-arm64.dll"), assembly, searchPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return NativeLibrary.Load("libssl.3.dylib", assembly, searchPath); - return IntPtr.Zero; + // 1. Handle Windows + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + targetLib = Path.Combine("runtimes", "win-x86", "native", libraryName == DLLNAME ? "libcrypto-3.dll" : "libssl-3.dll"); + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + targetLib = Path.Combine("runtimes", "win-x64", "native", libraryName == DLLNAME ? "libcrypto-3-x64.dll" : "libssl-3-x64.dll"); + else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + targetLib = Path.Combine("runtimes", "win-arm64", "native", libraryName == DLLNAME ? "libcrypto-3-arm64.dll" : "libssl-3-arm64.dll"); + } + // 2. Handle macOS + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + targetLib = libraryName == DLLNAME ? "libcrypto.3.dylib" : "libssl.3.dylib"; + } + // 3. Handle Linux (Aarch64 / x64) + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // Linux should find this by name globally. + targetLib = libraryName == DLLNAME ? "libcrypto.so.3" : "libssl.so.3"; + } + + if (targetLib != null) + { + try + { + //Console.WriteLine($"[OpenSSL-Debug] Trying NativeLibrary.Load: {targetLib}"); + IntPtr handle = NativeLibrary.Load(targetLib, assembly, searchPath); + //Console.WriteLine($"[OpenSSL-Debug] Successfully loaded: {targetLib} (Handle: {handle})"); + return handle; + } + catch (Exception ex) + { + Console.WriteLine($"[OpenSSL-Debug] Failed primary load ({targetLib}): {ex.Message}"); + + // Fallback attempt for Linux/OSX + try + { + string fallback = libraryName == DLLNAME ? "libcrypto.so" : "libssl.so"; + //Console.WriteLine($"[OpenSSL-Debug] Attempting fallback: {fallback}"); + return NativeLibrary.Load(fallback, assembly, searchPath); + } + catch (Exception ex2) + { + Console.WriteLine($"[OpenSSL-Debug] Fallback failed: {ex2.Message}"); + return IntPtr.Zero; + } + } + } } - // Otherwise, fallback to default import resolver. return IntPtr.Zero; - } - - #region Delegates - - // typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line); - // void SSL_CTX_set_keylog_callback(SSL_CTX* ctx, SSL_CTX_keylog_cb_func cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void SSL_CTX_keylog_cb_func(IntPtr ssl, string line); - - #endregion - - #region OPENSSL - - // int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int OPENSSL_init_ssl(UInt64 opts, IntPtr settings); - - #endregion - - #region SSL - - public const int SSL_NOTHING = 1; - public const int SSL_WRITING = 2; - public const int SSL_READING = 3; - public const int SSL_X509_LOOKUP = 4; - public const int SSL_ASYNC_PAUSED = 5; - public const int SSL_ASYNC_NO_JOBS = 6; - public const int SSL_CLIENT_HELLO_CB = 7; - public const int SSL_RETRY_VERIFY = 8; - - public const int SSL_CTRL_GET_CLIENT_CERT_REQUEST = 9; - public const int SSL_CTRL_GET_NUM_RENEGOTIATIONS = 10; - public const int SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS = 11; - public const int SSL_CTRL_GET_TOTAL_RENEGOTIATIONS = 12; - public const int SSL_CTRL_GET_FLAGS = 13; - public const int SSL_CTRL_EXTRA_CHAIN_CERT = 14; - public const int SSL_CTRL_SET_MSG_CALLBACK = 15; - public const int SSL_CTRL_SET_MSG_CALLBACK_ARG = 16; - public const int SSL_CTRL_SET_MTU = 17; - public const int SSL_CTRL_SESS_NUMBER = 20; - public const int SSL_CTRL_SESS_CONNECT = 21; - public const int SSL_CTRL_SESS_CONNECT_GOOD = 22; - public const int SSL_CTRL_SESS_CONNECT_RENEGOTIATE = 23; - public const int SSL_CTRL_SESS_ACCEPT = 24; - public const int SSL_CTRL_SESS_ACCEPT_GOOD = 25; - public const int SSL_CTRL_SESS_ACCEPT_RENEGOTIATE = 26; - public const int SSL_CTRL_SESS_HIT = 27; - public const int SSL_CTRL_SESS_CB_HIT = 28; - public const int SSL_CTRL_SESS_MISSES = 29; - public const int SSL_CTRL_SESS_TIMEOUTS = 30; - public const int SSL_CTRL_SESS_CACHE_FULL = 31; - public const int SSL_CTRL_MODE = 33; - public const int SSL_CTRL_GET_READ_AHEAD = 40; - public const int SSL_CTRL_SET_READ_AHEAD = 41; - public const int SSL_CTRL_SET_SESS_CACHE_SIZE = 42; - public const int SSL_CTRL_GET_SESS_CACHE_SIZE = 43; - public const int SSL_CTRL_SET_SESS_CACHE_MODE = 44; - public const int SSL_CTRL_GET_SESS_CACHE_MODE = 45; - public const int SSL_CTRL_GET_MAX_CERT_LIST = 50; - public const int SSL_CTRL_SET_MAX_CERT_LIST = 51; - public const int SSL_CTRL_SET_MAX_SEND_FRAGMENT = 52; - public const int SSL_CTRL_SET_TLSEXT_SERVERNAME_CB = 53; - public const int SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG = 54; - public const int SSL_CTRL_SET_TLSEXT_HOSTNAME = 55; - public const int SSL_CTRL_SET_TLSEXT_DEBUG_CB = 56; - public const int SSL_CTRL_SET_TLSEXT_DEBUG_ARG = 57; - public const int SSL_CTRL_GET_TLSEXT_TICKET_KEYS = 58; - public const int SSL_CTRL_SET_TLSEXT_TICKET_KEYS = 59; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB = 63; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB_ARG = 64; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE = 65; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_EXTS = 66; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_EXTS = 67; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_IDS = 68; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_IDS = 69; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP = 70; - public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP = 71; - public const int SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB = 75; - public const int SSL_CTRL_SET_SRP_VERIFY_PARAM_CB = 76; - public const int SSL_CTRL_SET_SRP_GIVE_CLIENT_PWD_CB = 77; - public const int SSL_CTRL_SET_SRP_ARG = 78; - public const int SSL_CTRL_SET_TLS_EXT_SRP_USERNAME = 79; - public const int SSL_CTRL_SET_TLS_EXT_SRP_STRENGTH = 80; - public const int SSL_CTRL_SET_TLS_EXT_SRP_PASSWORD = 81; - public const int DTLS_CTRL_GET_TIMEOUT = 73; - public const int DTLS_CTRL_HANDLE_TIMEOUT = 74; - public const int SSL_CTRL_GET_RI_SUPPORT = 76; - public const int SSL_CTRL_CLEAR_MODE = 78; - public const int SSL_CTRL_SET_NOT_RESUMABLE_SESS_CB = 79; - public const int SSL_CTRL_GET_EXTRA_CHAIN_CERTS = 82; - public const int SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS = 83; - public const int SSL_CTRL_CHAIN = 88; - public const int SSL_CTRL_CHAIN_CERT = 89; - public const int SSL_CTRL_GET_GROUPS = 90; - public const int SSL_CTRL_SET_GROUPS = 91; - public const int SSL_CTRL_SET_GROUPS_LIST = 92; - public const int SSL_CTRL_GET_SHARED_GROUP = 93; - public const int SSL_CTRL_SET_SIGALGS = 97; - public const int SSL_CTRL_SET_SIGALGS_LIST = 98; - public const int SSL_CTRL_CERT_FLAGS = 99; - public const int SSL_CTRL_CLEAR_CERT_FLAGS = 100; - public const int SSL_CTRL_SET_CLIENT_SIGALGS = 101; - public const int SSL_CTRL_SET_CLIENT_SIGALGS_LIST = 102; - public const int SSL_CTRL_GET_CLIENT_CERT_TYPES = 103; - public const int SSL_CTRL_SET_CLIENT_CERT_TYPES = 104; - public const int SSL_CTRL_BUILD_CERT_CHAIN = 105; - public const int SSL_CTRL_SET_VERIFY_CERT_STORE = 106; - public const int SSL_CTRL_SET_CHAIN_CERT_STORE = 107; - public const int SSL_CTRL_GET_PEER_SIGNATURE_NID = 108; - public const int SSL_CTRL_GET_PEER_TMP_KEY = 109; - public const int SSL_CTRL_GET_RAW_CIPHERLIST = 110; - public const int SSL_CTRL_GET_EC_POINT_FORMATS = 111; - public const int SSL_CTRL_GET_CHAIN_CERTS = 115; - public const int SSL_CTRL_SELECT_CURRENT_CERT = 116; - public const int SSL_CTRL_SET_CURRENT_CERT = 117; - public const int SSL_CTRL_SET_DH_AUTO = 118; - public const int DTLS_CTRL_SET_LINK_MTU = 120; - public const int DTLS_CTRL_GET_LINK_MIN_MTU = 121; - public const int SSL_CTRL_GET_EXTMS_SUPPORT = 122; - public const int SSL_CTRL_SET_MIN_PROTO_VERSION = 123; - public const int SSL_CTRL_SET_MAX_PROTO_VERSION = 124; - public const int SSL_CTRL_SET_SPLIT_SEND_FRAGMENT = 125; - public const int SSL_CTRL_SET_MAX_PIPELINES = 126; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE = 127; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB = 128; - public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB_ARG = 129; - public const int SSL_CTRL_GET_MIN_PROTO_VERSION = 130; - public const int SSL_CTRL_GET_MAX_PROTO_VERSION = 131; - public const int SSL_CTRL_GET_SIGNATURE_NID = 132; - public const int SSL_CTRL_GET_TMP_KEY = 133; - public const int SSL_CTRL_GET_NEGOTIATED_GROUP = 134; - public const int SSL_CTRL_SET_RETRY_VERIFY = 136; - public const int SSL_CTRL_GET_VERIFY_CERT_STORE = 137; - public const int SSL_CTRL_GET_CHAIN_CERT_STORE = 138; - - // SSL/TLS related defines useful to providers - // from: prov_ssl.h - public const int TLS1_VERSION = 0x0301; - public const int TLS1_1_VERSION = 0x0302; - public const int TLS1_2_VERSION = 0x0303; - public const int TLS1_3_VERSION = 0x0304; - - // int SSL_connect(SSL *ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_connect(IntPtr ssl); - - // void SSL_free(SSL *ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_free(IntPtr ssl); - - // int SSL_get_error(const SSL *ssl, int ret); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_get_error(IntPtr ssl, int ret_code); - - // int SSL_in_init(const SSL *s); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_in_init(IntPtr ssl); - - // SSL *SSL_new(SSL_CTX *ctx); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr SSL_new(IntPtr ctx); - - // int SSL_read(SSL *ssl, void *buf, int num); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_read(IntPtr ssl, byte[] buf, int len); - - // void SSL_set_accept_state(SSL *ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_set_accept_state(IntPtr ssl); - - // void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_set_bio(IntPtr ssl, IntPtr read_bio, IntPtr write_bio); - - // void SSL_set_connect_state(SSL *ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_set_connect_state(IntPtr ssl); - - // const char *SSL_state_string_long(const SSL *ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr SSL_state_string_long(IntPtr ssl); - - // int SSL_want(const SSL* ssl); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_want(IntPtr ssl); - - // int SSL_write(SSL *ssl, const void *buf, int num); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_write(IntPtr ssl, byte[] buf, int len); - - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr TLS_client_method(); - - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr SSL_export_keying_material(IntPtr ssl, byte[] outKeyMaterial, nint outKeyMaterialLength, char[] label, nint labelLength, IntPtr context, nint contextLength, int useContext); - - #endregion - - #region SSL_CTX - - // long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern long SSL_CTX_ctrl(IntPtr ctx, int cmd, long arg, IntPtr parg); - - // void SSL_CTX_free(SSL_CTX *ctx); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_CTX_free(IntPtr ctx); - - // SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr SSL_CTX_new(IntPtr sslMethod); - - // int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int SSL_CTX_set_ciphersuites(IntPtr ctx, string str); - - // typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line); - // void SSL_CTX_set_keylog_callback(SSL_CTX* ctx, SSL_CTX_keylog_cb_func cb); - [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void SSL_CTX_set_keylog_callback(IntPtr ctx, SSL_CTX_keylog_cb_func cb); - - #endregion - - #region BIO - - // long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern long BIO_ctrl(IntPtr bp, int cmd, long larg, IntPtr parg); - - // size_t BIO_ctrl_pending(BIO *b); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern nint BIO_ctrl_pending(IntPtr bio); - - // void BIO_free_all(BIO *a); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern void BIO_free_all(IntPtr bio); - - // int BIO_free(BIO *a); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int BIO_free(IntPtr bio); - - // BIO * BIO_new(const BIO_METHOD *type); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr BIO_new(IntPtr type); - - // int BIO_read(BIO *b, void *buf, int len); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int BIO_read(IntPtr b, byte[] buf, int len); - - // BIO_METHOD * BIO_s_mem(void); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr BIO_s_mem(); - - // int BIO_test_flags(const BIO *b, int flags); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int BIO_test_flags(IntPtr b, int flags); - - // int BIO_should_retry(BIO *b) -> define in bio.h: BIO_test_flags(a, BIO_FLAGS_SHOULD_RETRY) - const int BIO_FLAGS_SHOULD_RETRY = 0x08; - public static int BIO_should_retry(IntPtr b) - { - return Native.BIO_test_flags(b, BIO_FLAGS_SHOULD_RETRY); - } - - // int BIO_write(BIO *b, const void *data, int dlen); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern int BIO_write(IntPtr b, byte[] data, int dlen); - - // BIO_set_mem_eof_return -> define in bio.h: BIO_ctrl(b,BIO_C_SET_BUF_MEM_EOF_RETURN,v,NULL) - const int BIO_C_SET_BUF_MEM_EOF_RETURN = 130; // return end of input - public static long BIO_set_mem_eof_return(IntPtr b, int v) - { - return Native.BIO_ctrl(b, BIO_C_SET_BUF_MEM_EOF_RETURN, v, IntPtr.Zero); - } - #endregion - - #region ERR - public const int SSL_ERROR_NONE = 0; - public const int SSL_ERROR_SSL = 1; - public const int SSL_ERROR_WANT_READ = 2; - public const int SSL_ERROR_WANT_WRITE = 3; - public const int SSL_ERROR_WANT_X509_LOOKUP = 4; - public const int SSL_ERROR_SYSCALL = 5; - public const int SSL_ERROR_ZERO_RETURN = 6; - public const int SSL_ERROR_WANT_CONNECT = 7; - public const int SSL_ERROR_WANT_ACCEPT = 8; - - // void ERR_error_string_n(unsigned long e, char *buf, size_t len); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - //public static extern void ERR_error_string_n(ulong e, IntPtr buf, size_t len); - public static extern void ERR_error_string_n(ulong e, byte[] buf, nint len); - - // unsigned long ERR_get_error(void); - [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] - public static extern ulong ERR_get_error(); - - #endregion - - #region Utilities - - public static IntPtr ExpectNonNull(IntPtr ptr) - { - if (ptr == IntPtr.Zero) - throw new Exception(); - - return ptr; - } - - #endregion - } -} + } + + #region Delegates + + // typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line); + // void SSL_CTX_set_keylog_callback(SSL_CTX* ctx, SSL_CTX_keylog_cb_func cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void SSL_CTX_keylog_cb_func(IntPtr ssl, string line); + + #endregion + + #region OPENSSL + + // int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int OPENSSL_init_ssl(UInt64 opts, IntPtr settings); + + #endregion + + #region SSL + + public const int SSL_NOTHING = 1; + public const int SSL_WRITING = 2; + public const int SSL_READING = 3; + public const int SSL_X509_LOOKUP = 4; + public const int SSL_ASYNC_PAUSED = 5; + public const int SSL_ASYNC_NO_JOBS = 6; + public const int SSL_CLIENT_HELLO_CB = 7; + public const int SSL_RETRY_VERIFY = 8; + + public const int SSL_CTRL_GET_CLIENT_CERT_REQUEST = 9; + public const int SSL_CTRL_GET_NUM_RENEGOTIATIONS = 10; + public const int SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS = 11; + public const int SSL_CTRL_GET_TOTAL_RENEGOTIATIONS = 12; + public const int SSL_CTRL_GET_FLAGS = 13; + public const int SSL_CTRL_EXTRA_CHAIN_CERT = 14; + public const int SSL_CTRL_SET_MSG_CALLBACK = 15; + public const int SSL_CTRL_SET_MSG_CALLBACK_ARG = 16; + public const int SSL_CTRL_SET_MTU = 17; + public const int SSL_CTRL_SESS_NUMBER = 20; + public const int SSL_CTRL_SESS_CONNECT = 21; + public const int SSL_CTRL_SESS_CONNECT_GOOD = 22; + public const int SSL_CTRL_SESS_CONNECT_RENEGOTIATE = 23; + public const int SSL_CTRL_SESS_ACCEPT = 24; + public const int SSL_CTRL_SESS_ACCEPT_GOOD = 25; + public const int SSL_CTRL_SESS_ACCEPT_RENEGOTIATE = 26; + public const int SSL_CTRL_SESS_HIT = 27; + public const int SSL_CTRL_SESS_CB_HIT = 28; + public const int SSL_CTRL_SESS_MISSES = 29; + public const int SSL_CTRL_SESS_TIMEOUTS = 30; + public const int SSL_CTRL_SESS_CACHE_FULL = 31; + public const int SSL_CTRL_MODE = 33; + public const int SSL_CTRL_GET_READ_AHEAD = 40; + public const int SSL_CTRL_SET_READ_AHEAD = 41; + public const int SSL_CTRL_SET_SESS_CACHE_SIZE = 42; + public const int SSL_CTRL_GET_SESS_CACHE_SIZE = 43; + public const int SSL_CTRL_SET_SESS_CACHE_MODE = 44; + public const int SSL_CTRL_GET_SESS_CACHE_MODE = 45; + public const int SSL_CTRL_GET_MAX_CERT_LIST = 50; + public const int SSL_CTRL_SET_MAX_CERT_LIST = 51; + public const int SSL_CTRL_SET_MAX_SEND_FRAGMENT = 52; + public const int SSL_CTRL_SET_TLSEXT_SERVERNAME_CB = 53; + public const int SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG = 54; + public const int SSL_CTRL_SET_TLSEXT_HOSTNAME = 55; + public const int SSL_CTRL_SET_TLSEXT_DEBUG_CB = 56; + public const int SSL_CTRL_SET_TLSEXT_DEBUG_ARG = 57; + public const int SSL_CTRL_GET_TLSEXT_TICKET_KEYS = 58; + public const int SSL_CTRL_SET_TLSEXT_TICKET_KEYS = 59; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB = 63; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB_ARG = 64; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE = 65; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_EXTS = 66; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_EXTS = 67; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_IDS = 68; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_IDS = 69; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP = 70; + public const int SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP = 71; + public const int SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB = 75; + public const int SSL_CTRL_SET_SRP_VERIFY_PARAM_CB = 76; + public const int SSL_CTRL_SET_SRP_GIVE_CLIENT_PWD_CB = 77; + public const int SSL_CTRL_SET_SRP_ARG = 78; + public const int SSL_CTRL_SET_TLS_EXT_SRP_USERNAME = 79; + public const int SSL_CTRL_SET_TLS_EXT_SRP_STRENGTH = 80; + public const int SSL_CTRL_SET_TLS_EXT_SRP_PASSWORD = 81; + public const int DTLS_CTRL_GET_TIMEOUT = 73; + public const int DTLS_CTRL_HANDLE_TIMEOUT = 74; + public const int SSL_CTRL_GET_RI_SUPPORT = 76; + public const int SSL_CTRL_CLEAR_MODE = 78; + public const int SSL_CTRL_SET_NOT_RESUMABLE_SESS_CB = 79; + public const int SSL_CTRL_GET_EXTRA_CHAIN_CERTS = 82; + public const int SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS = 83; + public const int SSL_CTRL_CHAIN = 88; + public const int SSL_CTRL_CHAIN_CERT = 89; + public const int SSL_CTRL_GET_GROUPS = 90; + public const int SSL_CTRL_SET_GROUPS = 91; + public const int SSL_CTRL_SET_GROUPS_LIST = 92; + public const int SSL_CTRL_GET_SHARED_GROUP = 93; + public const int SSL_CTRL_SET_SIGALGS = 97; + public const int SSL_CTRL_SET_SIGALGS_LIST = 98; + public const int SSL_CTRL_CERT_FLAGS = 99; + public const int SSL_CTRL_CLEAR_CERT_FLAGS = 100; + public const int SSL_CTRL_SET_CLIENT_SIGALGS = 101; + public const int SSL_CTRL_SET_CLIENT_SIGALGS_LIST = 102; + public const int SSL_CTRL_GET_CLIENT_CERT_TYPES = 103; + public const int SSL_CTRL_SET_CLIENT_CERT_TYPES = 104; + public const int SSL_CTRL_BUILD_CERT_CHAIN = 105; + public const int SSL_CTRL_SET_VERIFY_CERT_STORE = 106; + public const int SSL_CTRL_SET_CHAIN_CERT_STORE = 107; + public const int SSL_CTRL_GET_PEER_SIGNATURE_NID = 108; + public const int SSL_CTRL_GET_PEER_TMP_KEY = 109; + public const int SSL_CTRL_GET_RAW_CIPHERLIST = 110; + public const int SSL_CTRL_GET_EC_POINT_FORMATS = 111; + public const int SSL_CTRL_GET_CHAIN_CERTS = 115; + public const int SSL_CTRL_SELECT_CURRENT_CERT = 116; + public const int SSL_CTRL_SET_CURRENT_CERT = 117; + public const int SSL_CTRL_SET_DH_AUTO = 118; + public const int DTLS_CTRL_SET_LINK_MTU = 120; + public const int DTLS_CTRL_GET_LINK_MIN_MTU = 121; + public const int SSL_CTRL_GET_EXTMS_SUPPORT = 122; + public const int SSL_CTRL_SET_MIN_PROTO_VERSION = 123; + public const int SSL_CTRL_SET_MAX_PROTO_VERSION = 124; + public const int SSL_CTRL_SET_SPLIT_SEND_FRAGMENT = 125; + public const int SSL_CTRL_SET_MAX_PIPELINES = 126; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE = 127; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB = 128; + public const int SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB_ARG = 129; + public const int SSL_CTRL_GET_MIN_PROTO_VERSION = 130; + public const int SSL_CTRL_GET_MAX_PROTO_VERSION = 131; + public const int SSL_CTRL_GET_SIGNATURE_NID = 132; + public const int SSL_CTRL_GET_TMP_KEY = 133; + public const int SSL_CTRL_GET_NEGOTIATED_GROUP = 134; + public const int SSL_CTRL_SET_RETRY_VERIFY = 136; + public const int SSL_CTRL_GET_VERIFY_CERT_STORE = 137; + public const int SSL_CTRL_GET_CHAIN_CERT_STORE = 138; + + // SSL/TLS related defines useful to providers + // from: prov_ssl.h + public const int TLS1_VERSION = 0x0301; + public const int TLS1_1_VERSION = 0x0302; + public const int TLS1_2_VERSION = 0x0303; + public const int TLS1_3_VERSION = 0x0304; + + // int SSL_connect(SSL *ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_connect(IntPtr ssl); + + // void SSL_free(SSL *ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_free(IntPtr ssl); + + // int SSL_get_error(const SSL *ssl, int ret); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_get_error(IntPtr ssl, int ret_code); + + // int SSL_in_init(const SSL *s); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_in_init(IntPtr ssl); + + // SSL *SSL_new(SSL_CTX *ctx); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr SSL_new(IntPtr ctx); + + // int SSL_read(SSL *ssl, void *buf, int num); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_read(IntPtr ssl, byte[] buf, int len); + + // void SSL_set_accept_state(SSL *ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_set_accept_state(IntPtr ssl); + + // void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_set_bio(IntPtr ssl, IntPtr read_bio, IntPtr write_bio); + + // void SSL_set_connect_state(SSL *ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_set_connect_state(IntPtr ssl); + + // const char *SSL_state_string_long(const SSL *ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr SSL_state_string_long(IntPtr ssl); + + // int SSL_want(const SSL* ssl); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_want(IntPtr ssl); + + // int SSL_write(SSL *ssl, const void *buf, int num); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_write(IntPtr ssl, byte[] buf, int len); + + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr TLS_client_method(); + + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_export_keying_material + ( + IntPtr ssl, + [Out] byte[] outKeyMaterial, + nint outKeyMaterialLength, + byte[] label, // Changed to byte[] for Linux + nint labelLength, + IntPtr context, + nint contextLength, + int useContext + ); + + #endregion + + #region SSL_CTX + + // long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern long SSL_CTX_ctrl(IntPtr ctx, int cmd, long arg, IntPtr parg); + + // void SSL_CTX_free(SSL_CTX *ctx); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_CTX_free(IntPtr ctx); + + // SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr SSL_CTX_new(IntPtr sslMethod); + + // int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_CTX_set_ciphersuites(IntPtr ctx, string str); + + // typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line); + // void SSL_CTX_set_keylog_callback(SSL_CTX* ctx, SSL_CTX_keylog_cb_func cb); + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void SSL_CTX_set_keylog_callback(IntPtr ctx, SSL_CTX_keylog_cb_func cb); + + #endregion + + #region BIO + //Added for troubleshoot force handshake in SSL_connect + [DllImport(SSLDLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int SSL_do_handshake(IntPtr ssl); + + // long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + // Change return and larg from long to nint + public static extern nint BIO_ctrl(IntPtr bp, int cmd, nint larg, IntPtr parg); + + // size_t BIO_ctrl_pending(BIO *b); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern nint BIO_ctrl_pending(IntPtr bio); + + // void BIO_free_all(BIO *a); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void BIO_free_all(IntPtr bio); + + // int BIO_free(BIO *a); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int BIO_free(IntPtr bio); + + // BIO * BIO_new(const BIO_METHOD *type); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr BIO_new(IntPtr type); + + // int BIO_read(BIO *b, void *buf, int len); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int BIO_read(IntPtr b, byte[] buf, int len); + + // BIO_METHOD * BIO_s_mem(void); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr BIO_s_mem(); + + // int BIO_test_flags(const BIO *b, int flags); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int BIO_test_flags(IntPtr b, int flags); + + // int BIO_should_retry(BIO *b) -> define in bio.h: BIO_test_flags(a, BIO_FLAGS_SHOULD_RETRY) + const int BIO_FLAGS_SHOULD_RETRY = 0x08; + public static int BIO_should_retry(IntPtr b) + { + return Native.BIO_test_flags(b, BIO_FLAGS_SHOULD_RETRY); + } + + // int BIO_write(BIO *b, const void *data, int dlen); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int BIO_write(IntPtr b, byte[] data, int dlen); + + // BIO_set_mem_eof_return -> define in bio.h: BIO_ctrl(b,BIO_C_SET_BUF_MEM_EOF_RETURN,v,NULL) + //Changed Long to nint for v and return type + const int BIO_C_SET_BUF_MEM_EOF_RETURN = 130; // return end of input + public static nint BIO_set_mem_eof_return(IntPtr b, int v) + { + // Use nint for the third parameter + return Native.BIO_ctrl(b, BIO_C_SET_BUF_MEM_EOF_RETURN, (nint)v, IntPtr.Zero); + } + #endregion + + #region ERR + public const int SSL_ERROR_NONE = 0; + public const int SSL_ERROR_SSL = 1; + public const int SSL_ERROR_WANT_READ = 2; + public const int SSL_ERROR_WANT_WRITE = 3; + public const int SSL_ERROR_WANT_X509_LOOKUP = 4; + public const int SSL_ERROR_SYSCALL = 5; + public const int SSL_ERROR_ZERO_RETURN = 6; + public const int SSL_ERROR_WANT_CONNECT = 7; + public const int SSL_ERROR_WANT_ACCEPT = 8; + + // void ERR_error_string_n(unsigned long e, char *buf, size_t len); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + //public static extern void ERR_error_string_n(ulong e, IntPtr buf, size_t len); + public static extern void ERR_error_string_n(ulong e, byte[] buf, nint len); + + // unsigned long ERR_get_error(void); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern ulong ERR_get_error(); + + #endregion + + #region Utilities + + public static IntPtr ExpectNonNull(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + throw new Exception(); + + return ptr; + } + + #endregion + } +} diff --git a/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs b/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs index bf61865..8293161 100644 --- a/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs +++ b/src/S7CommPlusDriver/OpenSSL/OpenSSLConnector.cs @@ -102,10 +102,14 @@ private int DataToWrite(byte[] pData, int dataLength) private int DataToRead(byte[] pData, int dataLength) { + //Console.WriteLine($"[DEBUG] Passing {dataLength} bytes to BIO_write."); + m_readRequired = false; int bytesUsed = Native.BIO_write(m_pBioIn, pData, dataLength); + //Console.WriteLine($"[OpenSSL-Net] Written to BIO_In: {bytesUsed} bytes."); + byte[] pBuffer = null; int bufferSize = 0; @@ -134,17 +138,24 @@ private int DataToRead(byte[] pData, int dataLength) private void SendPendingData() { - while (Native.BIO_ctrl_pending(m_pBioOut) > 0) + + nint pending = Native.BIO_ctrl_pending(m_pBioOut); + + while (pending > 0) { + //Console.WriteLine($"[BIO-Debug] BIO_ctrl_pending reported: {pending} bytes available."); + byte[] pBuffer = null; int bufferSize = 0; GetNextWriteDataBuffer(ref pBuffer, ref bufferSize); int bytesToSend = Native.BIO_read(m_pBioOut, pBuffer, bufferSize); + //Console.WriteLine($"[BIO-Debug] BIO_read attempted. Result: {bytesToSend} bytes."); if (bytesToSend > 0) { + //Console.WriteLine($"[OpenSSL-Net] Read {bytesToSend} bytes from BIO_Out to send to PLC."); OnDataToWrite(pBuffer, bytesToSend); } @@ -155,6 +166,8 @@ private void SendPendingData() HandleError(bytesToSend); } } + // Refresh pending count + pending = Native.BIO_ctrl_pending(m_pBioOut); } } @@ -167,7 +180,7 @@ void HandleError(int result) { if (result <= 0) { - int error = Native.SSL_get_error(m_pSslConnection, result); + int error = (int)Native.SSL_get_error(m_pSslConnection, result); switch (error) { @@ -192,6 +205,18 @@ protected void RunSSL() GetPendingOperations(ref dataToRead, ref dataToWrite); + // DEBUG: Force a handshake attempt to see if OpenSSL has something to say + //int handshakeRet = Native.SSL_do_handshake(m_pSslConnection); + + //if (handshakeRet <= 0) + //{ + // int err = (int)Native.SSL_get_error(m_pSslConnection, handshakeRet); + // if (err != Native.SSL_ERROR_WANT_READ) + // { + // Console.WriteLine($"[Handshake-Debug] SSL_do_handshake result: {handshakeRet}, Error: {err}"); + // } + //} + while ((!m_readRequired && dataToWrite) || dataToRead) { if (Native.SSL_in_init(m_pSslConnection) != 0) @@ -216,6 +241,13 @@ protected void RunSSL() GetPendingOperations(ref dataToRead, ref dataToWrite); } + + //If BIO_read isn't triggering, see if anything is stuck in BioOut + //if (Native.BIO_ctrl_pending(m_pBioOut) > 0) + //{ + // //Console.WriteLine($"[BIO-Debug] Handshake loop finished but {Native.BIO_ctrl_pending(m_pBioOut)} bytes are still stuck in BioOut."); + // SendPendingData(); + //} } public void Write(byte[] pData,int dataLen) @@ -346,14 +378,16 @@ void PutbackBuffer(DataBufferList list, DataBuffer pBuffer) list.AddFirst(pBuffer); } - /// /// Create OMS exporter secret that is needed for the legitimation with the PLC /// /// Secret public byte[] getOMSExporterSecret() { byte[] secretOut = new byte[32]; - int ret = (int)Native.SSL_export_keying_material(m_pSslConnection, secretOut, (nint)secretOut.Length, "EXPERIMENTAL_OMS".ToCharArray(), 16, IntPtr.Zero, 0, 0); + // Convert to UTF8 bytes to avoid the 2-byte char issue in linux + byte[] labelBytes = System.Text.Encoding.UTF8.GetBytes("EXPERIMENTAL_OMS"); + int ret = Native.SSL_export_keying_material(m_pSslConnection,secretOut,(nint)secretOut.Length,labelBytes,(nint)labelBytes.Length,IntPtr.Zero,0,0); + return secretOut; } }