[Pki-devel] [PATCH] 642 Added support for secure database connection in CLI.

Endi Sukma Dewata edewata at redhat.com
Fri Sep 11 21:46:57 UTC 2015


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

-- 
Endi S. Dewata
-------------- next part --------------
From cd0619e2074a51fa1525f2b4b83458dfd6a1a2d6 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
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 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
---
 .../cmstools/client/ClientCertShowCLI.java         | 176 +++++++++++++--------
 base/server/python/pki/server/__init__.py          | 120 +++++++++++++-
 base/server/python/pki/server/ca.py                |   8 +-
 3 files changed, 225 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..1c8943d171fbae98897105f468567ab961a9c566 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,46 @@ 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,
+            self.instance.password_conf,
+            self.config['%s.cert.signing.nickname' % self.name]
+        )
+
+        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]
+            )
+
+        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 +373,82 @@ class PKIInstance(object):
         return self.name
 
 
+class PKIDatabaseConnection(object):
+
+    def __init__(self, url='ldap://localhost:389'):
+
+        self.url = url
+
+        self.nssdb_dir = None
+        self.nssdb_password_file = None
+        self.ca_cert_nickname = None
+
+        self.bind_dn = None
+        self.bind_password = None
+        self.client_cert_nickname = None
+
+        self.temp_dir = None
+        self.ldap = None
+
+    def set_security_database(self, nssdb_dir=None, nssdb_password_file=None, ca_cert_nickname=None):
+        self.nssdb_dir = nssdb_dir
+        self.nssdb_password_file = nssdb_password_file
+        self.ca_cert_nickname = ca_cert_nickname
+
+    def set_credentials(self, bind_dn=None, bind_password=None, client_cert_nickname=None):
+        self.bind_dn = bind_dn
+        self.bind_password = bind_password
+        self.client_cert_nickname = client_cert_nickname
+
+    def open(self):
+
+        self.temp_dir = tempfile.mkdtemp()
+
+        if self.ca_cert_nickname:
+
+            ca_cert_file = os.path.join(self.temp_dir, 'ca.pem')
+
+            subprocess.check_call([
+                '/usr/bin/pki',
+                '-d', self.nssdb_dir,
+                '-C', self.nssdb_password_file,
+                'client-cert-show', self.ca_cert_nickname,
+                '--cert', ca_cert_file
+            ])
+
+            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_file)
+
+        if self.client_cert_nickname:
+
+            client_cert_file = os.path.join(self.temp_dir, 'cert.pem')
+            private_key_file = os.path.join(self.temp_dir, 'key.pem')
+
+            subprocess.check_call([
+                '/usr/bin/pki',
+                '-d', self.nssdb_dir,
+                '-C', self.nssdb_password_file,
+                'client-cert-show', self.client_cert_nickname,
+                '--cert', client_cert_file,
+                '--private-key', private_key_file
+            ])
+
+            ldap.set_option(ldap.OPT_X_TLS_CERTFILE, client_cert_file)
+            ldap.set_option(ldap.OPT_X_TLS_KEYFILE, private_key_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



More information about the Pki-devel mailing list