diff --git a/client/bin/pypssl b/client/bin/pypssl new file mode 100755 index 0000000..8c2b7a0 --- /dev/null +++ b/client/bin/pypssl @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import pypssl +import json + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Query a Passive SSL instance.') + parser.add_argument("--url", default='https://www.circl.lu/', help='URL where the passive SSL is running (no path).') + parser.add_argument("-v", "--version", type=int, default=2, help='URL where the passive SSL is running (no path).') + parser.add_argument("-u", "--username", help='Username to login on the platform.') + parser.add_argument("-p", "--password", help='Password to login on the platform.') + parser.add_argument("-t", "--token", help='Token to login on the platform.') + parser.add_argument("-i", "--ip", help='IP to query (can be a block, max /23).') + parser.add_argument("-c", "--cert", help='SHA1 of the certificate to search.') + parser.add_argument("-f", "--fetch", help='SHA1 of the certificate to fetch.') + args = parser.parse_args() + + p = pypssl.PyPSSL(args.url, args.version, (args.username, args.password), args.token) + + if args.ip is not None: + print(json.dumps(p.query(args.ip))) + elif args.cert is not None: + print(json.dumps(p.query_cert(args.cert))) + elif args.fetch is not None: + print(json.dumps(p.fetch_cert(args.fetch, make_datetime=False))) + else: + print('You didn\'t query anything...') diff --git a/client/pypssl/api.py b/client/pypssl/api.py index 818a980..48df9c6 100644 --- a/client/pypssl/api.py +++ b/client/pypssl/api.py @@ -3,13 +3,15 @@ import requests import socket +from urlparse import urljoin +import dateutil.parser class PyPSSL(object): - def __init__(self, url='https://www.circl.lu/pssl/query', basic_auth=None, - auth_token=None): - self.url = url + def __init__(self, base_url='https://www.circl.lu/', api_version=2, basic_auth=None, auth_token=None): + self.base_url = base_url + self.api_version = api_version self.session = requests.Session() if basic_auth is not None: @@ -21,6 +23,13 @@ class PyPSSL(object): # No authentication defined. pass + def _query(self, url): + response = self.session.get(url) + try: + return response.json() + except: + raise Exception('Unable to decode JSON object: ' + response.text) + def _check_IP(self, ip): if ':' in ip: return {'error': 'IPv6 is not (yet) supported'} @@ -39,9 +48,26 @@ class PyPSSL(object): check = self._check_IP(q) if check is not None: return check - response = self.session.get('{}/{}' .format(self.url, q)) - try: - return response.json() - except: - raise Exception('Unable to decode JSON object: ' + response.text) - return {} + if self.api_version == 1: + path = 'pssl/query/{}'.format(q) + else: + path = 'v2pssl/query/{}'.format(q) + return self._query(urljoin(self.base_url, path)) + + def query_cert(self, q): + if self.api_version != 2: + return {'error': 'Only available in API v2'} + path = 'v2pssl/cquery/{}'.format(q) + return self._query(urljoin(self.base_url, path)) + + def fetch_cert(self, q, make_datetime=True): + if self.api_version != 2: + return {'error': 'Only available in API v2'} + path = 'v2pssl/cfetch/{}'.format(q) + response = self._query(urljoin(self.base_url, path)) + if response.get('error') or not make_datetime: + return response + # create python datetime, doesn't return a json object + response['info']['not_before'] = dateutil.parser.parse(response['info']['not_before']) + response['info']['not_after'] = dateutil.parser.parse(response['info']['not_after']) + return response diff --git a/client/setup.py b/client/setup.py index 6435e0b..51900a6 100644 --- a/client/setup.py +++ b/client/setup.py @@ -4,7 +4,7 @@ from setuptools import setup setup( name='pypssl', - version='1.1', + version='2.0', author='Raphaël Vinot', author_email='raphael.vinot@circl.lu', maintainer='Raphaël Vinot', @@ -12,6 +12,7 @@ setup( description='Python API for PSSL.', long_description=open('README.md').read(), packages=['pypssl'], + scripts=['bin/pypssl'], classifiers=[ 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Development Status :: 5 - Production/Stable', @@ -23,6 +24,6 @@ setup( 'Topic :: Security', 'Topic :: Internet', ], - install_requires=['requests'], + install_requires=['requests', 'python-dateutil'], package_data={'': ['*.md', '*.rst', 'LICENSE']}, )