From 063306c1332bfeb4fd2d4de5d2a0104ef9254cd4 Mon Sep 17 00:00:00 2001 From: citronneur Date: Sun, 22 Jun 2014 11:21:21 +0200 Subject: [PATCH] Add license header + SSL server config --- README.md | 6 ++ rdpy/examples/rdpserver.py | 13 ++- rdpy/protocol/rdp/rdp.py | 29 ++++++- rdpy/protocol/rdp/tpdu.py | 159 +++++++++++++++++++++++++------------ rdpy/protocol/rdp/tpkt.py | 76 ++++++++++++------ 5 files changed, 205 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 4cc460a..53462a8 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,10 @@ Remote Desktop Protocol in Twisted Python * python-qt4 * python-qt4reactor +##Must be implemented before first release +* CreedSSP +* Packet redirection +* License +* Most common orders + this project is still in progress. diff --git a/rdpy/examples/rdpserver.py b/rdpy/examples/rdpserver.py index 9125de5..2505223 100644 --- a/rdpy/examples/rdpserver.py +++ b/rdpy/examples/rdpserver.py @@ -7,9 +7,18 @@ import sys, os sys.path.insert(1, os.path.join(sys.path[0], '../..')) from rdpy.protocol.rdp import rdp -from rdpy.network.layer import LayerMode + +class TestServerFactory(rdp.ServerFactory): + def startedConnecting(self, connector): + pass + + def clientConnectionLost(self, connector, reason): + pass + + def clientConnectionFailed(self, connector, reason): + pass if __name__ == '__main__': from twisted.internet import reactor - reactor.listenTCP(33389, rdp.Factory(LayerMode.SERVER)) + reactor.listenTCP(33389, TestServerFactory()) reactor.run() \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 9e12bba..6ebf813 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -47,14 +47,41 @@ class ClientFactory(protocol.Factory): ''' pduLayer = pdu.PDU(LayerMode.CLIENT) pduLayer.getController().addObserver(self.buildObserver()) - return tpkt.TPKT(tpdu.TPDU(mcs.MCS(pduLayer))); + return tpkt.TPKT(tpdu.createClient(mcs.MCS(pduLayer))); def buildObserver(self): ''' build observer use for connection ''' pass + +class ServerFactory(protocol.Factory): + ''' + Factory of Serrve RDP protocol + ''' + def __init__(self, privateKeyFileName, certificateFileName): + ''' + ctor + @param privateKeyFileName: file contain server private key + @param certficiateFileName: file that contain publi key + ''' + self._privateKeyFileName = privateKeyFileName + self._certificateFileName = certificateFileName + def buildProtocol(self, addr): + ''' + Function call from twisted and build rdp protocol stack + @param addr: destination address + ''' + pduLayer = pdu.PDU(LayerMode.SERVER) + #pduLayer.getController().addObserver(self.buildObserver()) + return tpkt.TPKT(tpdu.createServer(mcs.MCS(pduLayer), self._privateKeyFileName, self._certificateFileName)); + + def buildObserver(self): + ''' + build observer use for connection + ''' + pass class RDPClientObserver(object): ''' diff --git a/rdpy/protocol/rdp/tpdu.py b/rdpy/protocol/rdp/tpdu.py index f02c3dd..f124fcd 100644 --- a/rdpy/protocol/rdp/tpdu.py +++ b/rdpy/protocol/rdp/tpdu.py @@ -1,6 +1,29 @@ -''' -@author: sylvain -''' +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +Implement transport pdu layer + +This layer have main goal to negociate ssl transport +RDP basic security is not supported by RDPY (because is not a true security layer...) +""" + from rdpy.network.layer import LayerAutomata, LayerMode from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof from rdpy.network.error import InvalidExpectedDataException @@ -9,9 +32,9 @@ from rdpy.network.const import ConstAttributes, TypeAttributes @ConstAttributes @TypeAttributes(UInt8) class MessageType(object): - ''' + """ message type - ''' + """ X224_TPDU_CONNECTION_REQUEST = 0xE0 X224_TPDU_CONNECTION_CONFIRM = 0xD0 X224_TPDU_DISCONNECT_REQUEST = 0x80 @@ -21,9 +44,9 @@ class MessageType(object): @ConstAttributes @TypeAttributes(UInt8) class NegociationType(object): - ''' + """ negotiation header - ''' + """ TYPE_RDP_NEG_REQ = 0x01 TYPE_RDP_NEG_RSP = 0x02 TYPE_RDP_NEG_FAILURE = 0x03 @@ -31,9 +54,9 @@ class NegociationType(object): @ConstAttributes @TypeAttributes(UInt32Le) class Protocols(object): - ''' + """ protocols available for TPDU layer - ''' + """ PROTOCOL_RDP = 0x00000000 PROTOCOL_SSL = 0x00000001 PROTOCOL_HYBRID = 0x00000002 @@ -42,9 +65,9 @@ class Protocols(object): @ConstAttributes @TypeAttributes(UInt32Le) class NegotiationFailureCode(object): - ''' + """ protocol negotiation failure code - ''' + """ SSL_REQUIRED_BY_SERVER = 0x00000001 SSL_NOT_ALLOWED_BY_SERVER = 0x00000002 SSL_CERT_NOT_ON_SERVER = 0x00000003 @@ -53,10 +76,13 @@ class NegotiationFailureCode(object): SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006 class TPDUConnectMessage(CompositeType): - ''' + """ header of TPDU connection messages - ''' + """ def __init__(self, code): + """ + @param code: MessageType + """ CompositeType.__init__(self) self.len = UInt8(lambda:sizeof(self) - 1) self.code = UInt8(code.value, constant = True) @@ -65,9 +91,9 @@ class TPDUConnectMessage(CompositeType): self.protocolNeg = Negotiation(optional = True) class TPDUDataHeader(CompositeType): - ''' + """ header send when tpdu exchange application data - ''' + """ def __init__(self): CompositeType.__init__(self) self.header = UInt8(2, constant = True) @@ -75,12 +101,12 @@ class TPDUDataHeader(CompositeType): self.separator = UInt8(0x80, constant = True) class Negotiation(CompositeType): - ''' - negociation request message + """ + negociate request message @see: request -> http://msdn.microsoft.com/en-us/library/cc240500.aspx @see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx @see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx - ''' + """ def __init__(self, optional = False): CompositeType.__init__(self, optional = optional) self.code = UInt8() @@ -91,42 +117,55 @@ class Negotiation(CompositeType): self.failureCode = UInt32Le(conditional = lambda: self.code == NegociationType.TYPE_RDP_NEG_FAILURE) class TPDU(LayerAutomata): - ''' + """ TPDU layer management there is an connection automata - ''' - def __init__(self, presentation): - ''' - Constructor - @param presentation: MCS layer - ''' - LayerAutomata.__init__(self, presentation._mode, presentation) + """ + def __init__(self, mode, presentation): + """ + @param mode: automata mode (client or server) + @param presentation: upper layer, MCS layer in RDP case + """ + LayerAutomata.__init__(self, mode, presentation) #default selectedProtocol is SSl because is the only supported #in this version of RDPY #client requested selectedProtocol self._requestedProtocol = Protocols.PROTOCOL_SSL #server selected selectedProtocol self._selectedProtocol = Protocols.PROTOCOL_SSL + + #Server mode informations for tls connexion + self._serverPrivateKeyFileName = None + self._serverCertificateFileName = None + + def initTLSServerInfos(self, privateKeyFileName, certificateFileName): + """ + Init informations for ssl server connexion + @param privateKeyFileName: file contain server private key + @param certficiateFileName: file that contain publi key + """ + self._serverPrivateKeyFileName = privateKeyFileName + self._serverCertificateFileName = certificateFileName def connect(self): - ''' + """ connection request for client send a connection request packet - ''' + """ if self._mode == LayerMode.CLIENT: self.sendConnectionRequest() else: self.setNextState(self.recvConnectionRequest) def recvConnectionConfirm(self, data): - ''' - recv connection confirm message + """ + receive connection confirm message next state is recvData call connect on presentation layer if all is good @param data: Stream that contain connection confirm @see: response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx @see: failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx - ''' + """ message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_CONFIRM) data.readType(message) @@ -150,12 +189,12 @@ class TPDU(LayerAutomata): LayerAutomata.connect(self) def recvConnectionRequest(self, data): - ''' + """ read connection confirm packet next state is send connection confirm - @param data: stream + @param data: Stream @see : http://msdn.microsoft.com/en-us/library/cc240470.aspx - ''' + """ message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_REQUEST) data.readType(message) @@ -177,21 +216,21 @@ class TPDU(LayerAutomata): self.sendConnectionConfirm() def recvData(self, data): - ''' + """ read data header from packet and pass to presentation layer - @param data: stream - ''' + @param data: Stream + """ header = TPDUDataHeader() data.readType(header) LayerAutomata.recv(self, data) def sendConnectionRequest(self): - ''' + """ write connection request message next state is recvConnectionConfirm @see: http://msdn.microsoft.com/en-us/library/cc240500.aspx - ''' + """ message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_REQUEST) message.protocolNeg.code = NegociationType.TYPE_RDP_NEG_REQ message.protocolNeg.selectedProtocol = self._requestedProtocol @@ -199,36 +238,53 @@ class TPDU(LayerAutomata): self.setNextState(self.recvConnectionConfirm) def sendConnectionConfirm(self): - ''' + """ write connection confirm message next state is recvData @see : http://msdn.microsoft.com/en-us/library/cc240501.aspx - ''' + """ message = TPDUConnectMessage(MessageType.X224_TPDU_CONNECTION_CONFIRM) message.protocolNeg.code = NegociationType.TYPE_RDP_NEG_REQ message.protocolNeg.selectedProtocol = self._selectedProtocol self._transport.send(message) #_transport is TPKT and transport is TCP layer of twisted - self._transport.transport.startTLS(ServerTLSContext()) + self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName)) #connection is done send to presentation LayerAutomata.connect(self) def send(self, message): - ''' + """ write message packet for TPDU layer add TPDU header - ''' + @param message: network.Type message + """ self._transport.send((TPDUDataHeader(), message)) +def createClient(presentation): + """ + Factory of TPDU layer in Client mode + @param presentation: presentation layer, in RDP mode is MCS layer + """ + return TPDU(LayerMode.CLIENT, presentation) + +def createServer(presentation, privateKeyFileName, certificateFileName): + """ + Factory of TPDU layer in Server mode + @param privateKeyFileName: file contain server private key + @param certficiateFileName: file that contain publi key + """ + tpdu = TPDU(LayerMode.SERVER, presentation) + tpdu.initTLSServerInfos(privateKeyFileName, certificateFileName) + return tpdu #open ssl needed from twisted.internet import ssl from OpenSSL import SSL class ClientTLSContext(ssl.ClientContextFactory): - ''' + """ client context factory for open ssl - ''' + """ def getContext(self): context = SSL.Context(SSL.TLSv1_METHOD) context.set_options(0x00020000)#SSL_OP_NO_COMPRESSION @@ -237,9 +293,10 @@ class ClientTLSContext(ssl.ClientContextFactory): return context class ServerTLSContext(ssl.DefaultOpenSSLContextFactory): - ''' + """ server context factory for open ssl - ''' - def __init__(self, *args, **kw): - kw['sslmethod'] = SSL.TLSv1_METHOD - ssl.DefaultOpenSSLContextFactory.__init__(self, *args, **kw) \ No newline at end of file + @param privateKeyFileName: Name of a file containing a private key + @param certificateFileName: Name of a file containing a certificate + """ + def __init__(self, privateKeyFileName, certificateFileName): + ssl.DefaultOpenSSLContextFactory.__init__(self, privateKeyFileName, certificateFileName, SSL.TLSv1_METHOD) \ No newline at end of file diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index 32af92e..f1c4330 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -1,22 +1,44 @@ -''' -@author: sylvain -''' +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +Transport packet layer implementation + +Use to build correct size packet and handle slow path and fast path mode +""" from rdpy.network.layer import RawLayer, LayerMode from rdpy.network.type import UInt8, UInt16Be, sizeof class TPKT(RawLayer): - ''' + """ TPKT layer in RDP protocol stack this layer only handle size of packet and determine if is a fast path packet - ''' + """ #first byte of classic tpkt header TPKT_PACKET = UInt8(3) def __init__(self, presentation): - ''' + """ Constructor - ''' + @param presentation: presentation layer, in RDP case is TPDU layer + """ RawLayer.__init__(self, LayerMode.NONE, presentation) #last packet version read from header self._lastPacketVersion = UInt8() @@ -24,10 +46,10 @@ class TPKT(RawLayer): self._lastShortLength = UInt8() def connect(self): - ''' + """ call when transport layer connection is made (inherit from RawLayer) - ''' + """ #header is on two bytes self.expect(2, self.readHeader) #no connection automata on this layer @@ -35,9 +57,10 @@ class TPKT(RawLayer): self._presentation.connect() def readHeader(self, data): - ''' + """ read header of TPKT packet - ''' + @param data: Stream received from twisted layer + """ #first read packet version data.readType(self._lastPacketVersion) #classic packet @@ -57,18 +80,20 @@ class TPKT(RawLayer): def readExtendedHeader(self, data): - ''' + """ header may be on 4 bytes - ''' + @param data: Stream from twisted layer + """ #next state is read data size = UInt16Be() data.readType(size) self.expect(size.value - 4, self.readData) def readExtendedFastPathHeader(self, data): - ''' - fast ath header may be on 1 byte more - ''' + """ + fast path header may be on 1 byte more + @param data: Stream from twisted layer + """ leftPart = UInt8() data.readType(leftPart) self._lastShortLength.value &= ~0x80 @@ -77,21 +102,24 @@ class TPKT(RawLayer): self.expect(self._lastShortLength.value - 3, self.readFastPath) def readFastPath(self, data): - ''' + """ fast path data - ''' + @param data: Stream from twisted layer + """ pass def readData(self, data): - ''' - read classic TPKT packet - ''' + """ + read classic TPKT packet, last state in tpkt automata + @param data: Stream with correct size + """ #next state is pass to self._presentation.recv(data) self.expect(2, self.readHeader) def send(self, message): - ''' - send encapsuled data - ''' + """ + send encompassed data + @param message: network.Type message to send + """ RawLayer.send(self, (TPKT.TPKT_PACKET, UInt8(0), UInt16Be(sizeof(message) + 4), message)) \ No newline at end of file