Skip to content

Commit

Permalink
Merge pull request #8 from paterva/oauth
Browse files Browse the repository at this point in the history
Added support for decrypting the oauth token sent from the Maltego client for transforms using OAuth.
  • Loading branch information
phdowling authored Jul 3, 2020
2 parents c7e8035 + adcbcde commit 47e9bf0
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 6 deletions.
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-----

0 comments on commit 47e9bf0

Please sign in to comment.