diff --git a/bin/rdpy-rdpclient.py b/bin/rdpy-rdpclient.py index 99bf214..c45ba49 100755 --- a/bin/rdpy-rdpclient.py +++ b/bin/rdpy-rdpclient.py @@ -29,7 +29,7 @@ from rdpy.protocol.rdp import rdp from rdpy.core.error import RDPSecurityNegoFail import rdpy.core.log as log -log._LOG_LEVEL = log.Level.INFO +#log._LOG_LEVEL = log.Level.INFO class RDPClientQtFactory(rdp.ClientFactory): """ diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index dd3c4eb..328a28c 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -25,7 +25,9 @@ http://msdn.microsoft.com/en-us/library/cc240508.aspx from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType import per, mcs from rdpy.core.error import InvalidExpectedDataException -from rdpy.core import log, x509 +from rdpy.core import log +from rdpy.security import x509 +import rdpy.security.rsa_wrapper as rsa t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 ) @@ -312,7 +314,7 @@ class ServerCertificate(CompositeType): """ def __init__(self, certData = None, readLen = None, conditional = lambda:True): CompositeType.__init__(self, readLen = readLen, conditional = conditional) - self.dwVersion = UInt32Le() + self.dwVersion = UInt32Le(lambda:self.certData.__class__._TYPE_) def CertificateFactory(): """ @@ -328,18 +330,7 @@ class ServerCertificate(CompositeType): elif not "_TYPE_" in certData.__class__.__dict__: raise InvalidExpectedDataException("Try to send an invalid Certificate") - self.certData = FactoryType(CertificateFactory) - -def bin2bn(b): - """ - @summary: convert binary string to bignum - @param b: {str} binary string - @return: {long} bignum - """ - l = 0L - for ch in b: - l = (l<<8) | ord(ch) - return l + self.certData = FactoryType(CertificateFactory) class ProprietaryServerCertificate(CompositeType): """ @@ -363,8 +354,9 @@ class ProprietaryServerCertificate(CompositeType): """ @return: {Tuple} (modulus, publicExponent) """ + log.debug("read RSA public key from proprietary certificate") #reverse because bignum in little endian - return bin2bn(self.PublicKeyBlob.modulus.value[::-1]), self.PublicKeyBlob.pubExp.value + return rsa.PublicKey(self.PublicKeyBlob.pubExp.value, self.PublicKeyBlob.modulus.value[::-1]) class CertBlob(CompositeType): """ @@ -393,8 +385,10 @@ class X509CertificateChain(CompositeType): """ @return: {Tuple} (modulus, publicExponent) """ - #last certifcate contain publi key - return x509.extractRSAKey(x509.load(self.CertBlobArray[-1].abCert.value)) + log.debug("read RSA public key from x509 certificate") + #last certifcate contain public key + n, e = x509.extractRSAKey(x509.load(self.CertBlobArray[-1].abCert.value)) + return rsa.PublicKey(e, n) class RSAPublicKey(CompositeType): """ diff --git a/rdpy/protocol/rdp/lic.py b/rdpy/protocol/rdp/lic.py index 25d41b0..187e965 100644 --- a/rdpy/protocol/rdp/lic.py +++ b/rdpy/protocol/rdp/lic.py @@ -25,8 +25,8 @@ from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream from rdpy.core.error import InvalidExpectedDataException import rdpy.core.log as log -import sec, gcc -from rdpy.core import x509, rc4 +import sec +from rdpy.security import rc4 class MessageType(object): """ diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index 0a20c29..eaefde0 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -31,7 +31,7 @@ from rdpy.protocol.rdp.ber import writeLength import rdpy.core.log as log import ber, gcc, per -import rsa +import rdpy.security.rsa_wrapper as rsa class Message(object): """ @@ -491,7 +491,7 @@ class Server(MCSLayer): self._serverSettings.SC_SECURITY.encryptionMethod.value = gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT self._serverSettings.SC_SECURITY.encryptionLevel.value = gcc.EncryptionLevel.ENCRYPTION_LEVEL_HIGH - self._serverSettings.SC_SECURITY.serverRandom.value = rsa.randnum.read_random_bits(256) + self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256) self._serverSettings.SC_SECURITY.serverCertificate.certData = self._presentation.getCertificate() self._serverSettings.SC_CORE.clientRequestedProtocol.value = self._transport._requestedProtocol diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py index 338418a..27fe183 100644 --- a/rdpy/protocol/rdp/sec.py +++ b/rdpy/protocol/rdp/sec.py @@ -21,12 +21,14 @@ Some use full methods for security in RDP """ -import sha, md5, rsa +import sha, md5 import gcc, lic, tpkt, mcs from rdpy.core.type import CompositeType, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8 from rdpy.core.layer import LayerAutomata, IStreamSender from rdpy.core.error import InvalidExpectedDataException -from rdpy.core import log, rc4 +from rdpy.core import log +from rdpy.security import rc4 +import rdpy.security.rsa_wrapper as rsa class SecurityFlag(object): """ @@ -94,33 +96,39 @@ class AfInet(object): AF_INET = 0x00002 AF_INET6 = 0x0017 -def terminalServicesSigningKey(): - """ - @summary: security is security ;-) Generate RSA private key - @see: http://msdn.microsoft.com/en-us/library/cc240776.aspx - """ - modulus = "\x3d\x3a\x5e\xbd\x72\x43\x3e\xc9\x4d\xbb\xc1\x1e\x4a\xba\x5f\xcb\x3e\x88\x20\x87\xef\xf5\xc1\xe2\xd7\xb7\x6b\x9a\xf2\x52\x45\x95\xce\x63\x65\x6b\x58\x3a\xfe\xef\x7c\xe7\xbf\xfe\x3d\xf6\x5c\x7d\x6c\x5e\x06\x09\x1a\xf5\x61\xbb\x20\x93\x09\x5f\x05\x6d\xea\x87" - privateExponent = "\x87\xa7\x19\x32\xda\x11\x87\x55\x58\x00\x16\x16\x25\x65\x68\xf8\x24\x3e\xe6\xfa\xe9\x67\x49\x94\xcf\x92\xcc\x33\x99\xe8\x08\x60\x17\x9a\x12\x9f\x24\xdd\xb1\x24\x99\xc7\x3a\xb8\x0a\x7b\x0d\xdd\x35\x07\x79\x17\x0b\x51\x9b\xb3\xc7\x10\x01\x13\xe7\x3f\xf3\x5f" - publicExponent = "\x5b\x7b\x88\xc0" - return rsa.PublicKey(e = gcc.bin2bn(privateExponent[::-1]), n = gcc.bin2bn(modulus[::-1])) - def terminalServicesSign(certificate): """ @summary: sign proprietary certificate @param certificate: {gcc.ProprietaryServerCertificate} @see: http://msdn.microsoft.com/en-us/library/cc240778.aspx """ - s = Stream() - s.writeType(UInt32Le(gcc.ProprietaryServerCertificate._TYPE_)) - s.writeType(certificate.dwSigAlgId) - s.writeType(certificate.dwKeyAlgId) - s.writeType(certificate.wPublicKeyBlobType) - s.writeType(certificate.wPublicKeyBlobLen) - s.writeType(certificate.PublicKeyBlob) + modulus = "\x3d\x3a\x5e\xbd\x72\x43\x3e\xc9\x4d\xbb\xc1\x1e\x4a\xba\x5f\xcb\x3e\x88\x20\x87\xef\xf5\xc1\xe2\xd7\xb7\x6b\x9a\xf2\x52\x45\x95\xce\x63\x65\x6b\x58\x3a\xfe\xef\x7c\xe7\xbf\xfe\x3d\xf6\x5c\x7d\x6c\x5e\x06\x09\x1a\xf5\x61\xbb\x20\x93\x09\x5f\x05\x6d\xea\x87" + privateExponent = "\x87\xa7\x19\x32\xda\x11\x87\x55\x58\x00\x16\x16\x25\x65\x68\xf8\x24\x3e\xe6\xfa\xe9\x67\x49\x94\xcf\x92\xcc\x33\x99\xe8\x08\x60\x17\x9a\x12\x9f\x24\xdd\xb1\x24\x99\xc7\x3a\xb8\x0a\x7b\x0d\xdd\x35\x07\x79\x17\x0b\x51\x9b\xb3\xc7\x10\x01\x13\xe7\x3f\xf3\x5f" + publicExponent = "\x5b\x7b\x88\xc0" + + publicKeyBlob = Stream() + publicKeyBlob.writeType(certificate.wPublicKeyBlobType) + publicKeyBlob.writeType(certificate.wPublicKeyBlobLen) + publicKeyBlob.writeType(certificate.PublicKeyBlob) + + dwVersion = Stream() + dwVersion.writeType(UInt32Le(certificate.__class__._TYPE_)) + + dwSigAlgId = Stream() + dwSigAlgId.writeType(certificate.dwSigAlgId) + + dwKeyAlgId = Stream() + dwKeyAlgId.writeType(certificate.dwKeyAlgId) + md5Digest = md5.new() - md5Digest.update(s.getvalue()) + md5Digest.update(dwVersion.getvalue()) + md5Digest.update(dwSigAlgId.getvalue()) + md5Digest.update(dwKeyAlgId.getvalue()) + md5Digest.update(publicKeyBlob.getvalue()) + message = md5Digest.digest() + "\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01" - return rsa.encrypt(message, terminalServicesSigningKey()) + + return rsa.sign(message[::-1], rsa.PrivateKey(d = privateExponent[::-1], n = modulus[::-1])) def saltedHash(inputData, salt, salt1, salt2): """ @@ -179,7 +187,6 @@ def sessionKeyBlob(secret, random1, random2): """ return saltedHash("X", secret, random1, random2) + saltedHash("YY", secret, random1, random2) + saltedHash("ZZZ", secret, random1, random2) - def macData(macSaltKey, data): """ @see: http://msdn.microsoft.com/en-us/library/cc241995.aspx @@ -573,7 +580,7 @@ class Client(SecLayer): @summary: generate and send client random and init session keys """ #generate client random - clientRandom = rsa.randnum.read_random_bits(256) + clientRandom = rsa.random(256) self._macKey, self._initialDecrytKey, self._initialEncryptKey = generateKeys( clientRandom, self.getGCCServerSettings().SC_SECURITY.serverRandom.value, self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value) @@ -584,8 +591,7 @@ class Client(SecLayer): self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey) #send client random encrypted with - modulus, publicExponent = self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.getPublicKey() - serverPublicKey = rsa.PublicKey(modulus, publicExponent) + serverPublicKey = self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.getPublicKey() message = ClientSecurityExchangePDU() #reverse because bignum in little endian message.encryptedClientRandom.value = rsa.encrypt(clientRandom[::-1], serverPublicKey)[::-1] @@ -619,7 +625,7 @@ class Server(SecLayer): @param presentation: {Layer} """ SecLayer.__init__(self, presentation) - self._rsaPublicKey, self._rsaPrivaterKey = rsa.newkeys(512) + self._rsaPublicKey, self._rsaPrivateKey = rsa.newkeys(512) def connect(self): """ @@ -636,9 +642,9 @@ class Server(SecLayer): @summary: generate proprietary certificate from rsa public key """ certificate = gcc.ProprietaryServerCertificate() - certificate.PublicKeyBlob.modulus.value = hex(self._rsaPublicKey.n)[2:-1].decode('hex')[::-1] + certificate.PublicKeyBlob.modulus.value = rsa.int2bytes(self._rsaPublicKey.n)[::-1] certificate.PublicKeyBlob.pubExp.value = self._rsaPublicKey.e - certificate.SignatureBlob.value = hex(terminalServicesSign(certificate))[2:-1].decode('hex')[::-1] + certificate.SignatureBlob.value = terminalServicesSign(certificate)[::-1] + "\x00" * 8 return certificate def recvClientRandom(self, s): @@ -646,11 +652,19 @@ class Server(SecLayer): @summary: receive client random and generate session keys @param s: {Stream} """ + #packet preambule + securityFlag = UInt16Le() + securityFlagHi = UInt16Le() + s.readType((securityFlag, securityFlagHi)) + + if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT): + raise InvalidExpectedDataException("waiting client random") + message = ClientSecurityExchangePDU() s.readType(message) - clientRandom = rsa.decrypt(message.encryptedClientRandom.value, self._rsaPrivateKey) + clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1] - self._macKey, self._initialDecrytKey, self._initialEncryptKey = generateKeys( clientRandom, + self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom, self.getGCCServerSettings().SC_SECURITY.serverRandom.value, self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value) #initialize keys @@ -661,7 +675,6 @@ class Server(SecLayer): self.setNextState(self.recvInfoPkt) - def recvInfoPkt(self, s): """ @summary: receive info packet from client @@ -678,6 +691,9 @@ class Server(SecLayer): if not (securityFlag.value & SecurityFlag.SEC_INFO_PKT): raise InvalidExpectedDataException("Waiting info packet") + if securityFlag.value & SecurityFlag.SEC_ENCRYPT: + s = self.readEncryptedPayload(s) + s.readType(self._info) #next state send error license self.sendLicensingErrorMessage() diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index 65cc51e..0350f38 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -147,7 +147,7 @@ class TPKT(RawLayer, IFastPathSender): self.expect(2, self.readExtendedHeader) else: #is fast path packet - self._secFlag = version.value >> 6 + self._secFlag = ((version.value >> 6) & 0x3) data.readType(self._lastShortLength) if self._lastShortLength.value & 0x80: #size is 1 byte more @@ -207,4 +207,4 @@ class TPKT(RawLayer, IFastPathSender): @param fastPathS: type transform to stream and send as fastpath @param secFlag: {integer} Security flag for fastpath packet """ - RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | secFlag), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS)) \ No newline at end of file + RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS)) \ No newline at end of file diff --git a/rdpy/protocol/rdp/x224.py b/rdpy/protocol/rdp/x224.py index 26409c5..1287c6b 100644 --- a/rdpy/protocol/rdp/x224.py +++ b/rdpy/protocol/rdp/x224.py @@ -23,6 +23,7 @@ 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 log from rdpy.core.layer import LayerAutomata, IStreamSender from rdpy.core.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String @@ -131,7 +132,7 @@ class X224Layer(LayerAutomata, IStreamSender): """ LayerAutomata.__init__(self, presentation) #client requested selectedProtocol - self._requestedProtocol = Protocols.PROTOCOL_RDP | Protocols.PROTOCOL_SSL + self._requestedProtocol = Protocols.PROTOCOL_RDP #server selected selectedProtocol self._selectedProtocol = Protocols.PROTOCOL_SSL @@ -207,6 +208,7 @@ class Client(X224Layer): raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer") 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.transport.startTLS(ClientTLSContext()) @@ -262,6 +264,7 @@ class Server(X224Layer): #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 = ServerConnectionConfirm() message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE @@ -284,6 +287,7 @@ class Server(X224Layer): message.protocolNeg.selectedProtocol.value = self._selectedProtocol self._transport.send(message) if self._selectedProtocol == Protocols.PROTOCOL_SSL: + log.debug("*" * 5 + " select SSL layer") #_transport is TPKT and transport is TCP layer of twisted self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName)) diff --git a/rdpy/protocol/rfb/rfb.py b/rdpy/protocol/rfb/rfb.py index 50152ed..57341cb 100644 --- a/rdpy/protocol/rfb/rfb.py +++ b/rdpy/protocol/rfb/rfb.py @@ -28,7 +28,7 @@ Implement Remote FrameBuffer protocol use in VNC client and server from rdpy.core.layer import RawLayer, RawLayerClientFactory from rdpy.core.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType from rdpy.core.error import InvalidValue, CallPureVirtualFuntion -from rdpy.protocol.rfb.pyDes import des +from rdpy.security.pyDes import des import rdpy.core.log as log class ProtocolVersion(object): diff --git a/rdpy/security/__init__.py b/rdpy/security/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rdpy/protocol/rfb/pyDes.py b/rdpy/security/pyDes.py similarity index 100% rename from rdpy/protocol/rfb/pyDes.py rename to rdpy/security/pyDes.py diff --git a/rdpy/core/rc4.py b/rdpy/security/rc4.py similarity index 100% rename from rdpy/core/rc4.py rename to rdpy/security/rc4.py diff --git a/rdpy/security/rsa_wrapper.py b/rdpy/security/rsa_wrapper.py new file mode 100644 index 0000000..9472f8c --- /dev/null +++ b/rdpy/security/rsa_wrapper.py @@ -0,0 +1,90 @@ +# +# 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 . +# + +""" +Wrapper around RSA library +""" + +import rsa + +def newkeys(size): + """ + @summary: wrapper around rsa.newkeys function + @param size: {integer} size of key + """ + return rsa.newkeys(size) + +def PublicKey(e, n): + """ + @param e: {long | str}public exponent + @param n: {long | str}modulus + """ + if isinstance(e, str): + e = rsa.transform.bytes2int(e) + if isinstance(n, str): + n = rsa.transform.bytes2int(n) + return { 'e' : e, 'n' : n } + +def PrivateKey(d, n): + """ + @param d: {long | str}private exponent + @param n: {long | str}modulus + """ + if isinstance(d, str): + d = rsa.transform.bytes2int(d) + if isinstance(n, str): + n = rsa.transform.bytes2int(n) + return { 'd' : d, 'n' : n } + +def int2bytes(i): + """ + @summary: wrapper of rsa.transform.int2bytes + """ + return rsa.transform.int2bytes(i) + +def random(size): + """ + @summary: wrapper around rsa.randnum.read_random_bits function + @return: {str} random bytes array + """ + return rsa.randnum.read_random_bits(size) + +def encrypt(message, publicKey): + """ + @summary: wrapper around rsa.core.encrypt_int function + @param message: {str} source message + @param publicKey: {rsa.PublicKey} + """ + return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n'])) + +def decrypt(message, privateKey): + """ + @summary: wrapper around rsa.core.decrypt_int function + @param message: {str} source message + @param publicKey: {rsa.PrivateKey} + """ + return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n'])) + +def sign(message, privateKey): + """ + @summary: sign message with private key + @param message: {str} message to sign + @param privateKey : {rsa.privateKey} key use to sugn + """ + return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n'])) \ No newline at end of file diff --git a/rdpy/core/x509.py b/rdpy/security/x509.py similarity index 93% rename from rdpy/core/x509.py rename to rdpy/security/x509.py index 3bdcd42..a78b9f4 100644 --- a/rdpy/core/x509.py +++ b/rdpy/security/x509.py @@ -22,9 +22,7 @@ """ from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful -from pyasn1.codec.ber import decoder, encoder -from pyasn1 import error -from error import InvalidExpectedDataException +from pyasn1.codec.ber import decoder MAX = 64 @@ -149,9 +147,6 @@ def extractRSAKey(certificate): @return: (modulus, public exponent) """ #http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html - #disable check because nobody respect - #if certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('algorithm').getComponentByName('algorithm')._value != (1, 3, 14, 3, 2, 15): - # raise InvalidExpectedDataException("Certificate doesn't contain RSA public key") #extract binary data l = 0L