Files
rdpy/rdpy/core/x224.py
2020-04-20 22:51:32 +02:00

276 lines
10 KiB
Python

#
# Copyright (c) 2014-2015 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 <http://www.gnu.org/licenses/>.
#
"""
Implement transport PDU layer
This layer have main goal to negociate SSL transport
RDP basic security is supported only on client side
"""
from rdpy.core import tpkt
from rdpy.core.nla import sspi
from rdpy.model import log
from rdpy.model.message import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, Buffer
from rdpy.model.error import InvalidExpectedDataException, RDPSecurityNegoFail
class MessageType:
"""
@summary: Message type
"""
X224_TPDU_CONNECTION_REQUEST = 0xE0
X224_TPDU_CONNECTION_CONFIRM = 0xD0
X224_TPDU_DISCONNECT_REQUEST = 0x80
X224_TPDU_DATA = 0xF0
X224_TPDU_ERROR = 0x70
class NegociationType:
"""
@summary: Negotiation header
"""
TYPE_RDP_NEG_REQ = 0x01
TYPE_RDP_NEG_RSP = 0x02
TYPE_RDP_NEG_FAILURE = 0x03
class Protocols:
"""
@summary: Protocols available for x224 layer
@see: https://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
PROTOCOL_RDP = 0x00000000
PROTOCOL_SSL = 0x00000001
PROTOCOL_HYBRID = 0x00000002
PROTOCOL_HYBRID_EX = 0x00000008
class NegotiationFailureCode:
"""
@summary: Protocol negotiation failure code
"""
SSL_REQUIRED_BY_SERVER = 0x00000001
SSL_NOT_ALLOWED_BY_SERVER = 0x00000002
SSL_CERT_NOT_ON_SERVER = 0x00000003
INCONSISTENT_FLAGS = 0x00000004
HYBRID_REQUIRED_BY_SERVER = 0x00000005
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 0x00000006
class ConnectionRequestPDU(CompositeType):
"""
Connection Request PDU
Use to send protocol security level available for the client
:see: http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, constant=True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
self.cookie = Buffer(until=b"\x0d\x0a", conditional=lambda: (self.len._is_readed and self.len.value > 14))
# read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class ConnectionConfirmPDU(CompositeType):
"""
@summary: Server response
@see: http://msdn.microsoft.com/en-us/library/cc240506.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.len = UInt8(lambda:sizeof(self) - 1)
self.code = UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, constant = True)
self.padding = (UInt16Be(), UInt16Be(), UInt8())
#read if there is enough data
self.protocolNeg = Negotiation(optional = True)
class X224DataHeader(CompositeType):
"""
@summary: Header send when x224 exchange application data
"""
def __init__(self):
CompositeType.__init__(self)
self.header = UInt8(2)
self.messageType = UInt8(MessageType.X224_TPDU_DATA, constant = True)
self.separator = UInt8(0x80, constant = True)
class Negotiation(CompositeType):
"""
@summary: 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()
self.flag = UInt8(0)
#always 8
self.len = UInt16Le(0x0008, constant = True)
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
class X224:
"""
@summary: x224 layer management
there is an connection automata
"""
def __init__(self, tpkt: tpkt.Tpkt):
"""
@param tpkt: TPKT layer
"""
self.tpkt = tpkt
async def read(self):
"""
@summary: Read data header from packet
And pass to presentation layer
@param data: Stream
"""
header = X224DataHeader()
payload = await self.tpkt.read()
payload.read_type(header)
return payload
async def write(self, message):
"""
@summary: Write message packet for TPDU layer
Add TPDU header
@param message:
"""
await self.tpkt.write((X224DataHeader(), message))
async def connect(tpkt: tpkt.Tpkt, authentication_protocol: sspi.IAuthenticationProtocol) -> X224:
"""
Negotiate the security level and generate a X224 configured layer
:ivar tpkt: this is the tpkt layer use to negotiate the security level
:ivar authentication_protocol: Authentication protocol is used by NLA authentication
Actually only NTLMv2 is available
:see: http://msdn.microsoft.com/en-us/library/cc240500.aspx
"""
request = ConnectionRequestPDU()
request.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_REQ
request.protocolNeg.selectedProtocol.value = Protocols.PROTOCOL_HYBRID | Protocols.PROTOCOL_SSL
await tpkt.write(request)
respond = (await tpkt.read()).read_type(ConnectionConfirmPDU())
if respond.protocolNeg.failureCode._is_readed:
raise RDPSecurityNegoFail("negotiation failure code %x"%respond.protocolNeg.failureCode.value)
selected_protocol = Protocols.PROTOCOL_RDP
if respond.protocolNeg._is_readed:
selected_protocol = respond.protocolNeg.selectedProtocol.value
if selected_protocol in [Protocols.PROTOCOL_HYBRID_EX]:
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
if selected_protocol == Protocols.PROTOCOL_RDP:
return X224(tpkt)
elif selected_protocol == Protocols.PROTOCOL_SSL:
return X224(await tpkt.start_tls())
elif selected_protocol == Protocols.PROTOCOL_HYBRID:
return X224(await tpkt.start_nla(authentication_protocol))
class Server(X224):
"""
@summary: Server automata of X224 layer
"""
def __init__(self, presentation, privateKeyFileName = None, certificateFileName = None, forceSSL = False):
"""
@param presentation: {layer} upper layer, MCS layer in RDP case
@param privateKeyFileName: {str} file contain server private key
@param certficiateFileName: {str} file that contain public key
@param forceSSL: {boolean} reject old client that doerasn't support SSL
"""
X224Layer.__init__(self, presentation)
#Server mode informations for TLS connection
self._serverPrivateKeyFileName = privateKeyFileName
self._serverCertificateFileName = certificateFileName
self._forceSSL = forceSSL and not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None
def connect(self):
"""
@summary: Connection request for server wait connection request packet from client
"""
self.setNextState(self.recvConnectionRequest)
def recvConnectionRequest(self, data):
"""
@summary: Read connection confirm packet
Next state is send connection confirm
@param data: {Stream}
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
message = ConnectionRequestPDU()
data.read_type(message)
if not message.protocolNeg._is_readed:
self._requestedProtocol = Protocols.PROTOCOL_RDP
else:
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
#match best security layer available
if not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_SSL
else:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_RDP
#if force ssl is enable
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
log.warning("server reject client because doesn't support SSL")
#send error message and quit
message = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
self._transport.send(message)
self.close()
return
self.sendConnectionConfirm()
def sendConnectionConfirm(self):
"""
@summary: Write connection confirm message
Start TLS connection
Next state is recvData
@see : http://msdn.microsoft.com/en-us/library/cc240501.aspx
"""
message = ConnectionConfirmPDU()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
#_transport is TPKT and transport is TCP layer of twisted
#self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
#connection is done send to presentation
self.setNextState(self.recvData)
self._presentation.connect()