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

Added support for decrypting the oauth token sent from the Maltego client for transforms using OAuth. #8

Merged
merged 4 commits into from
Jul 3, 2020
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ build/
dist/
*.pyc
__pycache__
maltego_trx.egg-info
maltego_trx.egg-info
.pytest_cache
5 changes: 2 additions & 3 deletions demo/gunicorn/project.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import sys
import transforms

from maltego_trx.registry import register_transform_function, register_transform_classes
from maltego_trx.server import app, application
from maltego_trx.registry import register_transform_classes
from maltego_trx.server import app
from maltego_trx.handler import handle_run

# register_transform_function(transform_func)
register_transform_classes(transforms)

handle_run(__name__, sys.argv, app)
2 changes: 1 addition & 1 deletion maltego_trx/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "1.3.6"
VERSION = "1.3.7"
156 changes: 156 additions & 0 deletions maltego_trx/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""Maltego OAuth Crypto Helper"""
import base64
from Crypto import Random
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from requests.auth import AuthBase


class MaltegoOauth:
"""
A Crypto Helper for Maltego OAuth Secrets received from the Transform Distribution Server
The TDS will send back an encrypted combination of the following :
1. Token
2. Token Secret
3. Refresh Token
4. Expires In

Contains 1 Methods:
1. decrypt_secrets(private_key_path="pem file", ciphertext="request.getTransformSetting('name from TDS')")
"""

@staticmethod
def _rsa_decrypt(private_key_path=None, ciphertext=None):
"""
RSA Decryption function, returns decrypted plaintext in b64 encoding
"""
dsize = SHA.digest_size
sentinel = Random.new().read(20 + dsize)
ciphertext = base64.b64decode(ciphertext)
private_key = RSA.import_key(open(private_key_path).read())
cipher = PKCS1_v1_5.new(private_key)
plaintext = cipher.decrypt(ciphertext, sentinel).decode('utf8')
return plaintext

@staticmethod
def _aes_decrypt(key=None, ciphertext=None):
"""
AES Decryption function, returns decrypted plaintext value
"""
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
key = base64.b64decode(key)
ciphertext = base64.b64decode(ciphertext)
cipher = AES.new(key, AES.MODE_ECB)
plaintext = unpad(cipher.decrypt(ciphertext)).decode('utf8')
return plaintext

@classmethod
def decrypt_secrets(cls, private_key_path=None, encoded_ciphertext=None):
"""
The TDS will send back an encrypted combination of the following :
1. Token
2. Token Secret
3. Refresh Token
4. Expires In

This function decodes the combinations and decrypts as required and returns a dictionary with the following keys
{"token":"",
"token_secret": "",
"refresh_token": "",
"expires_in": ""}
"""

encrypted_fields = encoded_ciphertext.split("$")

if len(encrypted_fields) == 1:
token = cls._rsa_decrypt(private_key_path, encrypted_fields[0])
token_fields = {
"token": token
}

elif len(encrypted_fields) == 2:
token = cls._rsa_decrypt(private_key_path, encrypted_fields[0])
token_secret = cls._rsa_decrypt(private_key_path, encrypted_fields[1])
token_fields = {
"token": token,
"token_secret": token_secret
}

elif len(encrypted_fields) == 3:
aes_key = cls._rsa_decrypt(private_key_path, encrypted_fields[2])
token = cls._aes_decrypt(aes_key, encrypted_fields[0])
token_secret = cls._aes_decrypt(aes_key, encrypted_fields[1])
token_fields = {
"token": token,
"token_secret": token_secret
}
elif len(encrypted_fields) == 4:
token = cls._rsa_decrypt(private_key_path, encrypted_fields[0])
token_secret = cls._rsa_decrypt(private_key_path, encrypted_fields[1])
refresh_token = cls._rsa_decrypt(private_key_path, encrypted_fields[2])
expires_in = cls._rsa_decrypt(private_key_path, encrypted_fields[3])
token_fields = {
"token": token,
"token_secret": token_secret,
"refresh_token": refresh_token,
"expires_in": expires_in
}
elif len(encrypted_fields) == 5:
aes_key = cls._rsa_decrypt(private_key_path, encrypted_fields[4])
token = cls._aes_decrypt(aes_key, encrypted_fields[0])
token_secret = cls._aes_decrypt(aes_key, encrypted_fields[1])
refresh_token = cls._aes_decrypt(aes_key, encrypted_fields[2])
expires_in = cls._aes_decrypt(aes_key, encrypted_fields[3])
token_fields = {
"token": token,
"token_secret": token_secret,
"refresh_token": refresh_token,
"expires_in": expires_in
}
else:
token_fields = {
"token": "",
"token_secret": "",
"refresh_token": "",
"expires_in": ""
}

return token_fields


class OAuth2BearerToken(AuthBase):
"""Implements OAuth2 Bearer access token authentication.

Pass this object via the `auth` parameter to a request or a
session object in order to authenticate your requests.

Example usage, once you have the `access_token`:

class GreetPerson(DiscoverableTransform):

@classmethod
def create_entities(cls, request, response):
person_name = request.Value

private_key_path = "private_key.pem"

encrypted_secrets = request.getTransformSetting('maltego.web.api.key.linkedin')

token_fields = MaltegoCrypto.decrypt_secrets(private_key_path,encrypted_secrets)

api_url = ("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))")
auth = OAuth2BearerToken(token_fields['token'])
result = requests.get(api_url,auth=auth)

response.addEntity(Phrase, result.text)
"""

def __init__(self, access_token):
self.access_token = access_token

def __call__(self, request):
request.headers['Authorization'] = 'Bearer {}'.format(
self.access_token
)
return request
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
license='MIT',
install_requires=[
'flask>=1',
'six>=1'
'six>=1',
'pycryptodome>=3.9.7'
],
packages=[
'maltego_trx',
Expand Down
Empty file added tests/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from maltego_trx.oauth import MaltegoOauth

okta_ciphertoken = "pZ598ZEZ7EwpBQSOJSvCZJKkcWhtbX95K7Q0f0hwbk93O+xaUB4/NegK3r54PH1NReis/Jgt4UbGc5oCuU+R7UDM1icoDUmyCmV1U78iqjHElPdDBHlxPrl2zoXBwcU1iFbukDNy49Xghy3cwwqhmEXg/FKnYUlBQl6jdf06kE1pwfHiRgF5NrJISAD7eCmUybqiAVRDW4lbLeMGk/uSrrCpDQBxd2Em/sYKBzqO87pQRPLll23G9KNxyVinHQksGgc+dI0OPYv9EQcUs7g1JFraUKUeFjHAf/OyGGrp6WH84nGqG35afkH9xsDmySBb5DZWRjef8DzSd7oRagoGq3wKBHDh700mMKf/YkR8L9cK4l1w4yNh7EfIVlCWD5yWJUx4a1lN9CvJmFs7N9A903spVVGeP8avz50raWFb5g/4XhtIpei4ylVFi49dDeVjgb0BR0I4vuU7VDmcwFweAtSfclPb5Xfp98zI9yXnr8DB31gmzf3DIkVFIIFLxZe0gG+BSIEmC7/z0L7J+YvIqoGDq8jC0Ehe9IVXYfK1yluWAsbwj62rNAzkPVSe5l+FMQFrQRXpxZFBMXzphqRAhyYKfGNX/tocoMJoLFp8O3PBRC0kIHjTNaSX8KpftNYWreEdZx6wjk2n2eeOXM5nVeSOx4uoIifRe4NUV7VYzbGm3FSRc0k1HZIGHwn1WTvn95U3crvwFN86N3k41JU+0kzSpsLxy+Y63VfPxYTbRUET/wIOf0NTfG96QCPtVKkdVh9aTqnhaN0TpGoaZQEjUqpURxbj+8mT3g6MI4vOC9Qr6vw2wIkvzehr/yKxh6vsnfUUw8UJ1YtlZI7exPsHyFLUr2J3VvpDsaemRcn1KY/uHJZ3MMHK9GNciLA6Skw8SL38mw1tJJZ1h1bMMk1IYgBZLe2ZSXXyv7JaDPq/WJmHWDaIalmXE1cKMA56qMgYGdHxs3Pai5nwEtzIGVS1OcuMnBF6sOMMBTKSFAJmlcYo8NGwjKqe/yy1cVJZe7Ucv/jy/IS3AdE1my8cDya92jDsxifsaONQMm6YIs54SIA=$f7TU33wTTxYFtbVnwJeGGw==$c64l75qukq+980EkB1tQZKEicryt6HBB0IOjeMSRQmLOYCQpTcaXlPRJ3QdS+DfRocor7OL36wLAooRqz1IaN4QQ5+6Wx5reEXFVgKtaDX777I046DM5QU3Jf+iibSbm3mXYMw0z+OknbXyGLnc7ceSaPTJdP1LkCGQ75fLMXHCLWSpqwOKHOBhqyQGwrYlj2WxPzmOfAMaIkjKZIQWzoGjrYRzwkNCQbxykJcwD5TVuVDwAHyp84zcvW0WWoUZ+rrrooJwuJJQEdiTwLZsseqklXRNso4e5eFQwH49T9IDPHkKfVusu6rLiMNgFyc18rFR1d/BYLXBu7uzMAuvQ8wZdcOtJYx+JLJmOaPI65ymGNFTpwTHYnDBTXmpW0qX2dtEglAERw1nrdl94fwsa+I/iw3H7VIivqRF3pAchfvdNF75MIEm6XH2UXY/sZD7zjMdxxwKQiaEg2bpLd2mEtqsLc0mTq/zd03ZLEUnzXsXlp8brCfDgjoVsZAJH+ElQ6wr3dxzRMlDIxWWBYFcv8LnGVQk/Vciqtee780Yh/lgttv06kyXz81dIWjumz8TqV2eV9ZpP/YxTnNzAa91fleGNah/IKhMOV8PsGy2uOnHRiUL218p1T6+Cm9B2kmL1C/2MM3080NjJVWIfqYsyTGPbR+hURK0RB/oteG5QWPs="
private_key_path = "test_private_key.pem"


def testOktaDecrypt():
decrypted_linkedIn_token = MaltegoOauth.decrypt_secrets(private_key_path, okta_ciphertoken)['token']
expected_token = "eyJraWQiOiJ4ZzEyMmNZMXQ0NU5GM2l5YlUzUF9tWGluYkxEXzVrRHFwMkQ0VEl2RGd3IiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULklldnVNcXNJNDFqMURldEdPU1N2Q2VoNlI5eUxEQ0EydFBJdl9FZ3hDU3ciLCJpc3MiOiJodHRwczovL2Rldi02MTcyNDUub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiJhcGk6Ly9kZWZhdWx0IiwiaWF0IjoxNTg5NzgzNjU0LCJleHAiOjE1ODk3ODcyNTQsImNpZCI6IjBvYTR1YTZ1YlJIeFRDTGg4NHg2IiwidWlkIjoiMDB1NHU0ZzA4SDFqYXQwVnA0eDYiLCJzY3AiOlsib3BlbmlkIl0sInN1YiI6InRtQG1hbHRlZ28uY29tIn0.dAZrIZ9NCIqIx3fr8_0EexYCzarj8CC4CvWpVgZQCvfhtV08tGnMdWN8yuzADYvJUSzDz_meqIMMUCaVOpYZ5vzepr3LZfT-Xn00KoaaRcHGLPowphvsEPkpimvJqgnRmWw0e0VTH5Pfg9eyl3o2UUUsDofM-RkJNjxB4Uf0D6IyZaCyl0s_KhcXGZuh7hDGoR76UcKaCqRXqmqnOZs_GoMPIoDS5NHIze0MOK6sgKr8uiLikIhdh481n8PyWzPX2XZLUNcjogqX280so6Ki24VBloyxt5VgAJ1gV-e9SDj-9QSdQohQNDDSHbgiO4s1TUlhFKIm5UQWUImdhSuv1w"
assert decrypted_linkedIn_token == expected_token


def testReadKey():
private_key = open(private_key_path).read()
assert private_key.__sizeof__() == 3292


def testCipherSplit():
encrypted_fields = okta_ciphertoken.split("$")
assert len(encrypted_fields) == 3


def testToken_Field_Creation():
encrypted_fields = okta_ciphertoken.split("$")
aes_key = MaltegoOauth._rsa_decrypt(private_key_path, encrypted_fields[2])
token = MaltegoOauth._aes_decrypt(aes_key, encrypted_fields[0])
token_secret = MaltegoOauth._aes_decrypt(aes_key, encrypted_fields[1])
token_fields = {
"token": token,
"token_secret": token_secret
}
decrypted_azure_token_dict = MaltegoOauth.decrypt_secrets(private_key_path, okta_ciphertoken)
assert token_fields == decrypted_azure_token_dict
52 changes: 52 additions & 0 deletions tests/test_private_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
e+tSC0NMYgwe0MkkVArhPdYdAbQS
-----END PRIVATE KEY-----