>From bb6b49e0fba2b946c28d1beebfb6d22dfe6d568e Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Fri, 4 Sep 2015 06:30:27 +0200 Subject: [PATCH] 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 pki client-cert-show has been modified to provide an option to export client certificate's private key. https://fedorahosted.org/pki/ticket/1551 --- .../cmstools/client/ClientCertShowCLI.java | 176 +++++++++++++-------- base/server/python/pki/server/__init__.py | 99 +++++++++++- base/server/python/pki/server/ca.py | 8 +- 3 files changed, 204 insertions(+), 79 deletions(-) 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 f79501cfc62bace815ade9a24196f877857c1aed..e44fae7457ac226711fdf9f19cf20722d3aab296 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) { + File pkcs12File; - if (verbose) System.out.println("Exporting certificate to " + clientCertPath + "."); + if (pkcs12Path != null) { + // exporting certificate to PKCS #12 file - // late initialization - mainCLI.init(); - - client = mainCLI.getClient(); - X509Certificate cert = client.getCert(nickname); - - try (PrintWriter out = new PrintWriter(new FileWriter(certPath))) { - out.println(CertData.HEADER); - out.println(Utils.base64encode(cert.getEncoded())); - out.println(CertData.FOOTER); - } - - } 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); - } - - // export certificate chain and private key into PKCS #12 file - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12Path, - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - } else if (clientCertPath != null) { - - if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); - - // generate random PKCS #12 password - pkcs12Password = RandomStringUtils.randomAlphanumeric(16); - - // 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); - } - - // export certificate chain and private key into a temporary PKCS #12 file - File pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); + // prepare temporary PKCS #12 file + pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); pkcs12File.deleteOnExit(); - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - // export client certificate and private key into a PEM file - exportClientCertificate( - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - clientCertPath); + // generate random password + pkcs12Password = RandomStringUtils.randomAlphanumeric(16); } else { - // late initialization + // displaying certificate info + mainCLI.init(); 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(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + 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); + + if (certPath != null) { + if (verbose) System.out.println("Exporting certificate to " + certPath + "."); + exportCertificate( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + certPath); + } + + if (privateKeyPath != null) { + if (verbose) System.out.println("Exporting private key to " + privateKeyPath + "."); + exportPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + privateKeyPath); + } + + 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 70e35b8f2c7bc66339dc315dcab946177d87d1a3..ec4dd7e9cd677d9213d21e5d89d92123134ecc30 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -29,7 +29,9 @@ import operator import os import pwd import re +import shutil import subprocess +import tempfile import pki @@ -163,18 +165,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 @@ -343,6 +370,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 70ebf4dd1044ecad32a0cf072ba45c6ace5eacea..31e373ad85955fd1e64b08c8f5aedba8386384fa 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) -- 2.4.3