Rework the way SSL certificates are imported from PKCS#12 files.

Add the ability to provide PKCS#12 files during initial installation
Add the ability to provide PKCS#12 files when preparing a replica
Correct some issues with ipa-server-certinstall

452402
This commit is contained in:
Rob Crittenden 2008-07-11 11:34:29 -04:00
parent b95c05f5c6
commit 6980b07303
11 changed files with 276 additions and 77 deletions

View File

@ -103,7 +103,7 @@ def install_ds(config):
pkcs12_info = None
if ipautil.file_exists(config.dir + "/dscert.p12"):
pkcs12_info = (config.dir + "/dscert.p12",
config.dir + "/pwdfile.txt")
config.dir + "/dirsrv_pin.txt")
ds = dsinstance.DsInstance()
ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.domain_name, config.dirman_password, pkcs12_info)
@ -125,7 +125,7 @@ def install_http(config):
pkcs12_info = None
if ipautil.file_exists(config.dir + "/httpcert.p12"):
pkcs12_info = (config.dir + "/httpcert.p12",
config.dir + "/pwdfile.txt")
config.dir + "/http_pin.txt")
http = httpinstance.HTTPInstance()
http.create_instance(config.realm_name, config.host_name, config.domain_name, False, pkcs12_info)
@ -174,6 +174,9 @@ def main():
options, filename = parse_options()
installutils.standard_logging_setup("/var/log/ipareplica-install.log", options.debug)
if not ipautil.file_exists(filename):
sys.exit("Replica file %s does not exist" % filename)
check_dirsrv()
top_dir, dir = expand_info(filename)

View File

@ -40,8 +40,26 @@ def parse_options():
parser = OptionParser(version=version.VERSION)
args = ipa.config.init_config(sys.argv)
parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12",
help="install certificate for the directory server")
parser.add_option("--http_pkcs12", dest="http_pkcs12",
help="install certificate for the http server")
parser.add_option("--dirsrv_pin", dest="dirsrv_pin",
help="PIN for the Directory Server PKCS#12 file")
parser.add_option("--http_pin", dest="http_pin",
help="PIN for the Apache Server PKCS#12 file")
options, args = parser.parse_args(args)
# If any of the PKCS#12 options are selected, all are required. Create a
# list of the options and count it to enforce that all are required without
# having a huge set of it blocks.
pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin]
cnt = pkcs12.count(None)
if cnt > 0 and cnt < 4:
parser.error("error: All PKCS#12 options are required if any are used.")
if len(args) != 2:
parser.error("must provide the fully-qualified name of the replica")
@ -79,10 +97,11 @@ def check_ipa_configuration(realm_name):
logging.error("could not find directory instance: %s" % config_dir)
sys.exit(1)
def export_certdb(realm_name, ds_dir, dir, fname, subject):
def export_certdb(realm_name, ds_dir, dir, passwd_fname, fname, subject):
"""realm is the kerberos realm for the IPA server.
ds_dir is the location of the master DS we are creating a replica for.
dir is the location of the files for the replica we are creating.
passwd_fname is the file containing the PKCS#12 password
fname is the filename of the PKCS#12 file for this cert (minus the .p12).
subject is the subject of the certificate we are creating
"""
@ -95,10 +114,6 @@ def export_certdb(realm_name, ds_dir, dir, fname, subject):
raise e
pkcs12_fname = dir + "/" + fname + ".p12"
passwd_fname = dir + "/pwdfile.txt"
fd = open(passwd_fname, "w")
fd.write("\n")
fd.close()
try:
ca.export_pkcs12(pkcs12_fname, passwd_fname, "Server-Cert")
@ -150,6 +165,9 @@ def main():
replica_fqdn = args[1]
if not ipautil.file_exists("/usr/share/ipa/serial") and not options.dirsrv_pin:
sys.exit("The replica must be created on the primary IPA server.\nIf you installed IPA with your own certificates using PKCS#12 files you must provide PKCS#12 files for any replicas you create as well.")
print "Determining current realm name"
realm_name = get_realm_name()
if realm_name is None:
@ -177,10 +195,47 @@ def main():
dir = top_dir + "/realm_info"
os.mkdir(dir, 0700)
if options.dirsrv_pin:
passwd = options.dirsrv_pin
else:
passwd = ""
passwd_fname = dir + "/dirsrv_pin.txt"
fd = open(passwd_fname, "w")
fd.write("%s\n" % passwd)
fd.close()
if options.dirsrv_pkcs12:
print "Copying SSL certificate for the Directory Server from %s" % options.dirsrv_pkcs12
try:
shutil.copy(options.dirsrv_pkcs12, dir + "/dscert.p12")
except IOError, e:
print "Copy failed %s" % e
sys.exit(1)
else:
print "Creating SSL certificate for the Directory Server"
export_certdb(realm_name, ds_dir, dir, "dscert", "cn=%s,ou=Fedora Directory Server" % replica_fqdn)
export_certdb(realm_name, ds_dir, dir, passwd_fname, "dscert", "cn=%s,ou=Fedora Directory Server" % replica_fqdn)
if options.http_pin:
passwd = options.http_pin
else:
passwd = ""
passwd_fname = dir + "/http_pin.txt"
fd = open(passwd_fname, "w")
fd.write("%s\n" % passwd)
fd.close()
if options.http_pkcs12:
print "Copying SSL certificate for the Web Server from %s" % options.http_pkcs12
try:
shutil.copy(options.http_pkcs12, dir + "/httpcert.p12")
except IOError, e:
print "Copy failed %s" % e
sys.exit(1)
else:
print "Creating SSL certificate for the Web Server"
export_certdb(realm_name, ds_dir, dir, "httpcert", "cn=%s,ou=Apache Web Server" % replica_fqdn)
export_certdb(realm_name, ds_dir, dir, passwd_fname, "httpcert", "cn=%s,ou=Apache Web Server" % replica_fqdn)
print "Copying additional files"
copy_files(realm_name, dir)
print "Finalizing configuration"
@ -195,8 +250,6 @@ def main():
try:
if not os.geteuid()==0:
sys.exit("\nYou must be root to run this script.\n")
if not ipautil.file_exists("/usr/share/ipa/serial"):
sys.exit("The replica must be created on the primary IPA server.")
main()
except SystemExit, e:

View File

@ -21,6 +21,7 @@
import sys
import os
import pwd
import tempfile
import traceback
@ -40,12 +41,18 @@ def parse_options():
default=False, help="install certificate for the directory server")
parser.add_option("-w", "--http", dest="http", action="store_true",
default=False, help="install certificate for the http server")
parser.add_option("--dirsrv_pin", dest="dirsrv_pin",
help="The password of the Directory Server PKCS#12 file")
parser.add_option("--http_pin", dest="http_pin",
help="The password of the Apache Server PKCS#12 file")
options, args = parser.parse_args()
if not options.dirsrv and not options.http:
parser.error("you must specify dirsrv and/or http")
if ((options.dirsrv and not options.dirsrv_pin) or
(options.http and not options.http_pin)):
parser.error("you must provide the password for the PKCS#12 file")
if len(args) != 1:
parser.error("you must provide a pkcs12 filename")
@ -62,23 +69,6 @@ def set_ds_cert_name(cert_name, dm_password):
conn.unbind()
def set_http_cert_name(cert_name):
# find the existing cert name
fd = open(httpinstance.NSS_CONF)
nick_name = None
file = []
for line in fd:
if "NSSNickname" in line:
file.append('NSSNickname "%s"\n' % cert_name)
else:
file.append(line)
fd.close()
fd = open(httpinstance.NSS_CONF, "w")
fd.write("".join(file))
fd.close()
def choose_server_cert(server_certs):
print "Please select the certificate to use:"
num = 1
@ -106,15 +96,22 @@ def choose_server_cert(server_certs):
return server_certs[cert_num]
def import_cert(dirname, pkcs12_fname):
def import_cert(dirname, pkcs12_fname, pkcs12_passwd, db_password):
cdb = certs.CertDB(dirname)
cdb.create_passwd_file(False)
cdb.create_passwd_file(db_password)
cdb.create_certdbs()
[pw_fd, pw_name] = tempfile.mkstemp()
os.write(pw_fd, pkcs12_passwd)
os.close(pw_fd)
try:
cdb.import_pkcs12(pkcs12_fname)
try:
cdb.import_pkcs12(pkcs12_fname, pw_name)
except RuntimeError, e:
print str(e)
sys.exit(1)
finally:
os.remove(pw_name)
server_certs = cdb.find_server_certs()
if len(server_certs) == 0:
@ -137,14 +134,17 @@ def main():
dm_password = getpass.getpass("Directory Manager password: ")
realm = get_realm_name()
dirname = dsinstance.config_dirname(dsinstance.realm_to_serverid(realm))
server_cert = import_cert(dirname, pkcs12_fname)
fd = open(dirname + "/pwdfile.txt")
passwd = fd.read()
fd.close()
server_cert = import_cert(dirname, pkcs12_fname, options.dirsrv_pin, passwd)
set_ds_cert_name(server_cert[0], dm_password)
if options.http:
dirname = httpinstance.NSS_DIR
server_cert = import_cert(dirname, pkcs12_fname)
print server_cert
set_http_cert_name(server_cert[0])
server_cert = import_cert(dirname, pkcs12_fname, options.http_pin, "")
installutils.set_directive(httpinstance.NSS_CONF, 'NSSNickname', server_cert[0])
# Fix the database permissions
os.chmod(dirname + "/cert8.db", 0640)
@ -163,5 +163,4 @@ def main():
return 0
sys.exit(main())

View File

@ -50,6 +50,8 @@ from ipaserver.installutils import *
from ipa import sysrestore
from ipa.ipautil import *
pw_name = None
def parse_options():
parser = OptionParser(version=version.VERSION)
parser.add_option("-u", "--user", dest="ds_user",
@ -76,6 +78,14 @@ def parse_options():
default=False, help="uninstall an existing installation")
parser.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false",
help="do not configure ntp", default=True)
parser.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12",
help="PKCS#12 file containing the Directory Server SSL certificate")
parser.add_option("--http_pkcs12", dest="http_pkcs12",
help="PKCS#12 file containing the Apache Server SSL certificate")
parser.add_option("--dirsrv_pin", dest="dirsrv_pin",
help="The password of the Directory Server PKCS#12 file")
parser.add_option("--http_pin", dest="http_pin",
help="The password of the Apache Server PKCS#12 file")
options, args = parser.parse_args()
@ -89,6 +99,14 @@ def parse_options():
not options.dm_password or not options.admin_password):
parser.error("error: In unattended mode you need to provide at least -u, -r, -p and -a options")
# If any of the PKCS#12 options are selected, all are required. Create a
# list of the options and count it to enforce that all are required without
# having a huge set of it blocks.
pkcs12 = [options.dirsrv_pkcs12, options.http_pkcs12, options.dirsrv_pin, options.http_pin]
cnt = pkcs12.count(None)
if cnt > 0 and cnt < 4:
parser.error("error: All PKCS#12 options are required if any are used.")
return options
def signal_handler(signum, frame):
@ -312,6 +330,7 @@ def uninstall():
def main():
global ds
global pw_name
ds = None
options = parse_options()
@ -486,8 +505,18 @@ def main():
ntp = ipaserver.ntpinstance.NTPInstance(fstore)
ntp.create_instance()
if options.dirsrv_pin:
[pw_fd, pw_name] = tempfile.mkstemp()
os.write(pw_fd, options.dirsrv_pin)
os.close(pw_fd)
# Create a directory server instance
ds = ipaserver.dsinstance.DsInstance()
if options.dirsrv_pkcs12:
pkcs12_info = (options.dirsrv_pkcs12, pw_name)
ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info)
os.remove(pw_name)
else:
ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password)
# Create a kerberos instance
@ -495,8 +524,19 @@ def main():
krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password)
# Create a HTTP instance
if options.http_pin:
[pw_fd, pw_name] = tempfile.mkstemp()
os.write(pw_fd, options.http_pin)
os.close(pw_fd)
http = ipaserver.httpinstance.HTTPInstance(fstore)
http.create_instance(realm_name, host_name, domain_name)
if options.http_pkcs12:
pkcs12_info = (options.http_pkcs12, pw_name)
http.create_instance(realm_name, host_name, domain_name, False, pkcs12_info)
os.remove(pw_name)
else:
http.create_instance(realm_name, host_name, domain_name, False)
# Create the config file
fstore.backup_file("/etc/ipa/ipa.conf")
@ -563,16 +603,23 @@ def main():
print "\t and servers for correct operation. You should consider enabling ntpd."
print ""
if not options.dirsrv_pkcs12:
print "Be sure to back up the CA certificate stored in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "cacert.p12"
print "The password for this file is in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "pwdfile.txt"
else:
print "In order for Firefox autoconfiguration to work you will need to"
print "use a SSL signing certificate. See the IPA documentation for more details."
print "You also need to install a PEM copy of the HTTP issuing CA into"
print "/usr/share/ipa/html/ca.crt"
return 0
try:
try:
sys.exit(main())
except SystemExit, e:
except SystemExit, e:
sys.exit(e)
except Exception, e:
except Exception, e:
message = "Unexpected error - see ipaserver-install.log for details:\n %s" % str(e)
print message
message = str(e)
@ -580,3 +627,6 @@ except Exception, e:
message = message + "\n" + str
logging.debug(message)
sys.exit(1)
finally:
if pw_name and ipautil.file_exists(pw_name):
os.remove(pw_name)

View File

@ -128,13 +128,13 @@ class CertDB(object):
f.write(self.gen_password())
self.set_perms(self.noise_fname)
def create_passwd_file(self, passwd=True):
def create_passwd_file(self, passwd=None):
ipautil.backup_file(self.passwd_fname)
f = open(self.passwd_fname, "w")
if passwd:
f.write(self.gen_password())
if passwd is not None:
f.write("%s\n" % passwd)
else:
f.write("\n")
f.write(self.gen_password())
f.close()
self.set_perms(self.passwd_fname)
@ -159,14 +159,14 @@ class CertDB(object):
"-z", self.noise_fname,
"-f", self.passwd_fname])
def export_ca_cert(self, create_pkcs12=False):
def export_ca_cert(self, nickname, create_pkcs12=False):
"""create_pkcs12 tells us whether we should create a PKCS#12 file
of the CA or not. If we are running on a replica then we won't
have the private key to make a PKCS#12 file so we don't need to
do that step."""
# export the CA cert for use with other apps
ipautil.backup_file(self.cacert_fname)
self.run_certutil(["-L", "-n", "CA certificate",
self.run_certutil(["-L", "-n", nickname,
"-a",
"-o", self.cacert_fname])
self.set_perms(self.cacert_fname)
@ -174,7 +174,7 @@ class CertDB(object):
ipautil.backup_file(self.pk12_fname)
ipautil.run(["/usr/bin/pk12util", "-d", self.secdir,
"-o", self.pk12_fname,
"-n", "CA certificate",
"-n", self.cacert_name,
"-w", self.passwd_fname,
"-k", self.passwd_fname])
self.set_perms(self.pk12_fname)
@ -296,7 +296,7 @@ class CertDB(object):
f.close()
self.set_perms(self.pin_fname)
def trust_root_cert(self, nickname):
def find_root_cert(self, nickname):
p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir,
"-O", "-n", nickname], stdout=subprocess.PIPE)
@ -305,6 +305,11 @@ class CertDB(object):
root_nickname = re.match('\ *"(.*)".*', chain[0]).groups()[0]
return root_nickname
def trust_root_cert(self, nickname):
root_nickname = self.find_root_cert(nickname)
self.run_certutil(["-M", "-n", root_nickname,
"-t", "CT,CT,"])
@ -350,28 +355,50 @@ class CertDB(object):
"-k", self.passwd_fname,
"-w", pkcs12_pwd_fname])
def create_self_signed(self, passwd=True):
def create_self_signed(self, passwd=None):
self.create_noise_file()
self.create_passwd_file(passwd)
self.create_certdbs()
self.create_ca_cert()
self.export_ca_cert(True)
self.export_ca_cert(self.cacert_name, True)
self.create_pin_file()
def create_from_cacert(self, cacert_fname, passwd=False):
def create_from_cacert(self, cacert_fname, passwd=""):
self.create_noise_file()
self.create_passwd_file(passwd)
self.create_certdbs()
self.load_cacert(cacert_fname)
def create_from_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, nickname="CA certificate", passwd=True):
def create_from_pkcs12(self, pkcs12_fname, pkcs12_pwd_fname, passwd=None):
"""Create a new NSS database using the certificates in a PKCS#12 file.
pkcs12_fname: the filename of the PKCS#12 file
pkcs12_pwd_fname: the file containing the pin for the PKCS#12 file
nickname: the nickname/friendly-name of the cert we are loading
passwd: The password to use for the new NSS database we are creating
"""
self.create_noise_file()
self.create_passwd_file(passwd)
self.create_certdbs()
self.import_pkcs12(pkcs12_fname, pkcs12_pwd_fname)
server_certs = self.find_server_certs()
if len(server_certs) == 0:
raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_fname)
# We only handle one server cert
nickname = server_certs[0][0]
self.cacert_name = self.find_root_cert(nickname)
self.trust_root_cert(nickname)
self.create_pin_file()
self.export_ca_cert(False)
self.export_ca_cert(self.cacert_name, False)
# This file implies that we have our own self-signed CA. Ensure
# that it no longer exists (from previous installs, for example).
try:
os.remove("/usr/share/ipa/serial")
except:
pass
def backup_files(self):
self.fstore.backup_file(self.noise_fname)

View File

@ -324,9 +324,16 @@ class DsInstance(service.Service):
ca = certs.CertDB(dirname)
if self.pkcs12_info:
ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1])
server_certs = ca.find_server_certs()
if len(server_certs) == 0:
raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0])
# We only handle one server cert
nickname = server_certs[0][0]
else:
ca.create_self_signed()
ca.create_server_cert("Server-Cert", "cn=%s,ou=Fedora Directory Server" % self.host_name)
nickname = "Server-Cert"
conn = ipaldap.IPAdmin("127.0.0.1")
conn.simple_bind_s("cn=directory manager", self.dm_password)
@ -347,7 +354,7 @@ class DsInstance(service.Service):
entry.setValues("objectclass", "top", "nsEncryptionModule")
entry.setValues("cn", "RSA")
entry.setValues("nsSSLPersonalitySSL", "Server-Cert")
entry.setValues("nsSSLPersonalitySSL", nickname)
entry.setValues("nsSSLToken", "internal (software)")
entry.setValues("nsSSLActivation", "on")

View File

@ -145,6 +145,9 @@ class HTTPInstance(service.Service):
if installutils.update_file(NSS_CONF, '8443', '443') != 0:
print "Updating port in %s failed." % NSS_CONF
def __set_mod_nss_nickname(self, nickname):
installutils.set_directive(NSS_CONF, 'NSSNickname', nickname)
def __add_include(self):
"""This should run after __set_mod_nss_port so is already backed up"""
if installutils.update_file(NSS_CONF, '</VirtualHost>', 'Include conf.d/ipa-rewrite.conf\n</VirtualHost>') != 0:
@ -154,7 +157,15 @@ class HTTPInstance(service.Service):
ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm)))
ca = certs.CertDB(NSS_DIR)
if self.pkcs12_info:
ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], passwd=False)
ca.create_from_pkcs12(self.pkcs12_info[0], self.pkcs12_info[1], passwd="")
server_certs = ca.find_server_certs()
if len(server_certs) == 0:
raise RuntimeError("Could not find a suitable server cert in import in %s" % pkcs12_info[0])
# We only handle one server cert
nickname = server_certs[0][0]
self.__set_mod_nss_nickname(nickname)
else:
ca.create_from_cacert(ds_ca.cacert_fname)
ca.create_server_cert("Server-Cert", "cn=%s,ou=Apache Web Server" % self.fqdn, ds_ca)

View File

@ -200,6 +200,24 @@ def update_file(filename, orig, subst):
print "File %s doesn't exist." % filename
return 1
def set_directive(filename, directive, value):
"""Set a name/value pair directive in a configuration file.
This has only been tested with nss.conf
"""
fd = open(filename)
file = []
for line in fd:
if directive in line:
file.append('%s "%s"\n' % (directive, value))
else:
file.append(line)
fd.close()
fd = open(filename, "w")
fd.write("".join(file))
fd.close()
def kadmin(command):
ipautil.run(["/usr/kerberos/sbin/kadmin.local", "-q", command])

View File

@ -29,6 +29,19 @@ A replica can only be created on an IPA server installed with ipa\-server\-insta
You must provide the fully\-qualified hostname of the machine you want to install the replica on and a host\-specific replica_file will be created. It is host\-specific because SSL server certificates are generated as part of the process and they are specific to a particular hostname.
Once the file has been created it will be named replica\-hostname. This file can then be moved across the network to the target machine and a new IPA replica setup by running ipa\-replica\-install replica\-hostname.
.SH "OPTIONS"
.TP
\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR
PKCS#12 file containing the Directory Server SSL Certificate
.TP
\fB\-\-http_pkcs12\fR=\fIFILE\fR
PKCS#12 file containing the Apache Server SSL Certificate
.TP
\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR
The password of the Directory Server PKCS#12 file
.TP
\fB\-\-http_pin\fR=\fIHTTP_PIN\fR
The password of the Apache Server PKCS#12 file
.SH "EXIT STATUS"
0 if the command was successful

View File

@ -26,8 +26,9 @@ Replace the current SSL Directory and/or Apache server certificate(s) with the c
PKCS#12 is a file format used to safely transport SSL certificates and public/private keypairs.
They may be generated and managed using the NSS pk12util command or the OpeNSSL pkcs12 command.
They may be generated and managed using the NSS pk12util command or the OpenSSL pkcs12 command.
The service(s) are not automatically restarted. In order to use the newly installed certificate(s) you will need to manually restart the Directory and/or Apache servers.
.SH "OPTIONS"
.TP
\fB\-d\fR, \fB\-\-dirsrv\fR
@ -35,6 +36,12 @@ Install the certificate on the Directory Server
.TP
\fB\-w\fR, \fB\-\-http\fR
Install the certificate in the Apache Web Server
.TP
\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR
The password of the Directory Server PKCS#12 file
.TP
\fB\-\-http_pin\fR=\fIHTTP_PIN\fR
The password of the Apache Server PKCS#12 file
.SH "EXIT STATUS"
0 if the installation was successful

View File

@ -60,10 +60,21 @@ Generate a DNS zone file that contains auto\-discovery records for this IPA serv
.TP
\fB\-n\fR, \fB\-\-no\-ntp\fR
Do not configure NTP
<fb>\-U\fR, \fB\-\-uninstall\fR
\fB\-U\fR, \fB\-\-uninstall\fR
Uninstall an existing IPA installation
.TP
\fB\-\-dirsrv_pkcs12\fR=\fIFILE\fR
PKCS#12 file containing the Directory Server SSL Certificate
.TP
\fB\-\-http_pkcs12\fR=\fIFILE\fR
PKCS#12 file containing the Apache Server SSL Certificate
.TP
\fB\-\-dirsrv_pin\fR=\fIDIRSRV_PIN\fR
The password of the Directory Server PKCS#12 file
.TP
\fB\-\-http_pin\fR=\fIHTTP_PIN\fR
The password of the Apache Server PKCS#12 file
.PP
By default the full name, home Directory and login shell and username fields are displayed.
.SH "EXIT STATUS"
0 if the installation was successful