Skip to content

Commit

Permalink
Merge pull request #50 from EclecticIQ/fix-client-key-passphrase-for-…
Browse files Browse the repository at this point in the history
…jwt-auth

Enable client key passphrase for JWT token authentication request
  • Loading branch information
rjprins committed Mar 29, 2018
2 parents d8a4fbd + b87a152 commit fb0f73b
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 79 deletions.
33 changes: 9 additions & 24 deletions cabby/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ def prepare_generic_session(self):
password=self.password if not self.jwt_url else None,
cert_file=self.cert_file,
key_file=self.key_file,
verify_ssl=(self.ca_cert or self.verify_ssl))
key_password=self.key_password,
ca_cert=self.ca_cert,
verify_ssl=self.verify_ssl)

def _execute_request(self, request, uri=None, service_type=None):
'''
Expand All @@ -161,7 +163,6 @@ def _execute_request(self, request, uri=None, service_type=None):
A service is defined by ``uri`` parameter or is chosen from pre-cached
services by ``service_type``.
'''

if not uri and not service_type:
raise NoURIProvidedError('URI or service_type needed')
elif not uri:
Expand All @@ -181,28 +182,12 @@ def _execute_request(self, request, uri=None, service_type=None):
self.refresh_jwt_token(session=session)
session = dispatcher.set_jwt_token(session, self.jwt_token)

if self.key_password:
# If key_password is provided
message = dispatcher.send_taxii_request(
session,
self._prepare_url(uri),
request,
taxii_binding=self.taxii_binding,
# Details in case key_password is provided
tls_details={
'cert_file': self.cert_file,
'key_file': self.key_file,
'key_password': self.key_password,
'ca_cert': self.ca_cert
},
timeout=self.timeout)
else:
message = dispatcher.send_taxii_request(
session,
self._prepare_url(uri),
request,
taxii_binding=self.taxii_binding,
timeout=self.timeout)
message = dispatcher.send_taxii_request(
session,
self._prepare_url(uri),
request,
taxii_binding=self.taxii_binding,
timeout=self.timeout)

return message

Expand Down
132 changes: 77 additions & 55 deletions cabby/dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import namedtuple
import json
import os
import ssl
import sys
Expand Down Expand Up @@ -33,8 +34,8 @@ def raise_http_error(status_code, response_stream=None):
raise HTTPError(status_code)


def send_taxii_request(session, url, request, taxii_binding=None,
tls_details=None, timeout=None):
def send_taxii_request(
session, url, request, taxii_binding=None, timeout=None):
'''
Send XML message to a TAXII service and parse a response.
'''
Expand All @@ -50,12 +51,28 @@ def send_taxii_request(session, url, request, taxii_binding=None,
url_scheme=furl.furl(url).scheme,
message_binding=taxii_binding)

if tls_details and tls_details.get('key_password'):
stream, headers = request_stream(session, url, request_body, timeout)

gen = _parse_response(stream, headers, version=request.version)
obj = next(gen)

if obj == const.STREAM_MARKER:
return gen
elif hasattr(obj, 'status_type'):
if obj.status_type != 'SUCCESS':
raise UnsuccessfulStatusError(obj)
else:
return None
return obj


def request_stream(session, url, request_body, timeout, headers=None):
if session._cabby_key_password:
# Workaround until
# https://github.com/kennethreitz/requests/issues/2519 is fixed
try:
response = get_response_using_key_pass(
url, request_body, session, timeout=timeout, **tls_details)
response = request_with_key_password(
session, url, request_body, timeout, headers)
except urllib.error.HTTPError as e:
log.error(
"Error while connecting to {}".format(url),
Expand All @@ -64,8 +81,12 @@ def send_taxii_request(session, url, request, taxii_binding=None,

stream, headers = response, response.headers
else:
response = session.post(url, data=request_body, stream=True,
timeout=timeout)
response = session.post(
url,
data=request_body,
stream=True,
timeout=timeout,
headers=headers)
if not response.ok:
raise_http_error(response.status_code, response.raw)

Expand All @@ -81,17 +102,7 @@ def send_taxii_request(session, url, request, taxii_binding=None,

stream = gzip.GzipFile(fileobj=stream)

gen = _parse_response(stream, headers, version=request.version)
obj = next(gen)

if obj == const.STREAM_MARKER:
return gen
elif hasattr(obj, 'status_type'):
if obj.status_type != 'SUCCESS':
raise UnsuccessfulStatusError(obj)
else:
return None
return obj
return stream, headers


def _cleanup_batch(curr_elem, batch):
Expand Down Expand Up @@ -292,28 +303,32 @@ def __call__(self, r):
return r


def get_generic_session(proxies=None, headers=None,
username=None, password=None,
cert_file=None, key_file=None,
verify_ssl=True):
def get_generic_session(
proxies=None,
headers=None,
username=None,
password=None,
cert_file=None,
key_file=None,
key_password=None,
ca_cert=None,
verify_ssl=True):

session = requests.Session()
session.verify = verify_ssl

if ca_cert:
session.verify = ca_cert
else:
session.verify = verify_ssl
if proxies:
session.proxies = proxies

if headers:
session.headers.update(headers)

session.headers['User-Agent'] = 'Cabby {}'.format(cabby_version)
if username and password:
session.auth = HTTPBasicAuth(username, password)

session.headers['User-Agent'] = 'Cabby {}'.format(cabby_version)

if cert_file and key_file:
session.cert = (cert_file, key_file)

session._cabby_key_password = key_password
return session


Expand Down Expand Up @@ -351,60 +366,67 @@ def get_taxii_session(session, url_scheme='https', content_type=None,
return session


def obtain_jwt_token(session, jwt_url, username, password):
def obtain_jwt_token(session, jwt_url, username, password, timeout=None):
log.info("Obtaining JWT token from {}".format(jwt_url))

response = session.post(jwt_url, json={
'username': username,
'password': password
})
request_data = json.dumps({'username': username, 'password': password})
request_body = request_data.encode('utf-8')
headers = {'Content-Type': 'application/json'}

if not response.ok:
raise_http_error(response.status_code, response.raw)
stream, headers = request_stream(
session, jwt_url, request_body, timeout, headers)
response_body = stream.read().decode('utf-8')
response_data = json.loads(response_body)

body = response.json()
if 'token' not in body:
log.debug("Incorrect JWT response:\n{}".format(body))
if 'token' not in response_data:
log.debug("Incorrect JWT response:\n{}".format(response_body))
raise ValueError("No token found in JWT auth response")
return body['token']

return response_data['token']

def get_response_using_key_pass(url, data, session, cert_file, key_file,
key_password, ca_cert=None, timeout=None):

def request_with_key_password(
session, url, request_body, timeout=None, headers=None):
if sys.version_info < (2, 7, 9):
raise ValueError(
'Key password specification is not supported in Python < v2.7.9')

if session.auth:
# Using Requests Session's auth handlers to fill in proper headers
DummyRequest = namedtuple('DummyRequest', ['headers'])
headers = session.auth(DummyRequest(headers=session.headers)).headers
request_headers = session.auth(
DummyRequest(headers=session.headers)).headers
else:
headers = session.headers
request_headers = session.headers
if headers:
request_headers.update(headers)

# Take the TLS details from the session object and use them with urllib.
# See also 'get_generic_session' which sets many of these attributes.

# session 'verify' attribute can be a bool or a path to a CA bundle:
ca_cert = None
if not isinstance(session.verify, bool):
ca_cert = session.verify
context = ssl.create_default_context(
ssl.Purpose.CLIENT_AUTH, cafile=ca_cert)

cert_file, key_file = session.cert
key_password = session._cabby_key_password
context.load_cert_chain(cert_file, key_file, password=key_password)

if not session.verify and not ca_cert:
context.verify_mode = ssl.CERT_NONE
elif session.verify:
if session.verify:
context.verify_mode = ssl.CERT_REQUIRED

if not ca_cert:
context.set_default_verify_paths()
else:
context.verify_mode = ssl.CERT_NONE

handlers = [urllib.request.HTTPSHandler(context=context)]

if session.proxies:
handlers.append(
urllib.request.ProxyHandler(session.proxies))
handlers.append(urllib.request.ProxyHandler(session.proxies))

opener = urllib.request.build_opener(*handlers)

request = urllib.request.Request(url, data, headers)
request = urllib.request.Request(url, request_body, request_headers)

if timeout:
return opener.open(request, timeout=timeout)
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pytest-cov
setuptools-git
zest.releaser
flake8
werkzeug
30 changes: 30 additions & 0 deletions tests/ssl_test_files/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,B67E948DAB86C06E36B8BB6BA3EF27E9

D9JTUYv4cYUCdd9ZIs3yDdiCRr3ogsLWkXmHYiJdHmMGw/FDXSm+EkJKWPVMDDAp
hg6AaRVjtLHQwe8lPpD8COizOHgktXiWiZ1vkfz9Pyb4JHmiB6LY5zhnY8pb/2xx
2F8ZI3rbsVkIVUjeqN2PL+I8VM/FFZ2PPp4O57UnY2aDMJlscE0fvGrUrZpwGasz
CTr7S58r8w5cWqAR12J3Qw5crUxf/2YvcxJ1xlaLzVMnYUBVVTxrg6S+3jmsBq9F
fbz6GgxRSRoltSeV4WmCaeSrG4lwHx+yZLCHjaje5gijMfOuDq5MwVjS9FtiKnxq
ZnEDHFYEevBKFXwlPZYGQcDiTBuyet0n8UWpl1mXMc5gFzgEtz+QLWAc6Ws+Q9C5
+hXnbTWY3XRMGVR2qraUa3tTVolUrBdlwvf515oiy/H67OqVhIH5EmCvumA7yvjd
PFJdWlMSpLVHGeABYiez+S9n5qk2J5lGW6jjnt0uh+cR9ZllerQbhUgkCWs81xMV
e2FbUdRRdcSbc1QNLtEMtw6PaG5Rupcni8hLyZrR9c6RVQSSKDJimGXLS/hHlEn3
/uxbKM4kzFRLZbXiNB4kidyzVMLZePwc4FCtVzEHtmG6nKSydskCn/Q/KTMMTdb/
6lO33v+sxuLtx7tDmBhR1WqU0/uXEklUu7HBIfEt8dKpe1ZV/ox+LwvUQjdHN01m
REMv1s+q+wGRPiKYdukvXyfphi1rXRjAUzTXwD90fGpgCfiVXdcEO/XlP5sKlfdL
sbitpCciWcVuJmqKpKH6buYATlm0bOtE6+dWrdjnMSRyGSGjQFT4BZlfuCBP1C12
utl2jPRi44uiIVviezBvi54eplv+2OypP/muVQYebLw19H+fLtqV0m1yVtThP1cT
vURavF+ftLui87LCf7282g3y53JP7cU0QIVFruIVAPnw0Yw4rrCEptgN0tSCYv8V
mk/ACOAQ2JF69kS8+W+2MYPrwyt/Rwk7K0oWeX/tP8clp9wZ1bizGwkXgQcWiwvX
Axj+egWnGplI9EJh893uecFeEtvQf5UBtmVXgzGDcpUbZG7+VBftnEiWiOvPdGjj
/pY2FhXsmcDKFGFt3z3To6urMgVgwmq+o/viycs+VN2q45wxIFFEzY8AsIlaPtzP
q+LTGY4pyS4Lfyvt5WR+/pMyf494cdfFl6+H2gBgGOZYJzeYGehiryZWPl1CNCmF
ATEAjNZ6haEeZrXmy5YdlulBjrFkMpAbznmQdIRlTk8v/2IjpeIYecg+JkSnFM6t
r6fxTFFdZ9rlQmR3UNAcCkoQSDkN6MSWITz07ej26x+xlMSRHAVIPAwXiDrrqViR
G6cpWYp+MTzNaQvG/Yt2kcqWI7VrkoJ4DjoGlj/PkBCn+i6Q/fsIX19QsKr6wLRO
aPbDZN/2vBrjLY1DHmiYbXdVZ+oQhlTTBkGd+t/d335How4AsVPwaiMjrciDJiZz
VBs3TbytGfkRzHrIpLxgRQw52OLiPRAdA5J3ReS8pJ5dQgP17+maeC4eap18fYgR
KbqfigvYEeOofm1Lhc2rkyZEeXLq9St3Q0CN0+Gdej4G7tQbH0cITXG/2RqHeVaZ
-----END RSA PRIVATE KEY-----
23 changes: 23 additions & 0 deletions tests/ssl_test_files/client.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDvzCCAqcCCQDNW0hGu6KfqDANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC
TkwxFjAUBgNVBAgMDU5vcnRoLUhvbGxhbmQxEjAQBgNVBAcMCUFtc3RlcmRhbTET
MBEGA1UECgwKRWNsZWN0aWNJUTETMBEGA1UECwwKQ2FiYnkgVGVzdDETMBEGA1UE
AwwKY2FiYnktdGVzdDEjMCEGCSqGSIb3DQEJARYUY2FiYnlAZWNsZWN0aWNpcS5j
b20wHhcNMTgwMzI4MTI1NjUxWhcNMjEwMTE1MTI1NjUxWjCBpDELMAkGA1UEBhMC
TkwxFjAUBgNVBAgMDU5vcnRoLUhvbGxhbmQxEjAQBgNVBAcMCUFtc3RlcmRhbTET
MBEGA1UECgwKRWNsZWN0aWNJUTETMBEGA1UECwwKQ2FiYnkgVGVzdDEaMBgGA1UE
AwwRY2FiYnktdGVzdC1jbGllbnQxIzAhBgkqhkiG9w0BCQEWFGNhYmJ5QGVjbGVj
dGljaXEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzDmsgKh6
xSoTxyFkjbhQvoYZAQ7oniAN5u87KssY1Qml14b55oz2PO1BdY8DS/TEYJlW3xkB
NR10KGlbqnGyR0NnDjjd4E7KF+0ZV6rlF2d50judY/cibHXUp+N/qChJOyEzCKbD
uR7hPFsRicVW1LxyM2XW5s1t6ABsCCDsuQfxvTwumSzznzCTSeJaZQMB478R0eQx
RaOry28tgOYgSDCEWQjzjPiQIt1cl/CGz3qiLDNURRQIYEHl/3RwD29AWQHuycTN
SQXGdsJPA06wv7W3XQV7n+zt8YTA5d4tS9Gt6ZYpby0tovapxFvZSatpLrBl1/mm
wzArOXp8LDRmewIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCkaNcQHZT/BgIpaVUP
qsHyh5RkE/1tQwbFNYHnWrRcWnphcwZyGX7Oj1rpF5e2+YEt3ucpqKGOECfm/QKK
H9t5nCYE2KPBIiNhtiPUljAdejP0ysNqkuSLtkDd7sVJUGlldXsJ3pVPC1jKA4/p
XlY5FjdVo5QyIPPQ0x/oS8nmxyd/fybKtdO097Gb0m8SrgAHkUHM6osmaij9mSim
UPCHClUrP9VuGml4gK0XXfOiil8+DSdmUNX9Mf020lo9JUJFnne3Xku+jWL8RvtC
9Iwx0f4Twi7m45Vsp1jIXH5zQru62TEt8n5KJ6w0EzceyqzKeeeq75VWhodG5HaU
wdt7
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions tests/ssl_test_files/client_no_pass.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA0fYP7Q2xmfLoVNjtFsOQwTtYzQcvuR/3mxSXWDrDM/FcSgbX
spedvf26tzr8PX68YljJN9IwaRVx+20lykH8ZPKminYSht8RdcLUXH+vzpFqGFFd
edKsO+//apNw87KV6rYONp+nbE5HJ7GRiAj3Aop5EAtHEjANdJAizyYsMoJiImtP
f8doritQ11QSs59mTPsyRuM2ZRvu3CzloBEzx/ClTXSVAMP7jZFanxXcwnrJYSbL
/BKxrbPVyzPYBeMjx5V9GUg/YZcDC62C+s0x1CQjacK9meSIx7sXRap3OPFbZ/9c
/OWJiTxx6lzywecoELRX/nfDqsr1gn4/v+GvXwIDAQABAoIBAA7fIO2nhKbk37Py
0YhFPeGR9I5BLg4sx3sCkF29e8oYFHNh3LFXr+KfMDR+RxOMAIfuxgHgL3GiBTrL
1ltnJRt5XHZ+On23GyN+M1CB8s/s2Nj0GmzgkTaFn0/LNbrtMVU4o/UWheNUABI9
r5M1H1ncuQp74gLVyH4zH2QQzhydorho/RQ4C8wHIm/yUu2RlrlmoV/CCtuFql7Q
EOCwfuf3wom/LUbL2mFNdqYg2LS5ChzFps69zeiO7LF7cppqGAM6hQmtvacmUC7z
H1l1Qn6c7uWxI4zezvEF+zEYg2bZhV7Bm2G4k0H5NyC3lP9DD9OjAem9SQaVtO46
b7MLrzECgYEA8ypZBcT5YuKH6v5M/ndzY5tek2PrTuOuzkNY9XC3NgOafjyunbxR
o7kn6z6ttpaV8nr43PP5k5FiWyek9FxZp893myEWbxpCZ4s8iKGQs13ICqb3NhAb
kmLqEeaHkgqGt2pISOnwKwq323wWhl9iYOlipgpLa2E5aBTGOAAq/esCgYEA3QsP
AdwNcVTL9irThaBnVZUw0UViv5oaoYIVJBfkjAZeLSi5Oar1+SGErmD8c/RtrB07
FRABemjRk1Kz1Y1DM4qP6ldWlFaNqvvm+sH8MXtazNeHaFLWUZ5zeh33hV48FUBH
5CG9nXGWSOU/KNYbYk2oFS4fnlVUeXfmsKSBE10CgYBfU46qsFmD5oKaIS9V4sYd
ml8tMNKijqeMvOI29gUc67S5IFjkBVuL754yntPC7K3D7Wl6VTrWGvyP9663DS0o
mDCvY/1DeOvnY7JLbesoJe+yHVp0m0Pz00sn0VP8cJv3c3b6/prkhMMnDDJYYzRS
Aaxmo0qFwgubPemnMomoGQKBgF2OAqoM0vu2oiTsd15FR7cnT+Qi8+qYdNEK15vR
KQBC6bU/WTYZL8Zj815C2lbRi2GfdZQylA2VM66hAFBQW7MC4GqU0KY0A/3sZBSZ
6f2fcgzk5AC5ntAoukNjT5H5EoEEFeluhmyO2Ma9kH/eLvTqXUupm+RNxVUr9E2r
Mp5ZAoGAJWKK/jR2Bl/f/mNasbmoxs86wwCAHLd8hJ3YMxiN0sRkQfroV/+9En2H
LhWqQHIb+lQQzECwnayf4K+SxQUoR5GxOzLe8QIzcsaFfmSjA7pxlIqvzXBVCQKy
/kNpbVHGK/SsA0/YDdFA5XA9lO23+dL0lxsoptScrHP0cTKoMtI=
-----END RSA PRIVATE KEY-----
23 changes: 23 additions & 0 deletions tests/ssl_test_files/client_no_pass.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwTCCAqkCCQDNW0hGu6KfqTANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC
TkwxFjAUBgNVBAgMDU5vcnRoLUhvbGxhbmQxEjAQBgNVBAcMCUFtc3RlcmRhbTET
MBEGA1UECgwKRWNsZWN0aWNJUTETMBEGA1UECwwKQ2FiYnkgVGVzdDETMBEGA1UE
AwwKY2FiYnktdGVzdDEjMCEGCSqGSIb3DQEJARYUY2FiYnlAZWNsZWN0aWNpcS5j
b20wHhcNMTgwMzI5MDkwODEzWhcNMjEwMTE2MDkwODEzWjCBpjELMAkGA1UEBhMC
TkwxFjAUBgNVBAgMDU5vcnRoLUhvbGxhbmQxEjAQBgNVBAcMCUFtc3RlcmRhbTET
MBEGA1UECgwKRWNsZWN0aWNJUTEOMAwGA1UECwwFQ2FiYnkxITAfBgNVBAMMGGNh
YmJ5LXRlc3Qtbm8tcGFzc3BocmFzZTEjMCEGCSqGSIb3DQEJARYUY2FiYnlAZWNs
ZWN0aWNpcS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDR9g/t
DbGZ8uhU2O0Ww5DBO1jNBy+5H/ebFJdYOsMz8VxKBteyl529/bq3Ovw9frxiWMk3
0jBpFXH7bSXKQfxk8qaKdhKG3xF1wtRcf6/OkWoYUV150qw77/9qk3DzspXqtg42
n6dsTkcnsZGICPcCinkQC0cSMA10kCLPJiwygmIia09/x2iuK1DXVBKzn2ZM+zJG
4zZlG+7cLOWgETPH8KVNdJUAw/uNkVqfFdzCeslhJsv8ErGts9XLM9gF4yPHlX0Z
SD9hlwMLrYL6zTHUJCNpwr2Z5IjHuxdFqnc48Vtn/1z85YmJPHHqXPLB5ygQtFf+
d8OqyvWCfj+/4a9fAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACh1tE0dIfOF0Mi2
52976pChfkUGNlVmCbkI8UdwcPq9W3a298s8xbg2s+BnxBZhdRgF4xRepq4Db3h9
S7f9iQ0iCHN61yh0UNS/NgsM/ALGhHFCv1sP8ajs3vMDa4P4X5xoBavc+Pf+TKIm
CFq/yfXhnMXzfTvLh82zzaE8LM3x+IpthxLkq3F80OXNgyRs5gVFI3poln8TMWiB
I1PnrzBRJcHaod67Mly/iHjH0Zwy6jgkEgrbWLmzkmUXU2kEryt0HlaGO4pj5/2A
CeZqZxjgzp5NSkdFUEauHYH6bzwhSv+BT0jZOgm5+zt/lOGU8a1Y+BJmc2UhgWQ1
Rhf+7yc=
-----END CERTIFICATE-----
Loading

0 comments on commit fb0f73b

Please sign in to comment.