| Current Path : /usr/bin/ |
| Current File : //usr/bin/openssl-vulnkey |
#!/usr/bin/python
#
# openssl-vulnkey: check a database of sha1'd static key hashes for
# known vulnerable keys
# Copyright (C) 2008-2009 Canonical Ltd.
# Author: Jamie Strandboge <jamie@canonical.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from optparse import OptionParser
import os
import re
import hashlib
import subprocess
import sys
import tempfile
import shutil
version = "0.5-2"
db_prefix = "/usr/share/openssl-blacklist/blacklist.RSA-"
db_lines = []
parser = OptionParser(usage="%prog FILE [FILE]", \
version="%prog: " + version, \
description="This program checks if FILEs are known " + \
"vulnerable static keys")
parser.add_option("-q", "--quiet", action="store_true", dest="quiet", \
help="be quiet")
parser.add_option("-b", "--bits", dest="bits", \
help="number of bits (requires -m)")
parser.add_option("-m", "--modulus", dest="modulus", \
help="modulus to check (requires -b)")
(options, args) = parser.parse_args()
if not ((options.modulus and options.bits) or args):
parser.print_help()
sys.exit(1)
def cmd(command, input = None, stderr = subprocess.PIPE, stdout = subprocess.PIPE, stdin = None):
'''Try to execute given command (array) and return its stdout, or return
a textual error if it failed.'''
try:
sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True)
except OSError, e:
return [127, str(e)]
out = sp.communicate(input)[0]
return [sp.returncode,out]
def get_contents(file):
'''Determine the type of certificate it is. Returns empty string if
unsupported.'''
args = ['-modulus', '-text', '-in', file]
rc, report = cmd(['openssl', 'rsa'] + args)
if rc == 0:
return ("rsa", report)
rc, report = cmd(['openssl', 'x509'] + args)
if rc == 0:
return ("x509", report)
rc, report = cmd(['openssl', 'req'] + args)
if rc == 0:
return ("req", report)
return ("", report)
def get_bits(contents, type):
'''Find bit length of file. Returns empty string if unsupported.'''
for line in contents:
leading = "Private-Key: "
if type == "x509" or type == "req":
leading = "RSA Public Key: "
# TODO: don't hardcode these
if leading + "(512" in contents:
return "512"
elif leading + "(1024" in contents:
return "1024"
elif leading + "(2048" in contents:
return "2048"
elif leading + "(4096" in contents:
return "4096"
elif leading + "(8192" in contents:
return "8192"
return ""
def get_modulus(contents):
'''Find modulus of file'''
for line in contents.split('\n'):
if re.match(r'^Modulus=', line):
return line + '\n'
return ""
def get_exponent(contents):
'''Find exponent of file. Returns empty string if unsupported.'''
if "Exponent: 65537 " in contents:
return "65537"
return ""
def check_db(bits, last, modulus, name=""):
'''Check modulus against database'''
global db_lines
if last != bits:
db = db_prefix + bits
# Read in the database
try:
fh = open(db, 'r')
except:
try:
print >> sys.stderr, "WARN: could not open database for %s " \
"bits. Skipped %s" % (bits, name)
except IOError:
pass
return False
db_lines = fh.read().split('\n')
fh.close()
key = hashlib.sha1(modulus).hexdigest()
#print "bits: %s\nmodulus: %s\nkey: %s\nkey80: %s" % (bits, modulus, key, key[20:])
if key[20:] in db_lines:
if not options.quiet:
print "COMPROMISED: %s %s" % (key, name)
return True
else:
if not options.quiet:
print "Not blacklisted: %s %s" % (key, name)
return False
last_bits = ""
found = False
error = False
if options.bits and options.modulus:
found = check_db(options.bits, last_bits, \
"Modulus=%s\n" % (options.modulus))
else:
# Check each file
for f in args:
realname = f
if f == "-":
# dump stdin to tmpfile, operate on tmpfile instead
temp = tempfile.NamedTemporaryFile()
shutil.copyfileobj(sys.stdin,temp)
temp.flush()
f = temp.name
try:
file(f).read()
except IOError, e:
if not options.quiet:
print >> sys.stderr, "ERROR: %s: %s" % (realname, e.strerror)
error = True
continue
(type, contents) = get_contents(f)
if type == "":
if not options.quiet:
print >> sys.stderr, "Skipped: '%s' is unsupported type " + \
"(not x509, req or rsa)" % (realname)
continue
exp = get_exponent(contents)
if exp == "":
if not options.quiet:
print >> sys.stderr, "Skipped: '%s' has unsupported exponent" % \
(realname)
continue
bits = get_bits(contents, type)
if bits == "":
if not options.quiet:
print >> sys.stderr, "Skipped: '%s' has unsupported bit size" % \
(realname)
continue
modulus = get_modulus(contents)
if modulus == "":
if not options.quiet:
print >> sys.stderr, "ERROR: %s: problem finding modulus" % \
(realname)
error = True
continue
if check_db(bits, last_bits, modulus, realname):
found = True
last_bits = bits
if found:
sys.exit(1)
elif error:
sys.exit(2)