crl-monitor/bin/x509/ip-ssl-subject-api.py
Alexandre Dulaunoy e4344dcfae cfetch/ API added to fetch and parse certificate from the datastore
Sample query:

curl http://127.0.0.1:8888/cfetch/37ffbb160d4c97c42f5126bebc9c18eeffe5ede3

{
  "pem": "-----BEGIN CERTIFICATE-----\nMIIEwTCCA6mgAwIBAgIJAIo7DnOg3SPpMA0GCSqGSIb3DQEBBQUAMIGbMQswCQYD\nVQQGEwItLTELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNVBAoT\nCU15Q29tcGFueTEOMAwGA1UECxMFTXlPcmcxHjAcBgNVBAMTFWxvY2FsaG9zdC5s\nb2NhbGRvbWFpbjEpMCcGCSqGSIb3DQEJARYacm9vdEBsb2NhbGhvc3QubG9jYWxk\nb21haW4wHhcNMTMxMDA5MDkzODU3WhcNMjMxMDA3MDkzODU3WjCBmzELMAkGA1UE\nBhMCLS0xCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRIwEAYDVQQKEwlN\neUNvbXBhbnkxDjAMBgNVBAsTBU15T3JnMR4wHAYDVQQDExVsb2NhbGhvc3QubG9j\nYWxkb21haW4xKTAnBgkqhkiG9w0BCQEWGnJvb3RAbG9jYWxob3N0LmxvY2FsZG9t\nYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmE+M/HAURvtG9JPc\nKndfyZ0UhGDHUg8Y+UHxKbOomscUh55EGkxdhdFeSyOTdugZ4eADf3ssCrvv0kop\nljay3yOI9Q3nWEMO4Zk0B5fA8XLuY4+pRPakskyJeoKHkY9tiIUxAaPCrwj2aiNF\nqnt0Cd9w2h0eAz1oaJNoXlOxINRFkyB2sfMg8e1XKxQFBrjK5fANqLd++HrWOeV3\nRxCf8pWJMBK4rTz8p0dDMWhaN1n66kP6qbUxwqtTe1YZ4t/Gy87u2g7WcI8XH9or\nZpqzdt5H+mswfKK/CIcGPqj5xx4ad+VvhMM+bijw5DMCttZA0Okv6T12nRuzFe9n\noJmwZQIDAQABo4IBBDCCAQAwHQYDVR0OBBYEFJswttZ8BCZz+JhJCjRueL3i9Qs4\nMIHQBgNVHSMEgcgwgcWAFJswttZ8BCZz+JhJCjRueL3i9Qs4oYGhpIGeMIGbMQsw\nCQYDVQQGEwItLTELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxEjAQBgNV\nBAoTCU15Q29tcGFueTEOMAwGA1UECxMFTXlPcmcxHjAcBgNVBAMTFWxvY2FsaG9z\ndC5sb2NhbGRvbWFpbjEpMCcGCSqGSIb3DQEJARYacm9vdEBsb2NhbGhvc3QubG9j\nYWxkb21haW6CCQCKOw5zoN0j6TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA\nA4IBAQBAECr0U7DZhuIZQW5aNlysJM1WIbAajnKyILthTXya18zcTsJQisn0zUc5\nl4obCj1xQ1krJOEupTE5miBRtvwhp4ymfBjLxLFT7R6rHO7/t5dZUPvXtkfK3QeY\nrtqb9vZSdKhfm+zzr7ra/N0XeWlgoja9+54Dtc3qZqzY1tUblDy3J2NBabOz7eF7\nf0jgHEbF+2CP20bhCltklGyA7U7m1qUS6bgKsGr/gfPL+ioDKPGNJTiPrfsD9YsN\nYyG05ZJ6RUpU1TNTOvcao29Yk2DLfriYgBIqi1oriFZYxX6TryUryhqVjGTi+Ksf\n4DX9WTUxVPEg8uYgUktztLGlRTK9\n-----END CERTIFICATE-----\n",
  "info": {
    "subject": "C=--, ST=WA, L=Seattle, O=MyCompany, OU=MyOrg, CN=localhost.localdomain/emailAddress=root@localhost.localdomain",
    "not_before": "2013-10-09T09:38:57+00:00",
    "issuer": "C=--, ST=WA, L=Seattle, O=MyCompany, OU=MyOrg, CN=localhost.localdomain/emailAddress=root@localhost.localdomain",
    "fingerprint": "16C25D401F35DD52FB4AEC85EB1F1A28CE16F961",
    "key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmE+M/HAURvtG9JPcKndf\nyZ0UhGDHUg8Y+UHxKbOomscUh55EGkxdhdFeSyOTdugZ4eADf3ssCrvv0kopljay\n3yOI9Q3nWEMO4Zk0B5fA8XLuY4+pRPakskyJeoKHkY9tiIUxAaPCrwj2aiNFqnt0\nCd9w2h0eAz1oaJNoXlOxINRFkyB2sfMg8e1XKxQFBrjK5fANqLd++HrWOeV3RxCf\n8pWJMBK4rTz8p0dDMWhaN1n66kP6qbUxwqtTe1YZ4t/Gy87u2g7WcI8XH9orZpqz\ndt5H+mswfKK/CIcGPqj5xx4ad+VvhMM+bijw5DMCttZA0Okv6T12nRuzFe9noJmw\nZQIDAQAB\n-----END PUBLIC KEY-----\n",
    "keylength": 2048,
    "not_after": "2023-10-07T09:38:57+00:00",
    "extension": {
      "basicConstraints": "CA:TRUE",
      "authorityKeyIdentifier": "keyid:9B:30:B6:D6:7C:04:26:73:F8:98:49:0A:34:6E:78:BD:E2:F5:0B:38\nDirName:/C=--/ST=WA/L=Seattle/O=MyCompany/OU=MyOrg/CN=localhost.localdomain/emailAddress=root@localhost.localdomain\nserial:8A:3B:0E:73:A0:DD:23:E9\n",
      "subjectKeyIdentifier": "9B:30:B6:D6:7C:04:26:73:F8:98:49:0A:34:6E:78:BD:E2:F5:0B:38"
    }
  }
}
2015-08-27 09:35:09 +00:00

182 lines
5.8 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Lookup IP or CIDR block for known fingerprints and X.509 subjects via HTTP API /query/149.13.30.0/24
# Lookup fingerprint of certificate where seen /cquery/16c25d401f35dd52fb4aec85eb1f1a28ce16f961
#
# Software is free software released under the GNU General Public License version 3 and later
#
# Copyright (c) 2015 Alexandre Dulaunoy - a@foo.be
import redis
import sys
import netaddr
import json
import re
import os
import M2Crypto
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8888, help="run on the given port", type=int)
certrepo = '/data/certs{}'
ipmaxsize = 512 #/23
servername = 'SSL Certificate API - https://github.com/adulau/crl-monitor'
def checksha1(value=False):
if value is False or len(value) != 40:
return False
try:
sha1int = int(value, 16)
except ValueError:
return False
return True
def bpath(ha=None, level=6):
if ha is None:
return False
fn = ""
for i in range(0, level*2, 2):
fn = fn + "/"+ ha[i:2+i]
return fn
class SSLQueryHandler(tornado.web.RequestHandler):
def get(self, input):
try:
#Redis structure Set of (FP) per IP
r = redis.StrictRedis(host='127.0.0.1', port=8323)
except:
print "Unable to connect to the Redis server"
sys.exit(255)
subnets = [input]
out = {}
for subnet in subnets:
if re.findall(r":", subnet):
self.clear()
self.set_status(400)
self.finish('IPv6 is not (yet) supported')
continue
try:
iplist = netaddr.IPNetwork(subnet)
except:
self.clear()
self.set_status(400)
self.finish('Incorrect format')
continue
if iplist.size > ipmaxsize:
self.clear()
self.set_status(400)
self.finish('Maximum CIDR block size reached >/23')
if not self._finished:
self.finish()
for ip in iplist:
s = r.smembers(ip)
if s:
out[str(ip)] = {}
out[str(ip)]['certificates'] = []
out[str(ip)]['subjects'] = {}
for fingerprint in s:
subjects = r.smembers(fingerprint)
out[str(ip)]['certificates'].append(fingerprint)
if subjects:
out[str(ip)]['subjects'][fingerprint] = {}
out[str(ip)]['subjects'][fingerprint]['values'] = []
for subject in subjects:
out[str(ip)]['subjects'][fingerprint]['values'].append(subject)
if not self._finished:
self.set_header('Content-Type', 'application/json')
self.set_header('Server', servername)
self.write(json.dumps(out))
class CertificateQueryHandler(tornado.web.RequestHandler):
def get(self, input):
try:
r = redis.StrictRedis(host='127.0.0.1', port=8323)
except:
print "Unable to connect to the Redis server"
sys.exit(255)
fp = input.lower()
if not checksha1(value=fp):
self.clear()
self.set_status(400)
self.finish('Incorrect format of the certificate fingerprint (expected SHA1 in hex format)')
out = {}
out['certificate'] = fp
out['seen'] = []
ips = r.smembers('s:{}'.format(fp))
out['hits'] = len(ips)
for ip in ips:
out['seen'].append(ip)
if not self._finished:
self.set_header('Content-Type', 'application/json')
self.set_header('Server', servername)
self.write(json.dumps(out))
class FetchCertificateHandler(tornado.web.RequestHandler):
def get(self, input):
try:
r = redis.StrictRedis(host='127.0.0.1', port=8323)
except:
print "Unable to connect to the Redis server"
sys.exit(255)
fp = input.lower()
if not checksha1(value=fp):
self.clear()
self.set_status(400)
self.finish('Incorrect format of the certificate fingerprint (expected SHA1 in hex format)')
certpath = bpath(ha=fp)
certpath = os.path.join(certpath, fp)
certpath = certrepo.format(certpath)
if not os.path.exists(certpath):
self.clear()
self.set_status(400)
self.finish('Not existing certificate')
cert = M2Crypto.X509.load_cert(certpath, M2Crypto.X509.FORMAT_DER)
out = {}
out['pem'] = cert.as_pem()
out['info'] = {}
out['info']['issuer'] = cert.get_issuer().as_text()
out['info']['subject'] = cert.get_subject().as_text()
out['info']['fingerprint'] = cert.get_fingerprint(md='sha1')
out['info']['keylength'] = cert.get_pubkey().get_rsa().__len__()
out['info']['key'] = cert.get_pubkey().get_rsa().as_pem()
out['info']['not_before'] = cert.get_not_before().get_datetime().isoformat()
out['info']['not_after'] = cert.get_not_after().get_datetime().isoformat()
out['info']['extension'] = {}
extcount = cert.get_ext_count()
for i in range(0, extcount):
out['info']['extension'][cert.get_ext_at(i).get_name()] = cert.get_ext_at(i).get_value()
if not self._finished:
self.set_header('Content-Type', 'application/json')
self.set_header('Server', servername)
self.write(json.dumps(out))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[
(r"/query/(.*)", SSLQueryHandler),
(r"/cquery/(.*)", CertificateQueryHandler),
(r"/cfetch/(.*)", FetchCertificateHandler)
])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()