Imported Debian patch 4.0.5-6~numeezy

This commit is contained in:
Alexandre Ellert
2016-02-17 15:07:45 +01:00
committed by Mario Fetka
parent c44de33144
commit 10dfc9587b
1203 changed files with 53869 additions and 241462 deletions

View File

@@ -19,6 +19,7 @@
import os
import stat
import re
import sys
import tempfile
import shutil
@@ -26,30 +27,44 @@ import xml.dom.minidom
import pwd
import base64
from hashlib import sha1
import fcntl
import time
import datetime
from six.moves import configparser
from nss import nss
from nss.error import NSPRError
from ipapython.ipa_log_manager import root_logger
from ipapython import dogtag
from ipapython import sysrestore
from ipapython import ipautil
from ipapython import certmonger
from ipapython.certdb import get_ca_nickname, find_cert_from_txt, NSSDatabase
from ipapython.certdb import get_ca_nickname
from ipapython.dn import DN
from ipalib import pkcs10, x509, api
from ipalib.errors import CertificateOperationError
from ipalib.text import _
from ipaplatform import services
from ipaplatform.constants import constants
from ipaplatform.paths import paths
# Apache needs access to this database so we need to create it
# where apache can reach
NSS_DIR = paths.HTTPD_ALIAS_DIR
def find_cert_from_txt(cert, start=0):
"""
Given a cert blob (str) which may or may not contian leading and
trailing text, pull out just the certificate part. This will return
the FIRST cert in a stream of data.
Returns a tuple (certificate, last position in cert)
"""
s = cert.find('-----BEGIN CERTIFICATE-----', start)
e = cert.find('-----END CERTIFICATE-----', s)
if e > 0: e = e + 25
if s < 0 or e < 0:
raise RuntimeError("Unable to find certificate")
cert = cert[s:e]
return (cert, e)
def get_cert_nickname(cert):
"""
@@ -68,6 +83,213 @@ def get_cert_nickname(cert):
return (str(dn[0]), dn)
class NSSDatabase(object):
"""A general-purpose wrapper around a NSS cert database
For permanent NSS databases, pass the cert DB directory to __init__
For temporary databases, do not pass nssdir, and call close() when done
to remove the DB. Alternatively, a NSSDatabase can be used as a
context manager that calls close() automatically.
"""
# Traditionally, we used CertDB for our NSS DB operations, but that class
# got too tied to IPA server details, killing reusability.
# BaseCertDB is a class that knows nothing about IPA.
# Generic NSS DB code should be moved here.
def __init__(self, nssdir=None):
if nssdir is None:
self.secdir = tempfile.mkdtemp()
self._is_temporary = True
else:
self.secdir = nssdir
self._is_temporary = False
def close(self):
if self._is_temporary:
shutil.rmtree(self.secdir)
def __enter__(self):
return self
def __exit__(self, type, value, tb):
self.close()
def run_certutil(self, args, stdin=None):
new_args = [paths.CERTUTIL, "-d", self.secdir]
new_args = new_args + args
return ipautil.run(new_args, stdin)
def create_db(self, password_filename):
"""Create cert DB
:param password_filename: Name of file containing the database password
"""
self.run_certutil(["-N", "-f", password_filename])
def list_certs(self):
"""Return nicknames and cert flags for all certs in the database
:return: List of (name, trust_flags) tuples
"""
certs, stderr, returncode = self.run_certutil(["-L"])
certs = certs.splitlines()
# FIXME, this relies on NSS never changing the formatting of certutil
certlist = []
for cert in certs:
nickname = cert[0:61]
trust = cert[61:]
if re.match(r'\w*,\w*,\w*', trust):
certlist.append((nickname.strip(), trust.strip()))
return tuple(certlist)
def find_server_certs(self):
"""Return nicknames and cert flags for server certs in the database
Server certs have an "u" character in the trust flags.
:return: List of (name, trust_flags) tuples
"""
server_certs = []
for name, flags in self.list_certs():
if 'u' in flags:
server_certs.append((name, flags))
return server_certs
def get_trust_chain(self, nickname):
"""Return names of certs in a given cert's trust chain
:param nickname: Name of the cert
:return: List of certificate names
"""
root_nicknames = []
chain, stderr, returncode = self.run_certutil([
"-O", "-n", nickname])
chain = chain.splitlines()
for c in chain:
m = re.match('\s*"(.*)" \[.*', c)
if m:
root_nicknames.append(m.groups()[0])
return root_nicknames
def import_pkcs12(self, pkcs12_filename, db_password_filename,
pkcs12_passwd=None):
args = [paths.PK12UTIL, "-d", self.secdir,
"-i", pkcs12_filename,
"-k", db_password_filename, '-v']
if pkcs12_passwd is not None:
pkcs12_passwd = pkcs12_passwd + '\n'
args = args + ["-w", paths.DEV_STDIN]
try:
ipautil.run(args, stdin=pkcs12_passwd)
except ipautil.CalledProcessError, e:
if e.returncode == 17:
raise RuntimeError("incorrect password for pkcs#12 file %s" %
pkcs12_filename)
elif e.returncode == 10:
raise RuntimeError("Failed to open %s" % pkcs12_filename)
else:
raise RuntimeError("unknown error import pkcs#12 file %s" %
pkcs12_filename)
def trust_root_cert(self, root_nickname):
if root_nickname[:7] == "Builtin":
root_logger.debug(
"No need to add trust for built-in root CAs, skipping %s" %
root_nickname)
else:
try:
self.run_certutil(["-M", "-n", root_nickname,
"-t", "CT,CT,"])
except ipautil.CalledProcessError, e:
raise RuntimeError(
"Setting trust on %s failed" % root_nickname)
def get_cert(self, nickname, pem=False):
args = ['-L', '-n', nickname]
if pem:
args.append('-a')
else:
args.append('-r')
try:
cert, err, returncode = self.run_certutil(args)
except ipautil.CalledProcessError:
raise RuntimeError("Failed to get %s" % nickname)
return cert
def export_pem_cert(self, nickname, location):
"""Export the given cert to PEM file in the given location"""
cert = self.get_cert(nickname)
with open(location, "w+") as fd:
fd.write(cert)
os.chmod(location, 0444)
def import_pem_cert(self, nickname, flags, location):
"""Import a cert form the given PEM file.
The file must contain exactly one certificate.
"""
try:
with open(location) as fd:
certs = fd.read()
except IOError as e:
raise RuntimeError(
"Failed to open %s: %s" % (location, e.strerror)
)
cert, st = find_cert_from_txt(certs)
self.add_single_pem_cert(nickname, flags, cert)
try:
find_cert_from_txt(certs, st)
except RuntimeError:
pass
else:
raise ValueError('%s contains more than one certificate' %
location)
def add_single_pem_cert(self, nick, flags, cert):
"""Import a cert in PEM format"""
self.run_certutil(["-A", "-n", nick,
"-t", flags,
"-a"],
stdin=cert)
def delete_cert(self, nick):
self.run_certutil(["-D", "-n", nick])
def verify_server_cert_validity(self, nickname, hostname):
"""Verify a certificate is valid for a SSL server with given hostname
Raises a ValueError if the certificate is invalid.
"""
certdb = cert = None
nss.nss_init(self.secdir)
try:
certdb = nss.get_default_certdb()
cert = nss.find_cert_from_nickname(nickname)
intended_usage = nss.certificateUsageSSLServer
try:
approved_usage = cert.verify_now(certdb, True, intended_usage)
except NSPRError, e:
if e.errno != -8102:
raise ValueError(e.strerror)
approved_usage = 0
if not approved_usage & intended_usage:
raise ValueError('invalid for a SSL server')
if not cert.verify_hostname(hostname):
raise ValueError('invalid for server %s' % hostname)
finally:
del certdb, cert
nss.nss_shutdown()
return None
class CertDB(object):
"""An IPA-server-specific wrapper around NSS
@@ -97,7 +319,7 @@ class CertDB(object):
self.subject_base = subject_base
try:
self.cwd = os.getcwd()
except OSError as e:
except OSError, e:
raise RuntimeError("Unable to determine the current directory: %s" % str(e))
if not subject_base:
@@ -164,8 +386,8 @@ class CertDB(object):
def gen_password(self):
return sha1(ipautil.ipa_generate_password()).hexdigest()
def run_certutil(self, args, stdin=None, **kwargs):
return self.nssdb.run_certutil(args, stdin, **kwargs)
def run_certutil(self, args, stdin=None):
return self.nssdb.run_certutil(args, stdin)
def run_signtool(self, args, stdin=None):
with open(self.passwd_fname, "r") as f:
@@ -228,12 +450,11 @@ class CertDB(object):
do that step."""
# export the CA cert for use with other apps
ipautil.backup_file(self.cacert_fname)
root_nicknames = self.find_root_cert(nickname)[:-1]
root_nicknames = self.find_root_cert(nickname)
fd = open(self.cacert_fname, "w")
for root in root_nicknames:
result = self.run_certutil(["-L", "-n", root, "-a"],
capture_output=True)
fd.write(result.output)
(cert, stderr, returncode) = self.run_certutil(["-L", "-n", root, "-a"])
fd.write(cert)
fd.close()
os.chmod(self.cacert_fname, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
if create_pkcs12:
@@ -245,7 +466,7 @@ class CertDB(object):
"-k", self.passwd_fname])
self.set_perms(self.pk12_fname)
def load_cacert(self, cacert_fname, trust_flags):
def load_cacert(self, cacert_fname):
"""
Load all the certificates from a given file. It is assumed that
this file creates CA certificates.
@@ -264,7 +485,7 @@ class CertDB(object):
nick = get_ca_nickname(self.realm)
else:
nick = str(subject_dn)
self.nssdb.add_cert(cert, nick, trust_flags, pem=True)
self.nssdb.add_single_pem_cert(nick, "CT,,C", cert)
except RuntimeError:
break
@@ -277,8 +498,7 @@ class CertDB(object):
"""
try:
args = ["-L", "-n", nickname, "-a"]
result = self.run_certutil(args, capture_output=True)
cert = result.output
(cert, err, returncode) = self.run_certutil(args)
if pem:
return cert
else:
@@ -297,10 +517,14 @@ class CertDB(object):
/usr/lib[64]/ipa/certmonger.
"""
if command is not None and not os.path.isabs(command):
command = paths.CERTMONGER_COMMAND_TEMPLATE % (command)
if sys.maxsize > 2**32L:
libpath = 'lib64'
else:
libpath = 'lib'
command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, command)
try:
request_id = certmonger.start_tracking(nickname, self.secdir, password_file, command)
except RuntimeError as e:
except RuntimeError, e:
root_logger.error("certmonger failed starting to track certificate: %s" % str(e))
return
@@ -316,7 +540,7 @@ class CertDB(object):
"""
try:
certmonger.stop_tracking(self.secdir, nickname=nickname)
except RuntimeError as e:
except RuntimeError, e:
root_logger.error("certmonger failed to stop tracking certificate: %s" % str(e))
def create_server_cert(self, nickname, hostname, other_certdb=None, subject=None):
@@ -335,7 +559,7 @@ class CertDB(object):
subject=DN(('CN', hostname), self.subject_base)
self.request_cert(subject)
cdb.issue_server_cert(self.certreq_fname, self.certder_fname)
self.import_cert(self.certder_fname, nickname)
self.add_cert(self.certder_fname, nickname)
fd = open(self.certder_fname, "r")
dercert = fd.read()
fd.close()
@@ -353,7 +577,7 @@ class CertDB(object):
subject=DN(('CN', hostname), self.subject_base)
self.request_cert(subject)
cdb.issue_signing_cert(self.certreq_fname, self.certder_fname)
self.import_cert(self.certder_fname, nickname)
self.add_cert(self.certder_fname, nickname)
os.unlink(self.certreq_fname)
os.unlink(self.certder_fname)
@@ -368,10 +592,9 @@ class CertDB(object):
"-z", self.noise_fname,
"-f", self.passwd_fname,
"-a"]
result = self.run_certutil(args,
capture_output=True, capture_error=True)
(stdout, stderr, returncode) = self.run_certutil(args)
os.remove(self.noise_fname)
return (result.output, result.error_output)
return (stdout, stderr)
def issue_server_cert(self, certreq_fname, cert_fname):
self.setup_cert_request()
@@ -387,7 +610,7 @@ class CertDB(object):
# We just want the CSR bits, make sure there is nothing else
csr = pkcs10.strip_header(csr)
params = {'profileId': dogtag.DEFAULT_PROFILE,
params = {'profileId': 'caIPAserviceCert',
'cert_request_type': 'pkcs10',
'requestor_name': 'IPA Installer',
'cert_request': csr,
@@ -398,13 +621,17 @@ class CertDB(object):
password = f.readline()
f.close()
result = dogtag.https_request(
self.host_name, 8443, "/ca/ee/ca/profileSubmitSSLClient",
self.host_name,
api.env.ca_ee_install_port or
dogtag.configured_constants().EE_SECURE_PORT,
"/ca/ee/ca/profileSubmitSSLClient",
self.secdir, password, "ipaCert", **params)
http_status, http_headers, http_body = result
http_status, http_reason_phrase, http_headers, http_body = result
if http_status != 200:
raise CertificateOperationError(
error=_('Unable to communicate with CMS (status %d)') % http_status)
error=_('Unable to communicate with CMS (%s)') %
http_reason_phrase)
# The result is an XML blob. Pull the certificate out of that
doc = xml.dom.minidom.parseString(http_body)
@@ -451,9 +678,12 @@ class CertDB(object):
password = f.readline()
f.close()
result = dogtag.https_request(
self.host_name, 8443, "/ca/ee/ca/profileSubmitSSLClient",
self.host_name,
api.env.ca_ee_install_port or
dogtag.configured_constants().EE_SECURE_PORT,
"/ca/ee/ca/profileSubmitSSLClient",
self.secdir, password, "ipaCert", **params)
http_status, http_headers, http_body = result
http_status, http_reason_phrase, http_headers, http_body = result
if http_status != 200:
raise RuntimeError("Unable to submit cert request")
@@ -472,10 +702,7 @@ class CertDB(object):
f.write(cert)
f.close()
def add_cert(self, cert, nick, flags, pem=False):
self.nssdb.add_cert(cert, nick, flags, pem)
def import_cert(self, cert_fname, nickname):
def add_cert(self, cert_fname, nickname):
"""
Load a certificate from a PEM file and add minimal trust.
"""
@@ -512,7 +739,8 @@ class CertDB(object):
f.write(pwdfile.read())
f.close()
pwdfile.close()
self.set_perms(self.pwd_conf, uid=constants.HTTPD_USER)
# TODO: replace explicit uid by a platform-specific one
self.set_perms(self.pwd_conf, uid="apache")
def find_root_cert(self, nickname):
"""
@@ -523,13 +751,13 @@ class CertDB(object):
return root_nicknames
def trust_root_cert(self, root_nickname, trust_flags=None):
def trust_root_cert(self, root_nickname):
if root_nickname is None:
root_logger.debug("Unable to identify root certificate to trust. Continuing but things are likely to fail.")
return
try:
self.nssdb.trust_root_cert(root_nickname, trust_flags)
self.nssdb.trust_root_cert(root_nickname)
except RuntimeError:
pass
@@ -578,10 +806,10 @@ class CertDB(object):
# a new certificate database.
self.create_passwd_file(passwd)
self.create_certdbs()
self.load_cacert(cacert_fname, 'CT,C,C')
self.load_cacert(cacert_fname)
def create_from_pkcs12(self, pkcs12_fname, pkcs12_passwd, passwd=None,
ca_file=None, trust_flags=None):
ca_file=None):
"""Create a new NSS database using the certificates in a PKCS#12 file.
pkcs12_fname: the filename of the PKCS#12 file
@@ -603,31 +831,19 @@ class CertDB(object):
raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_fname)
if ca_file:
try:
with open(ca_file) as fd:
certs = fd.read()
except IOError as e:
raise RuntimeError(
"Failed to open %s: %s" % (ca_file, e.strerror))
st = 0
num = 1
while True:
try:
cert, st = find_cert_from_txt(certs, st)
except RuntimeError:
break
self.add_cert(cert, 'CA %s' % num, ',,', pem=True)
num += 1
self.nssdb.import_pem_cert('CA', 'CT,CT,', ca_file)
# We only handle one server cert
nickname = server_certs[0][0]
ca_names = self.find_root_cert(nickname)[:-1]
ca_names = [name for name, flags
in self.nssdb.list_certs() if 'u' not in flags]
if len(ca_names) == 0:
raise RuntimeError("Could not find a CA cert in %s" % pkcs12_fname)
self.cacert_name = ca_names[-1]
self.trust_root_cert(self.cacert_name, trust_flags)
self.cacert_name = ca_names[0]
for ca in ca_names:
self.trust_root_cert(ca)
self.create_pin_file()
self.export_ca_cert(nickname, False)
@@ -640,119 +856,7 @@ class CertDB(object):
def publish_ca_cert(self, location):
shutil.copy(self.cacert_fname, location)
os.chmod(location, 0o444)
os.chmod(location, 0444)
def export_pem_cert(self, nickname, location):
return self.nssdb.export_pem_cert(nickname, location)
def request_service_cert(self, nickname, principal, host, pwdconf=False):
self.create_from_cacert(paths.IPA_CA_CRT)
if pwdconf:
self.create_password_conf()
reqid = certmonger.request_cert(nssdb=self.secdir,
nickname=nickname,
principal=principal,
subject=host,
passwd_fname=self.passwd_fname)
# Now wait for the cert to appear. Check three times then abort
certmonger.wait_for_request(reqid, timeout=15)
class _CrossProcessLock(object):
_DATETIME_FORMAT = '%Y%m%d%H%M%S%f'
def __init__(self, filename):
self._filename = filename
def __enter__(self):
self.acquire()
def __exit__(self, exc_type, exc_value, traceback):
self.release()
def acquire(self, owner=None):
self._do(self._acquire, owner)
def release(self, owner=None):
self._do(self._release, owner)
def _acquire(self, owner):
now = datetime.datetime.utcnow()
if self._locked and now >= self._expire:
self._locked = False
if self._locked:
return False
self._locked = True
self._owner = owner
self._expire = now + datetime.timedelta(hours=1)
return True
def _release(self, owner):
if not self._locked or self._owner != owner:
raise RuntimeError("lock not acquired by %s" % owner)
self._locked = False
self._owner = None
self._expire = None
return True
def _do(self, func, owner):
if owner is None:
owner = '%s[%s]' % (os.path.basename(sys.argv[0]), os.getpid())
while True:
with open(self._filename, 'a+') as f:
fcntl.flock(f, fcntl.LOCK_EX)
f.seek(0)
self._read(f)
if func(owner):
f.seek(0)
f.truncate()
self._write(f)
return
time.sleep(10)
def _read(self, fileobj):
p = configparser.RawConfigParser()
p.readfp(fileobj)
try:
self._locked = p.getboolean('lock', 'locked')
if self._locked:
self._owner = p.get('lock', 'owner')
expire = p.get('lock', 'expire')
try:
self._expire = datetime.datetime.strptime(
expire, self._DATETIME_FORMAT)
except ValueError:
raise configparser.Error
except configparser.Error:
self._locked = False
self._owner = None
self._expire = None
def _write(self, fileobj):
p = configparser.RawConfigParser()
p.add_section('lock')
locked = '1' if self._locked else '0'
p.set('lock', 'locked', locked)
if self._locked:
expire = self._expire.strftime(self._DATETIME_FORMAT)
p.set('lock', 'owner', self._owner)
p.set('lock', 'expire', expire)
p.write(fileobj)
renewal_lock = _CrossProcessLock(paths.IPA_RENEWAL_LOCK)