andrej@2544: #!/usr/bin/env python andrej@2544: # -*- coding: utf-8 -*- andrej@2544: andrej@2544: # This file is part of Beremiz, a Integrated Development Environment for andrej@2544: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@2544: # andrej@2544: # Copyright (C) 2019: Edouard TISSERANT andrej@2544: # andrej@2544: # See COPYING file for copyrights details. andrej@2544: # andrej@2544: # This program is free software; you can redistribute it and/or andrej@2544: # modify it under the terms of the GNU General Public License andrej@2544: # as published by the Free Software Foundation; either version 2 andrej@2544: # of the License, or (at your option) any later version. andrej@2544: # andrej@2544: # This program is distributed in the hope that it will be useful, andrej@2544: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@2544: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@2544: # GNU General Public License for more details. andrej@2544: # andrej@2544: # You should have received a copy of the GNU General Public License andrej@2544: # along with this program; if not, write to the Free Software andrej@2544: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. andrej@2544: andrej@2544: andrej@2544: """ andrej@2544: The TLS-PSK adapter that handles SSL connections instead of regular sockets, andrej@2544: but using Pre Shared Keys instead of Certificates andrej@2544: """ andrej@2544: Edouard@2313: from __future__ import absolute_import Edouard@2313: from __future__ import print_function Edouard@2312: Edouard@2313: import socket Edouard@2314: import re andrej@2537: import ssl Edouard@2312: import Pyro Edouard@2314: from Pyro.core import PyroURI edouard@2492: from Pyro.protocol import _connect_socket, TCPConnection, PYROAdapter Edouard@2312: from Pyro.errors import ConnectionDeniedError, ProtocolError Edouard@2312: from Pyro.util import Log Edouard@2312: edouard@2588: try: edouard@2588: import sslpsk edouard@2588: except ImportError as e: edouard@2588: print(str(e)) edouard@2588: sslpsk = None edouard@2492: edouard@2598: Edouard@2312: class PYROPSKAdapter(PYROAdapter): andrej@2544: """ andrej@2544: This is essentialy the same as in Pyro/protocol.py andrej@2544: only raw_sock wrapping into sock through sslpsk.wrap_socket was added andrej@2544: Pyro unfortunately doesn't allow cleaner customization andrej@2544: """ andrej@2544: edouard@2492: def bindToURI(self, URI): Edouard@2312: with self.lock: # only 1 thread at a time can bind the URI Edouard@2312: try: edouard@2492: self.URI = URI Edouard@2312: Edouard@2312: # This are the statements that differ from Pyro/protocol.py Edouard@2312: raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Edouard@2312: _connect_socket(raw_sock, URI.address, URI.port, self.timeout) Edouard@2312: sock = sslpsk.wrap_socket( Edouard@2313: raw_sock, psk=Pyro.config.PYROPSK, server_side=False, edouard@2492: ciphers="PSK-AES256-CBC-SHA", # available in openssl 1.0.2 Edouard@2316: ssl_version=ssl.PROTOCOL_TLSv1) edouard@2492: # all the rest is the same as in Pyro/protocol.py Edouard@2312: edouard@2492: conn = TCPConnection(sock, sock.getpeername()) Edouard@2312: # receive the authentication challenge string, and use that to build the actual identification string. Edouard@2312: try: edouard@2492: authChallenge = self.recvAuthChallenge(conn) andrej@2536: except ProtocolError as x: Edouard@2312: # check if we were denied edouard@2492: if hasattr(x, "partialMsg") and x.partialMsg[:len(self.denyMSG)] == self.denyMSG: Edouard@2312: raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(x.partialMsg[-1])]) Edouard@2312: else: Edouard@2312: raise Edouard@2312: # reply with our ident token, generated from the ident passphrase and the challenge edouard@2492: msg = self._sendConnect(sock, self.newConnValidator.createAuthToken(self.ident, authChallenge, conn.addr, self.URI, None)) edouard@2492: if msg == self.acceptMSG: edouard@2492: self.conn = conn edouard@2492: self.conn.connected = 1 edouard@2492: Log.msg('PYROAdapter', 'connected to', str(URI)) edouard@2492: if URI.protocol == 'PYROLOCPSK': edouard@2492: self.resolvePYROLOC_URI("PYROPSK") # updates self.URI edouard@2492: elif msg[:len(self.denyMSG)] == self.denyMSG: Edouard@2312: try: Edouard@2312: raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(msg[-1])]) edouard@2492: except (KeyError, ValueError): Edouard@2312: raise ConnectionDeniedError('invalid response') Edouard@2312: except socket.error: edouard@2492: Log.msg('PYROAdapter', 'connection failed to URI', str(URI)) Edouard@2312: raise ProtocolError('connection failed') Edouard@2312: edouard@2492: Edouard@2312: _getProtocolAdapter = Pyro.protocol.getProtocolAdapter edouard@2492: edouard@2492: Edouard@2312: def getProtocolAdapter(protocol): Edouard@2312: if protocol in ('PYROPSK', 'PYROLOCPSK'): Edouard@2312: return PYROPSKAdapter() Edouard@2318: return _getProtocolAdapter(protocol) Edouard@2312: edouard@2492: Edouard@2314: _processStringURI = Pyro.core.processStringURI edouard@2492: edouard@2492: Edouard@2314: def processStringURI(URI): edouard@2492: x = re.match(r'(?PPYROLOCPSK)://(?P[^\s:]+):?(?P\d+)?/(?P\S*)', URI) Edouard@2314: if x: edouard@2492: protocol = x.group('protocol') edouard@2492: hostname = x.group('hostname') edouard@2492: port = x.group('port') Edouard@2314: if port: edouard@2492: port = int(port) Edouard@2314: else: edouard@2492: port = 0 edouard@2492: name = x.group('name') edouard@2492: return PyroURI(hostname, name, port, protocol) Edouard@2314: return _processStringURI(URI) edouard@2492: edouard@2492: andrej@2543: def setupPSKAdapter(): andrej@2543: """ andrej@2543: Add PyroAdapter to the list of available in andrej@2543: Pyro adapters and handle new supported protocols andrej@2543: andrej@2543: This function should be called after andrej@2543: reimport of Pyro module to enable PYROS:// again. andrej@2543: """ edouard@2588: if sslpsk is not None: edouard@2588: Pyro.protocol.getProtocolAdapter = getProtocolAdapter edouard@2588: Pyro.core.processStringURI = processStringURI edouard@2588: else: edouard@2588: raise Exception("sslpsk python module unavailable")