commit f153bd8a455953698e8af5085cd3cd7b368b1247 Author: Endi S. Dewata Date: Fri Sep 4 06:30:27 2015 +0200 Added support for secure database connection in CLI. The pki-server subsystem-cert-update has been modified to support secure database connection with client certificate authentication. The certificate and the private key will be exported temporarily into PEM files so python-ldap can use them. The pki client-cert-show has been modified to provide an option to export client certificate's private key. https://fedorahosted.org/pki/ticket/1551 diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java index f79501c..e44fae7 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java @@ -29,10 +29,8 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.mozilla.jss.crypto.X509Certificate; -import com.netscape.certsrv.cert.CertData; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; -import com.netscape.cmsutil.util.Utils; /** * @author Endi S. Dewata @@ -57,6 +55,10 @@ public class ClientCertShowCLI extends CLI { option.setArgName("path"); options.addOption(option); + option = new Option(null, "private-key", true, "PEM file to store the private key."); + option.setArgName("path"); + options.addOption(option); + option = new Option(null, "client-cert", true, "PEM file to store the certificate and the private key."); option.setArgName("path"); options.addOption(option); @@ -107,90 +109,82 @@ public class ClientCertShowCLI extends CLI { String nickname = cmdArgs[0]; String certPath = cmd.getOptionValue("cert"); + String privateKeyPath = cmd.getOptionValue("private-key"); + String clientCertPath = cmd.getOptionValue("client-cert"); String pkcs12Path = cmd.getOptionValue("pkcs12"); String pkcs12Password = cmd.getOptionValue("pkcs12-password"); - String clientCertPath = cmd.getOptionValue("client-cert"); - - if (certPath != null) { - - if (verbose) System.out.println("Exporting certificate to " + clientCertPath + "."); - - // late initialization - mainCLI.init(); - client = mainCLI.getClient(); - X509Certificate cert = client.getCert(nickname); + File pkcs12File; - try (PrintWriter out = new PrintWriter(new FileWriter(certPath))) { - out.println(CertData.HEADER); - out.println(Utils.base64encode(cert.getEncoded())); - out.println(CertData.FOOTER); - } + if (pkcs12Path != null) { + // exporting certificate to PKCS #12 file - } else if (pkcs12Path != null) { - - if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12Path + "."); + pkcs12File = new File(pkcs12Path); if (pkcs12Password == null) { throw new Exception("Missing PKCS #12 password"); } - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); + } else if (certPath != null || clientCertPath != null || privateKeyPath != null) { + // exporting certificate and/or private key to PEM files using temporary PKCS #12 file - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } + // prepare temporary PKCS #12 file + pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); + pkcs12File.deleteOnExit(); - // export certificate chain and private key into PKCS #12 file - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12Path, - pkcs12PasswordFile.getAbsolutePath(), - nickname); + // generate random password + pkcs12Password = RandomStringUtils.randomAlphanumeric(16); - } else if (clientCertPath != null) { + } else { + // displaying certificate info - if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); + mainCLI.init(); - // generate random PKCS #12 password - pkcs12Password = RandomStringUtils.randomAlphanumeric(16); + client = mainCLI.getClient(); + X509Certificate cert = client.getCert(nickname); + + ClientCLI.printCertInfo(cert); + return; + } - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } - // export certificate chain and private key into a temporary PKCS #12 file - File pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); - pkcs12File.deleteOnExit(); + if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12File + "."); + exportPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + nickname); - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), + if (certPath != null) { + if (verbose) System.out.println("Exporting certificate to " + certPath + "."); + exportCertificate( pkcs12File.getAbsolutePath(), pkcs12PasswordFile.getAbsolutePath(), - nickname); + certPath); + } - // export client certificate and private key into a PEM file - exportClientCertificate( + if (privateKeyPath != null) { + if (verbose) System.out.println("Exporting private key to " + privateKeyPath + "."); + exportPrivateKey( pkcs12File.getAbsolutePath(), pkcs12PasswordFile.getAbsolutePath(), - clientCertPath); - - } else { - // late initialization - mainCLI.init(); - - client = mainCLI.getClient(); - X509Certificate cert = client.getCert(nickname); + privateKeyPath); + } - ClientCLI.printCertInfo(cert); + if (clientCertPath != null) { + if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); + exportClientCertificateAndPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + clientCertPath); } } @@ -218,7 +212,53 @@ public class ClientCertShowCLI extends CLI { } } - public void exportClientCertificate( + public void exportCertificate( + String pkcs12Path, + String pkcs12PasswordPath, + String certPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-clcerts", // certificate only + "-nokeys", + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", certPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export certificate", e); + } + } + + public void exportPrivateKey( + String pkcs12Path, + String pkcs12PasswordPath, + String privateKeyPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-nocerts", // private key only + "-nodes", // no encryption + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", privateKeyPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export private key", e); + } + } + + public void exportClientCertificateAndPrivateKey( String pkcs12Path, String pkcs12PasswordPath, String clientCertPath) throws Exception { @@ -226,7 +266,7 @@ public class ClientCertShowCLI extends CLI { String[] command = { "/bin/openssl", "pkcs12", - "-clcerts", // client certificate only + "-clcerts", // client certificate and private key "-nodes", // no encryption "-in", pkcs12Path, "-passin", "file:" + pkcs12PasswordPath, @@ -237,7 +277,7 @@ public class ClientCertShowCLI extends CLI { run(command); } catch (Exception e) { - throw new Exception("Unable to export client certificate", e); + throw new Exception("Unable to export client certificate and private key", e); } } diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index d004465..89d4acf 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -28,7 +28,9 @@ import operator import os import pwd import re +import shutil import subprocess +import tempfile import pki @@ -162,18 +164,43 @@ class PKISubsystem(object): def open_database(self, name='internaldb'): + # TODO: add LDAPI support hostname = self.config['%s.ldapconn.host' % name] port = self.config['%s.ldapconn.port' % name] - bind_dn = self.config['%s.ldapauth.bindDN' % name] + secure = self.config['%s.ldapconn.secureConn' % name] - # TODO: add support for other authentication - # mechanisms (e.g. client cert authentication, LDAPI) - bind_password = self.instance.get_password(name) + if secure == 'true': + url = 'ldaps://%s:%s' % (hostname, port) - con = ldap.initialize('ldap://%s:%s' % (hostname, port)) - con.simple_bind_s(bind_dn, bind_password) + elif secure == 'false': + url = 'ldap://%s:%s' % (hostname, port) - return con + else: + raise Exception('Invalid parameter value in %s.ldapconn.secureConn: %s' % (name, secure)) + + connection = PKIDatabaseConnection(url) + + connection.set_security_database(self.instance.nssdb_dir) + + auth_type = self.config['%s.ldapauth.authtype' % name] + if auth_type == 'BasicAuth': + connection.set_credentials( + bind_dn=self.config['%s.ldapauth.bindDN' % name], + bind_password=self.instance.get_password(name) + ) + + elif auth_type == 'SslClientAuth': + connection.set_credentials( + client_cert_nickname=self.config['%s.ldapauth.clientCertNickname' % name], + nssdb_password=self.instance.get_password('internal') + ) + + else: + raise Exception('Invalid parameter value in %s.ldapauth.authtype: %s' % (name, auth_type)) + + connection.open() + + return connection def __repr__(self): return str(self.instance) + '/' + self.name @@ -337,6 +364,64 @@ class PKIInstance(object): return self.name +class PKIDatabaseConnection(object): + + def __init__(self, url='ldap://localhost:389'): + + self.url = url + + self.nssdb_dir = None + + self.bind_dn = None + self.bind_password = None + + self.client_cert_nickname = None + self.nssdb_password = None + + self.temp_dir = None + self.ldap = None + + def set_security_database(self, nssdb_dir=None): + self.nssdb_dir = nssdb_dir + + def set_credentials(self, bind_dn=None, bind_password=None, + client_cert_nickname=None, nssdb_password=None): + self.bind_dn = bind_dn + self.bind_password = bind_password + self.client_cert_nickname = client_cert_nickname + self.nssdb_password = nssdb_password + + def open(self): + + self.temp_dir = tempfile.mkdtemp() + + if self.nssdb_dir: + + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.nssdb_dir) + + if self.client_cert_nickname: + + password_file = os.path.join(self.temp_dir, 'password.txt') + with open(password_file, 'w') as f: + f.write(self.nssdb_password) + + ldap.set_option(ldap.OPT_X_TLS_CERTFILE, self.client_cert_nickname) + ldap.set_option(ldap.OPT_X_TLS_KEYFILE, password_file) + + self.ldap = ldap.initialize(self.url) + + if self.bind_dn and self.bind_password: + self.ldap.simple_bind_s(self.bind_dn, self.bind_password) + + def close(self): + + if self.ldap: + self.ldap.unbind_s() + + if self.temp_dir: + shutil.rmtree(self.temp_dir) + + class PKIServerException(pki.PKIException): def __init__(self, message, exception=None, diff --git a/base/server/python/pki/server/ca.py b/base/server/python/pki/server/ca.py index 70ebf4d..31e373a 100644 --- a/base/server/python/pki/server/ca.py +++ b/base/server/python/pki/server/ca.py @@ -45,13 +45,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'ou=ca,ou=requests,%s' % base_dn, ldap.SCOPE_ONELEVEL, search_filter, None) - con.unbind_s() + con.close() requests = [] for entry in entries: @@ -65,13 +65,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'cn=%s,ou=ca,ou=requests,%s' % (request_id, base_dn), ldap.SCOPE_BASE, '(objectClass=*)', None) - con.unbind_s() + con.close() entry = entries[0] return self.create_request_object(entry) diff --git a/base/server/python/pki/server/cli/subsystem.py b/base/server/python/pki/server/cli/subsystem.py index fc89c27..19db203 100644 --- a/base/server/python/pki/server/cli/subsystem.py +++ b/base/server/python/pki/server/cli/subsystem.py @@ -104,7 +104,7 @@ class SubsystemFindCLI(pki.cli.CLI): if first: first = False else: - print + print() SubsystemCLI.print_subsystem(subsystem) diff --git a/base/server/upgrade/10.2.0/01-AddTLSRangeSupport b/base/server/upgrade/10.2.0/01-AddTLSRangeSupport index b5b83f4..e225924 100755 --- a/base/server/upgrade/10.2.0/01-AddTLSRangeSupport +++ b/base/server/upgrade/10.2.0/01-AddTLSRangeSupport @@ -29,7 +29,7 @@ import pki.server.upgrade class AddTLSRangeSupport(pki.server.upgrade.PKIServerUpgradeScriptlet): def __init__(self): - + super(AddTLSRangeSupport, self).__init__() self.message = 'Add TLS Range Support' self.parser = etree.XMLParser(remove_blank_text=True)