Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaced pycrypto with pycryptodome #128

Merged
merged 4 commits into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ PyRDP was [first introduced in a blogpost](https://www.gosecure.net/blog/2018/12
- [Installing](#installing)
* [Installing with Docker](#installing-with-docker)
* [Installing on Windows](#installing-on-windows)
* [Migrating away from pycrypto](#Migrating-away-from-pycrypto)
- [Using the PyRDP Man-in-the-Middle](#using-the-pyrdp-man-in-the-middle)
* [Specifying the private key and certificate](#specifying-the-private-key-and-certificate)
* [Connecting to the PyRDP player](#connecting-to-the-pyrdp-player)
Expand Down Expand Up @@ -152,6 +153,24 @@ If you plan on using the player, X11 forwarding using an SSH connection would be
If you want to install PyRDP on Windows, note that `setup.py` will try to compile `ext/rle.c`, so you will need to have
a C compiler installed. You will also need to generate a private key and certificate to run the MITM.

### Migrating away from pycrypto
Since pycrypto isn't maintained anymore, we chose to migrate to pycryptodome.
If you get this error, it means that you are using the module pycrypto instead of pycryptodome.

```
[...]
File "[...]/pyrdp/pyrdp/pdu/rdp/connection.py", line 10, in <module>
from Crypto.PublicKey.RSA import RsaKey
ImportError: cannot import name 'RsaKey'
```

You will need to remove the module pycrypto and reinstall PyRDP.

```
pip3 uninstall pycrypto
pip3 install -U -e .
```

## Using the PyRDP Man-in-the-Middle
Use `pyrdp-mitm.py <ServerIP>` or `pyrdp-mitm.py <ServerIP>:<ServerPort>` to run the MITM.

Expand Down
4 changes: 3 additions & 1 deletion pyrdp/mitm/SecurityMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#

from logging import LoggerAdapter
from Crypto.PublicKey.RSA import RsaKey

from pyrdp.core import decodeUTF16LE
from pyrdp.enum import ClientInfoFlags, PlayerPDUType
Expand All @@ -14,6 +15,7 @@
from pyrdp.parser import ClientInfoParser
from pyrdp.pdu import SecurityExchangePDU
from pyrdp.recording import Recorder
from pyrdp.security.crypto import RSA


class SecurityMITM:
Expand Down Expand Up @@ -52,7 +54,7 @@ def onSecurityExchange(self, pdu: SecurityExchangePDU):
Set the security settings' client random from the security exchange.
:param pdu: the security exchange
"""
clientRandom = self.state.rc4RSAKey.decrypt(pdu.clientRandom[:: -1])[:: -1]
clientRandom = RSA(self.state.rc4RSAKey).decrypt(pdu.clientRandom[:: -1])[:: -1]
self.state.securitySettings.setClientRandom(clientRandom)

self.server.sendSecurityExchange(self.state.securitySettings.encryptClientRandom())
Expand Down
4 changes: 2 additions & 2 deletions pyrdp/parser/rdp/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def parseProprietaryCertificate(self, stream: BytesIO) -> ProprietaryCertificate

return ProprietaryCertificate(signatureAlgorithmID, keyAlgorithmID, publicKeyType, publicKey, signatureType, signature, padding)

def parsePublicKey(self, data: bytes) -> RSA.pubkey.pubkey:
def parsePublicKey(self, data: bytes) -> RSA.RsaKey:
stream = BytesIO(data)
_magic = stream.read(4)
keyLength = Uint32LE.unpack(stream)
Expand Down Expand Up @@ -475,7 +475,7 @@ def writeProprietaryCertificate(self, stream: BytesIO, cert: ProprietaryCertific
stream.write(cert.signature)
stream.write(b"\x00" * 8)

def writePublicKey(self, publicKey: RSA.pubkey.pubkey) -> bytes:
def writePublicKey(self, publicKey: RSA.RsaKey) -> bytes:
modulus = publicKey.n
publicExponent = publicKey.e

Expand Down
6 changes: 3 additions & 3 deletions pyrdp/pdu/rdp/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import socket
from typing import Optional

from Crypto.PublicKey import RSA
from Crypto.PublicKey.RSA import RsaKey

from pyrdp.enum import ChannelOption, ConnectionDataType, RDPVersion, ServerCertificateType, EncryptionLevel
from pyrdp.enum.rdp import ClientCapabilityFlag, ColorDepth, ConnectionType, DesktopOrientation, EncryptionMethod, \
Expand Down Expand Up @@ -161,14 +161,14 @@ def generate(serverSelectedProtocol: NegotiationProtocols,


class ServerCertificate:
def __init__(self, certificateType: ServerCertificateType, publicKey: RSA.pubkey.pubkey, signature: bytes):
def __init__(self, certificateType: ServerCertificateType, publicKey: RsaKey, signature: bytes):
self.type = certificateType
self.publicKey = publicKey
self.signature = signature


class ProprietaryCertificate(ServerCertificate):
def __init__(self, signatureAlgorithmID: int, keyAlgorithmID: int, publicKeyType: int, publicKey: RSA.pubkey.pubkey, signatureType: int, signature: bytes, padding: bytes):
def __init__(self, signatureAlgorithmID: int, keyAlgorithmID: int, publicKeyType: int, publicKey: RsaKey, signatureType: int, signature: bytes, padding: bytes):
ServerCertificate.__init__(self, ServerCertificateType.PROPRIETARY, publicKey, signature)
self.signatureAlgorithmID = signatureAlgorithmID
self.keyAlgorithmID = keyAlgorithmID
Expand Down
36 changes: 36 additions & 0 deletions pyrdp/security/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Licensed under the GPLv3 or later.
#

from Crypto.PublicKey.RSA import RsaKey
from Crypto.Util.number import bytes_to_long, long_to_bytes

from pyrdp.security import rc4
from pyrdp.security.key import macData, macSaltedData, generateKeys, updateKey
from pyrdp.enum import EncryptionMethod
Expand All @@ -12,6 +15,39 @@
Cryptographic utility functions
"""

# Adapted from https://github.com/dlitz/pycrypto/blob/master/lib/Crypto/PublicKey/_slowmath.py
class RSA:
"""
Class for encrypting or decrypting data with RSA
Not meant to be a safe implementation. We only need raw RSA without padding.
"""

def __init__(self, key: RsaKey):
self.key = key

def encrypt(self, plaintext: bytes) -> bytes:
m = bytes_to_long(plaintext)
ciphertext = pow(m, self.key.e, self.key.n)
return long_to_bytes(ciphertext)

def decrypt(self, ciphertext: bytes) -> bytes:
c = bytes_to_long(ciphertext)

# compute c**d (mod n)
if (hasattr(self.key, 'p') and hasattr(self.key, 'q') and hasattr(self.key, 'u')):
m1 = pow(c, self.key.d % (self.key.p - 1), self.key.p)
m2 = pow(c, self.key.d % (self.key.q - 1), self.key.q)
h = m2 - m1

if (h < 0):
h = h + self.key.q
h = h * self.key.u % self.key.q

plaintext = h * self.key.p + m1
else:
plaintext = pow(c, self.key.d, self.key.n)

return long_to_bytes(plaintext)

class RC4:
"""
Expand Down
11 changes: 6 additions & 5 deletions pyrdp/security/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
#

import Crypto.Random
from Crypto.PublicKey import RSA
from Crypto.PublicKey.RSA import RsaKey

from pyrdp.core import ObservedBy, Observer, Subject
from pyrdp.enum import EncryptionMethod
from pyrdp.exceptions import StateError
from pyrdp.security.crypto import RC4Crypter
from pyrdp.security.crypto import RC4Crypter, RSA


class SecuritySettingsObserver(Observer):
Expand Down Expand Up @@ -67,8 +67,9 @@ def encryptClientRandom(self) -> bytes:
"""
Encrypt the client random using the public key.
"""
# Client random is stored as little-endian but crypto functions expect it to be in big-endian format.
return self.serverPublicKey.encrypt(self.clientRandom[:: -1], 0)[0][:: -1]
# plaintext is stored as little-endian but crypto functions expect it to be in big-endian format.
return RSA(self.serverPublicKey).encrypt(self.clientRandom[:: -1])[:: -1]


def setEncryptionMethod(self, encryptionMethod: EncryptionMethod):
"""
Expand All @@ -77,7 +78,7 @@ def setEncryptionMethod(self, encryptionMethod: EncryptionMethod):
"""
self.encryptionMethod = encryptionMethod

def setServerPublicKey(self, serverPublicKey: RSA.pubkey.pubkey):
def setServerPublicKey(self, serverPublicKey: RsaKey):
"""
Set the server's public key.
:param serverPublicKey: the server's public key.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
'names',
'notify2',
'pyasn1',
'pycrypto',
'pycryptodome',
'pyopenssl',
'PySide2',
'pytz',
Expand Down