Blob Blame History Raw
From 6e8a5957eaaf8d9d770ee5b194007cd4dd605d1e Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Mon, 13 Jul 2015 19:09:57 -0400
Subject: [PATCH] Added support for cloning 3rd-party CA certificates.

The PKCS12Export has been modified to store the trust flags of
the certificates in the PKCS #12 file such that they can be
restored when the file is imported into an NSS database.

During cloning the installation scripts have been modified to
import the CA certificates in the PKCS #12 file separately using
certutil before the server is started. This includes the PKI CA
certificates and 3rd-party CA certificates. This step is necessary
since JSS is unable to preserve the CA certificate nicknames.

Some pki and pki-server commands have been added to help importing
and exportng certificates and keys between PKCS #12 file and NSS
database.

https://fedorahosted.org/pki/ticket/2022
(cherry picked from commit 28ef4b65ce5910fbebaf21446ec30c9a0770a604)

Conflicts:
	base/java-tools/src/com/netscape/cmstools/PKCS12Export.java

(cherry picked from commit 09fd21429666575adf72d7f17b15eda313d94db1)
---
 base/common/CMakeLists.txt                         |   1 +
 base/common/python/pki/cli.py                      | 198 -------
 base/common/python/pki/cli/__init__.py             | 198 +++++++
 base/common/python/pki/cli/pkcs12.py               | 297 ++++++++++
 base/common/python/pki/nssdb.py                    | 202 ++++---
 base/common/share/etc/logging.properties           |  28 +
 base/common/share/etc/pki.conf                     |   3 +
 base/java-tools/bin/pki                            | 314 +++++++---
 .../src/com/netscape/cmstools/PKCS12Export.java    | 221 +------
 .../src/com/netscape/cmstools/cli/MainCLI.java     |   3 +
 .../com/netscape/cmstools/pkcs12/PKCS12CLI.java    |  47 ++
 .../netscape/cmstools/pkcs12/PKCS12CertAddCLI.java | 167 ++++++
 .../netscape/cmstools/pkcs12/PKCS12CertCLI.java    |  59 ++
 .../cmstools/pkcs12/PKCS12CertExportCLI.java       | 207 +++++++
 .../cmstools/pkcs12/PKCS12CertFindCLI.java         | 162 ++++++
 .../cmstools/pkcs12/PKCS12CertRemoveCLI.java       | 149 +++++
 .../netscape/cmstools/pkcs12/PKCS12ExportCLI.java  | 166 ++++++
 .../netscape/cmstools/pkcs12/PKCS12ImportCLI.java  | 153 +++++
 .../com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java |  43 ++
 .../netscape/cmstools/pkcs12/PKCS12KeyFindCLI.java | 163 ++++++
 .../cmstools/pkcs12/PKCS12KeyRemoveCLI.java        | 150 +++++
 .../templates/pki_java_command_wrapper.in          |   7 +-
 base/server/python/pki/server/__init__.py          | 165 +++++-
 base/server/python/pki/server/cli/ca.py            | 205 ++++++-
 base/server/python/pki/server/cli/instance.py      |  98 ++++
 base/server/python/pki/server/cli/kra.py           | 142 +++++
 base/server/python/pki/server/cli/ocsp.py          | 140 +++++
 base/server/python/pki/server/cli/subsystem.py     |  76 ++-
 base/server/python/pki/server/cli/tks.py           | 140 +++++
 base/server/python/pki/server/cli/tps.py           | 140 +++++
 .../server/deployment/scriptlets/configuration.py  |   2 +-
 .../deployment/scriptlets/security_databases.py    |  28 +
 base/server/sbin/pki-server                        |   9 +
 base/util/src/netscape/security/pkcs/PKCS12.java   | 205 +++++++
 .../src/netscape/security/pkcs/PKCS12CertInfo.java |  65 +++
 .../src/netscape/security/pkcs/PKCS12KeyInfo.java  |  56 ++
 .../src/netscape/security/pkcs/PKCS12Util.java     | 642 +++++++++++++++++++++
 37 files changed, 4459 insertions(+), 592 deletions(-)
 delete mode 100644 base/common/python/pki/cli.py
 create mode 100644 base/common/python/pki/cli/__init__.py
 create mode 100644 base/common/python/pki/cli/pkcs12.py
 create mode 100644 base/common/share/etc/logging.properties
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertFindCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertRemoveCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyFindCLI.java
 create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyRemoveCLI.java
 create mode 100644 base/server/python/pki/server/cli/kra.py
 create mode 100644 base/server/python/pki/server/cli/ocsp.py
 create mode 100644 base/server/python/pki/server/cli/tks.py
 create mode 100644 base/server/python/pki/server/cli/tps.py
 create mode 100644 base/util/src/netscape/security/pkcs/PKCS12.java
 create mode 100644 base/util/src/netscape/security/pkcs/PKCS12CertInfo.java
 create mode 100644 base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
 create mode 100644 base/util/src/netscape/security/pkcs/PKCS12Util.java

diff --git a/base/common/CMakeLists.txt b/base/common/CMakeLists.txt
index ee401f2..1213925 100644
--- a/base/common/CMakeLists.txt
+++ b/base/common/CMakeLists.txt
@@ -13,6 +13,7 @@ configure_file(
 
 install(
     FILES
+        ${CMAKE_CURRENT_SOURCE_DIR}/share/etc/logging.properties
         ${CMAKE_CURRENT_BINARY_DIR}/share/etc/pki.conf
     DESTINATION
         ${DATA_INSTALL_DIR}/etc/
diff --git a/base/common/python/pki/cli.py b/base/common/python/pki/cli.py
deleted file mode 100644
index 2c51056..0000000
--- a/base/common/python/pki/cli.py
+++ /dev/null
@@ -1,198 +0,0 @@
-#!/usr/bin/python
-# Authors:
-#     Endi S. Dewata <edewata@redhat.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; version 2 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright (C) 2015 Red Hat, Inc.
-# All rights reserved.
-#
-
-import collections
-import getopt
-import sys
-
-
-class CLI(object):
-
-    def __init__(self, name, description):
-
-        self.name = name
-        self.description = description
-        self.parent = None
-
-        self.verbose = False
-        self.debug = False
-
-        self.modules = collections.OrderedDict()
-
-    def set_verbose(self, verbose):
-        self.verbose = verbose
-        if self.parent:
-            self.parent.set_verbose(verbose)
-
-    def set_debug(self, debug):
-        self.debug = debug
-        if self.parent:
-            self.parent.set_debug(debug)
-
-    def get_full_name(self):
-        if self.parent:
-            return self.parent.get_full_module_name(self.name)
-        return self.name
-
-    def get_full_module_name(self, module_name):
-        return self.get_full_name() + '-' + module_name
-
-    def add_module(self, module):
-        self.modules[module.name] = module
-        module.parent = self
-
-    def get_module(self, name):
-        return self.modules.get(name)
-
-    def get_top_module(self):
-        if self.parent:
-            return self.parent.get_top_module()
-        return self
-
-    def print_message(self, message):
-        print '-' * len(message)
-        print message
-        print '-' * len(message)
-
-    def print_help(self):
-
-        print 'Commands:'
-
-        for module in self.modules.itervalues():
-            full_name = module.get_full_name()
-            print ' {:30}{:30}'.format(full_name, module.description)
-
-    def find_module(self, command):
-
-        module = self
-
-        while True:
-            (module, command) = module.parse_command(command)
-
-            if not module or not command:
-                return module
-
-    def parse_command(self, command):
-
-        # A command consists of parts joined by dashes: <part 1>-<part 2>-...-<part N>.
-        # For example: cert-request-find
-
-        # The command will be split into module name and sub command, for example:
-        #  - module name: cert
-        #  - sub command: request-find
-        module_name = None
-        sub_command = None
-
-        # Search the module by incrementally adding parts into module name.
-        # Repeat until it finds the module or until there is no more parts to add.
-        module = None
-        position = 0
-
-        while True:
-
-            # Find the next dash.
-            i = command.find('-', position)
-            if i >= 0:
-                # Dash found. Split command into module name and sub command.
-                module_name = command[0:i]
-                sub_command = command[i+1:]
-            else:
-                # Dash not found. Use the whole command.
-                module_name = command
-                sub_command = None
-
-            if self.debug:
-                print 'Module: %s' % module_name
-
-            m = self.get_module(module_name)
-            if m:
-                # Module found. Check sub command.
-                if not sub_command:
-                    # No sub command. Use this module.
-                    module = m
-                    break
-
-                # There is a sub command. It must be processed by module's children.
-                if len(m.modules) > 0:
-                    # Module has children. Use this module.
-                    module = m
-                    break
-
-                # Module doesn't have children. Keep looking.
-
-            # If there's no more dashes, stop.
-            if i < 0:
-                break
-
-            position = i + 1
-
-        return (module, sub_command)
-
-    def parse_args(self, args):
-
-        command = args[0]
-        (module, sub_command) = self.parse_command(command)
-
-        if not module:
-            raise Exception('Invalid module "%s".' % command)
-
-        # Prepare module arguments.
-        if sub_command:
-            # If module command exists, include it as arguments: <module command> <args>...
-            module_args = [sub_command] + args[1:]
-
-        else:
-            # Otherwise, pass the original arguments: <args>...
-            module_args = args[1:]
-
-        return (module, module_args)
-
-    def execute(self, argv):
-
-        try:
-            opts, args = getopt.getopt(argv, 'v', [
-                'verbose', 'help'])
-
-        except getopt.GetoptError as e:
-            print 'ERROR: ' + str(e)
-            self.print_help()
-            sys.exit(1)
-
-        if len(args) == 0:
-            self.print_help()
-            sys.exit()
-
-        for o, _ in opts:
-            if o in ('-v', '--verbose'):
-                self.set_verbose(True)
-
-            elif o == '--help':
-                self.print_help()
-                sys.exit()
-
-            else:
-                print 'ERROR: unknown option %s' % o
-                self.print_help()
-                sys.exit(1)
-
-        (module, module_args) = self.parse_args(argv)
-
-        module.execute(module_args)
diff --git a/base/common/python/pki/cli/__init__.py b/base/common/python/pki/cli/__init__.py
new file mode 100644
index 0000000..2c51056
--- /dev/null
+++ b/base/common/python/pki/cli/__init__.py
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+# All rights reserved.
+#
+
+import collections
+import getopt
+import sys
+
+
+class CLI(object):
+
+    def __init__(self, name, description):
+
+        self.name = name
+        self.description = description
+        self.parent = None
+
+        self.verbose = False
+        self.debug = False
+
+        self.modules = collections.OrderedDict()
+
+    def set_verbose(self, verbose):
+        self.verbose = verbose
+        if self.parent:
+            self.parent.set_verbose(verbose)
+
+    def set_debug(self, debug):
+        self.debug = debug
+        if self.parent:
+            self.parent.set_debug(debug)
+
+    def get_full_name(self):
+        if self.parent:
+            return self.parent.get_full_module_name(self.name)
+        return self.name
+
+    def get_full_module_name(self, module_name):
+        return self.get_full_name() + '-' + module_name
+
+    def add_module(self, module):
+        self.modules[module.name] = module
+        module.parent = self
+
+    def get_module(self, name):
+        return self.modules.get(name)
+
+    def get_top_module(self):
+        if self.parent:
+            return self.parent.get_top_module()
+        return self
+
+    def print_message(self, message):
+        print '-' * len(message)
+        print message
+        print '-' * len(message)
+
+    def print_help(self):
+
+        print 'Commands:'
+
+        for module in self.modules.itervalues():
+            full_name = module.get_full_name()
+            print ' {:30}{:30}'.format(full_name, module.description)
+
+    def find_module(self, command):
+
+        module = self
+
+        while True:
+            (module, command) = module.parse_command(command)
+
+            if not module or not command:
+                return module
+
+    def parse_command(self, command):
+
+        # A command consists of parts joined by dashes: <part 1>-<part 2>-...-<part N>.
+        # For example: cert-request-find
+
+        # The command will be split into module name and sub command, for example:
+        #  - module name: cert
+        #  - sub command: request-find
+        module_name = None
+        sub_command = None
+
+        # Search the module by incrementally adding parts into module name.
+        # Repeat until it finds the module or until there is no more parts to add.
+        module = None
+        position = 0
+
+        while True:
+
+            # Find the next dash.
+            i = command.find('-', position)
+            if i >= 0:
+                # Dash found. Split command into module name and sub command.
+                module_name = command[0:i]
+                sub_command = command[i+1:]
+            else:
+                # Dash not found. Use the whole command.
+                module_name = command
+                sub_command = None
+
+            if self.debug:
+                print 'Module: %s' % module_name
+
+            m = self.get_module(module_name)
+            if m:
+                # Module found. Check sub command.
+                if not sub_command:
+                    # No sub command. Use this module.
+                    module = m
+                    break
+
+                # There is a sub command. It must be processed by module's children.
+                if len(m.modules) > 0:
+                    # Module has children. Use this module.
+                    module = m
+                    break
+
+                # Module doesn't have children. Keep looking.
+
+            # If there's no more dashes, stop.
+            if i < 0:
+                break
+
+            position = i + 1
+
+        return (module, sub_command)
+
+    def parse_args(self, args):
+
+        command = args[0]
+        (module, sub_command) = self.parse_command(command)
+
+        if not module:
+            raise Exception('Invalid module "%s".' % command)
+
+        # Prepare module arguments.
+        if sub_command:
+            # If module command exists, include it as arguments: <module command> <args>...
+            module_args = [sub_command] + args[1:]
+
+        else:
+            # Otherwise, pass the original arguments: <args>...
+            module_args = args[1:]
+
+        return (module, module_args)
+
+    def execute(self, argv):
+
+        try:
+            opts, args = getopt.getopt(argv, 'v', [
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print 'ERROR: ' + str(e)
+            self.print_help()
+            sys.exit(1)
+
+        if len(args) == 0:
+            self.print_help()
+            sys.exit()
+
+        for o, _ in opts:
+            if o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print 'ERROR: unknown option %s' % o
+                self.print_help()
+                sys.exit(1)
+
+        (module, module_args) = self.parse_args(argv)
+
+        module.execute(module_args)
diff --git a/base/common/python/pki/cli/pkcs12.py b/base/common/python/pki/cli/pkcs12.py
new file mode 100644
index 0000000..eaca3c6
--- /dev/null
+++ b/base/common/python/pki/cli/pkcs12.py
@@ -0,0 +1,297 @@
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+import pki.cli
+import pki.nssdb
+
+
+class PKCS12CLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(PKCS12CLI, self).__init__(
+            'pkcs12', 'PKCS #12 utilities')
+
+        self.add_module(PKCS12ImportCLI())
+
+
+class PKCS12ImportCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(PKCS12ImportCLI, self).__init__(
+            'import', 'Import PKCS #12 file into NSS database')
+
+    def print_help(self):  # flake8: noqa
+        print('Usage: pki pkcs12-import [OPTIONS]')
+        print()
+        print('      --pkcs12-file <path>           PKCS #12 file containing certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  containing the PKCS #12 password.')
+        print('      --no-trust-flags               Do not include trust flags')
+        print('      --no-user-certs                Do not import user certificates')
+        print('      --no-ca-certs                  Do not import CA certificates')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --debug                        Run in debug mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'v', [
+                'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'no-trust-flags', 'no-user-certs', 'no-ca-certs',
+                'verbose', 'debug', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        pkcs12_file = None
+        pkcs12_password = None
+        password_file = None
+        no_trust_flags = False
+        import_user_certs = True
+        import_ca_certs = True
+        debug = False
+
+        for o, a in opts:
+            if o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                password_file = a
+
+            elif o == '--no-trust-flags':
+                no_trust_flags = True
+
+            elif o == '--no-user-certs':
+                import_user_certs = False
+
+            elif o == '--no-ca-certs':
+                import_ca_certs = False
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--debug':
+                debug = True
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password and not password_file:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        main_cli = self.parent.parent
+
+        # Due to JSS limitation, CA certificates need to be imported
+        # using certutil in order to preserve the nickname stored in
+        # the PKCS #12 file.
+
+        if main_cli.verbose:
+            print('Getting certificate infos in PKCS #12 file')
+
+        certs = []
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            # find all certs in PKCS #12 file
+            output_file = os.path.join(tmpdir, 'pkcs12-cert-find.txt')
+            with open(output_file, 'wb') as f:
+
+                cmd = ['pkcs12-cert-find']
+
+                if pkcs12_file:
+                    cmd.extend(['--pkcs12-file', pkcs12_file])
+
+                if pkcs12_password:
+                    cmd.extend(['--pkcs12-password', pkcs12_password])
+
+                if password_file:
+                    cmd.extend(['--pkcs12-password-file', password_file])
+
+                if no_trust_flags:
+                    cmd.extend(['--no-trust-flags'])
+
+                if self.verbose:
+                    cmd.extend(['--verbose'])
+
+                if debug:
+                    cmd.extend(['--debug'])
+
+                main_cli.execute_java(cmd, stdout=f)
+
+            # parse results
+            with open(output_file, 'r') as f:
+
+                for line in f:
+                    match = re.match(r'  Certificate ID: (.*)$', line)
+                    if match:
+                        cert_info = {}
+                        cert_info['id'] = match.group(1)
+                        certs.append(cert_info)
+                        continue
+
+                    match = re.match(r'  Nickname: (.*)$', line)
+                    if match:
+                        cert_info['nickname'] = match.group(1)
+                        continue
+
+                    match = re.match(r'  Trust Flags: (.*)$', line)
+                    if match:
+                        cert_info['trust_flags'] = match.group(1)
+                        continue
+
+                    match = re.match(r'  Has Key: (.*)$', line)
+                    if match:
+                        cert_info['has_key'] = match.group(1) == 'true'
+                        continue
+
+        finally:
+            shutil.rmtree(tmpdir)
+
+        # import CA certificates if requested
+        if import_ca_certs:
+
+            if main_cli.verbose:
+                print('Importing CA certificates')
+
+            tmpdir = tempfile.mkdtemp()
+
+            try:
+                cert_file = os.path.join(tmpdir, 'ca-cert.pem')
+
+                nssdb = pki.nssdb.NSSDatabase(
+                    main_cli.database,
+                    token=main_cli.token,
+                    password=main_cli.password,
+                    password_file=main_cli.password_file)
+
+                for cert_info in certs:
+
+                    has_key = cert_info['has_key']
+                    if has_key:
+                        continue
+
+                    cert_id = cert_info['id']
+                    nickname = cert_info['nickname']
+                    trust_flags = cert_info['trust_flags']
+
+                    if main_cli.verbose:
+                        print('Exporting %s (%s) from PKCS #12 file' % (nickname, cert_id))
+
+                    cmd = ['pkcs12-cert-export']
+
+                    if pkcs12_file:
+                        cmd.extend(['--pkcs12-file', pkcs12_file])
+
+                    if pkcs12_password:
+                        cmd.extend(['--pkcs12-password', pkcs12_password])
+
+                    if password_file:
+                        cmd.extend(['--pkcs12-password-file', password_file])
+
+                    cmd.extend(['--cert-file', cert_file])
+
+                    cmd.extend(['--cert-id', cert_id])
+
+                    if self.verbose:
+                        cmd.extend(['--verbose'])
+
+                    if debug:
+                        cmd.extend(['--debug'])
+
+                    main_cli.execute_java(cmd)
+
+                    if main_cli.verbose:
+                        print('Importing %s' % nickname)
+
+                    nssdb.add_cert(nickname, cert_file, trust_flags)
+
+            finally:
+                shutil.rmtree(tmpdir)
+
+        # import user certificates if requested
+        if import_user_certs:
+
+            if main_cli.verbose:
+                print('Importing user certificates')
+
+            nicknames = []
+            for cert_info in certs:
+
+                has_key = cert_info['has_key']
+                if not has_key:
+                    continue
+
+                nickname = cert_info['nickname']
+                if nickname not in nicknames:
+                    nicknames.append(nickname)
+
+            cmd = ['pkcs12-import']
+
+            if pkcs12_file:
+                cmd.extend(['--pkcs12-file', pkcs12_file])
+
+            if pkcs12_password:
+                cmd.extend(['--pkcs12-password', pkcs12_password])
+
+            if password_file:
+                cmd.extend(['--pkcs12-password-file', password_file])
+
+            if no_trust_flags:
+                cmd.extend(['--no-trust-flags'])
+
+            if self.verbose:
+                cmd.extend(['--verbose'])
+
+            if debug:
+                cmd.extend(['--debug'])
+
+            cmd.extend(nicknames)
+
+            main_cli.execute_java(cmd)
diff --git a/base/common/python/pki/nssdb.py b/base/common/python/pki/nssdb.py
index 67fd90b..0f9e13d 100644
--- a/base/common/python/pki/nssdb.py
+++ b/base/common/python/pki/nssdb.py
@@ -19,6 +19,7 @@
 # All rights reserved.
 #
 
+from __future__ import absolute_import
 import base64
 import os
 import shutil
@@ -37,7 +38,6 @@ PKCS7_FOOTER = '-----END PKCS7-----'
 
 
 def convert_data(data, input_format, output_format, header=None, footer=None):
-
     if input_format == output_format:
         return data
 
@@ -47,7 +47,7 @@ def convert_data(data, input_format, output_format, header=None, footer=None):
         data = data.replace('\r', '').replace('\n', '')
 
         # re-split the line into fixed-length lines
-        lines = [data[i:i+64] for i in range(0, len(data), 64)]
+        lines = [data[i:i + 64] for i in range(0, len(data), 64)]
 
         # add header and footer
         return '%s\n%s\n%s\n' % (header, '\n'.join(lines), footer)
@@ -68,20 +68,20 @@ def convert_data(data, input_format, output_format, header=None, footer=None):
 
     raise Exception('Unable to convert data from %s to %s' % (input_format, output_format))
 
-def convert_csr(csr_data, input_format, output_format):
 
+def convert_csr(csr_data, input_format, output_format):
     return convert_data(csr_data, input_format, output_format, CSR_HEADER, CSR_FOOTER)
 
-def convert_cert(cert_data, input_format, output_format):
 
+def convert_cert(cert_data, input_format, output_format):
     return convert_data(cert_data, input_format, output_format, CERT_HEADER, CERT_FOOTER)
 
-def convert_pkcs7(pkcs7_data, input_format, output_format):
 
+def convert_pkcs7(pkcs7_data, input_format, output_format):
     return convert_data(pkcs7_data, input_format, output_format, PKCS7_HEADER, PKCS7_FOOTER)
 
-def get_file_type(filename):
 
+def get_file_type(filename):
     with open(filename, 'r') as f:
         data = f.read()
 
@@ -99,7 +99,11 @@ def get_file_type(filename):
 
 class NSSDatabase(object):
 
-    def __init__(self, directory, token='internal', password=None, password_file=None):
+    def __init__(self, directory=None, token=None, password=None, password_file=None):
+
+        if not directory:
+            directory = os.path.join(os.path.expanduser("~"), '.dogtag', 'nssdb')
+
         self.directory = directory
         self.token = token
 
@@ -119,42 +123,44 @@ class NSSDatabase(object):
     def close(self):
         shutil.rmtree(self.tmpdir)
 
-    def add_cert(self,
-        nickname,
-        cert_file,
-        trust_attributes=',,'):
-
+    def add_cert(self, nickname, cert_file, trust_attributes=',,'):
         cmd = [
             'certutil',
             '-A',
-            '-d', self.directory,
-            '-h', self.token,
+            '-d', self.directory
+        ]
+
+        if self.token:
+            cmd.extend(['-h', self.token])
+
+        cmd.extend([
             '-f', self.password_file,
             '-n', nickname,
             '-i', cert_file,
             '-t', trust_attributes
-        ]
+        ])
 
         subprocess.check_call(cmd)
 
-    def modify_cert(self,
-        nickname,
-        trust_attributes):
-
+    def modify_cert(self, nickname, trust_attributes):
         cmd = [
             'certutil',
             '-M',
-            '-d', self.directory,
-            '-h', self.token,
+            '-d', self.directory
+        ]
+
+        if self.token:
+            cmd.extend(['-h', self.token])
+
+        cmd.extend([
             '-f', self.password_file,
             '-n', nickname,
             '-t', trust_attributes
-        ]
+        ])
 
         subprocess.check_call(cmd)
 
     def create_noise(self, noise_file, size=2048):
-
         subprocess.check_call([
             'openssl',
             'rand',
@@ -162,15 +168,9 @@ class NSSDatabase(object):
             str(size)
         ])
 
-    def create_request(self,
-        subject_dn,
-        request_file,
-        noise_file=None,
-        key_type=None,
-        key_size=None,
-        curve=None,
-        hash_alg=None):
-
+    def create_request(self, subject_dn, request_file, noise_file=None,
+                       key_type=None, key_size=None, curve=None,
+                       hash_alg=None):
         tmpdir = tempfile.mkdtemp()
 
         try:
@@ -189,13 +189,18 @@ class NSSDatabase(object):
             cmd = [
                 'certutil',
                 '-R',
-                '-d', self.directory,
-                '-h', self.token,
+                '-d', self.directory
+            ]
+
+            if self.token:
+                cmd.extend(['-h', self.token])
+
+            cmd.extend([
                 '-f', self.password_file,
                 '-s', subject_dn,
                 '-o', binary_request_file,
                 '-z', noise_file
-            ]
+            ])
 
             if key_type:
                 cmd.extend(['-k', key_type])
@@ -230,19 +235,20 @@ class NSSDatabase(object):
         finally:
             shutil.rmtree(tmpdir)
 
-    def create_self_signed_ca_cert(self,
-        subject_dn,
-        request_file,
-        cert_file,
-        serial='1',
-        validity=240):
+    def create_self_signed_ca_cert(self, subject_dn, request_file, cert_file,
+                                   serial='1', validity=240):
 
         cmd = [
             'certutil',
             '-C',
             '-x',
-            '-d', self.directory,
-            '-h', self.token,
+            '-d', self.directory
+        ]
+
+        if self.token:
+            cmd.extend(['-h', self.token])
+
+        cmd.extend([
             '-f', self.password_file,
             '-c', subject_dn,
             '-a',
@@ -255,9 +261,10 @@ class NSSDatabase(object):
             '-3',
             '--extSKID',
             '--extAIA'
-        ]
+        ])
 
-        p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT)
 
         keystroke = ''
 
@@ -334,12 +341,17 @@ class NSSDatabase(object):
         cmd = [
             'certutil',
             '-L',
-            '-d', self.directory,
-            '-h', self.token,
+            '-d', self.directory
+        ]
+
+        if self.token:
+            cmd.extend(['-h', self.token])
+
+        cmd.extend([
             '-f', self.password_file,
             '-n', nickname,
             output_format_option
-        ]
+        ])
 
         cert_data = subprocess.check_output(cmd)
 
@@ -353,38 +365,46 @@ class NSSDatabase(object):
         cmd = [
             'certutil',
             '-D',
-            '-d', self.directory,
-            '-h', self.token,
+            '-d', self.directory
+        ]
+
+        if self.token:
+            cmd.extend(['-h', self.token])
+
+        cmd.extend([
             '-f', self.password_file,
             '-n', nickname
-        ]
+        ])
 
         subprocess.check_call(cmd)
 
-    def import_cert_chain(self, nickname, cert_chain_file, trust_attributes=None):
+    def import_cert_chain(self, nickname, cert_chain_file,
+                          trust_attributes=None):
 
         tmpdir = tempfile.mkdtemp()
 
         try:
             file_type = get_file_type(cert_chain_file)
 
-            if file_type == 'cert': # import single PEM cert
+            if file_type == 'cert':  # import single PEM cert
                 self.add_cert(
                     nickname=nickname,
                     cert_file=cert_chain_file,
                     trust_attributes=trust_attributes)
-                return self.get_cert(
-                    nickname=nickname,
-                    output_format='base64')
+                return (
+                    self.get_cert(nickname=nickname, output_format='base64'),
+                    [nickname]
+                )
 
-            elif file_type == 'pkcs7': # import PKCS #7 cert chain
-                return self.import_pkcs7(
+            elif file_type == 'pkcs7':  # import PKCS #7 cert chain
+                chain, nicks = self.import_pkcs7(
                     pkcs7_file=cert_chain_file,
                     nickname=nickname,
                     trust_attributes=trust_attributes,
                     output_format='base64')
+                return chain, nicks
 
-            else: # import PKCS #7 data without header/footer
+            else:  # import PKCS #7 data without header/footer
                 with open(cert_chain_file, 'r') as f:
                     base64_data = f.read()
                 pkcs7_data = convert_pkcs7(base64_data, 'base64', 'pem')
@@ -393,17 +413,18 @@ class NSSDatabase(object):
                 with open(tmp_cert_chain_file, 'w') as f:
                     f.write(pkcs7_data)
 
-                self.import_pkcs7(
+                chain, nicks = self.import_pkcs7(
                     pkcs7_file=tmp_cert_chain_file,
                     nickname=nickname,
                     trust_attributes=trust_attributes)
 
-                return base64_data
+                return base64_data, nicks
 
         finally:
             shutil.rmtree(tmpdir)
 
-    def import_pkcs7(self, pkcs7_file, nickname, trust_attributes=None, output_format='pem'):
+    def import_pkcs7(self, pkcs7_file, nickname, trust_attributes=None,
+                     output_format='pem'):
 
         tmpdir = tempfile.mkdtemp()
 
@@ -419,6 +440,7 @@ class NSSDatabase(object):
             # parse PEM output into separate PEM certificates
             certs = []
             lines = []
+            nicks = []
             state = 'header'
 
             for line in output.splitlines():
@@ -460,6 +482,7 @@ class NSSDatabase(object):
                     n = '%s #%d' % (nickname, counter)
 
                 self.add_cert(n, cert_file, trust_attributes)
+                nicks.append(n)
 
                 counter += 1
 
@@ -467,12 +490,16 @@ class NSSDatabase(object):
             with open(pkcs7_file, 'r') as f:
                 data = f.read()
 
-            return convert_pkcs7(data, 'pem', output_format)
+            return convert_pkcs7(data, 'pem', output_format), nicks
 
         finally:
             shutil.rmtree(tmpdir)
 
-    def import_pkcs12(self, pkcs12_file, pkcs12_password=None, pkcs12_password_file=None):
+    def import_pkcs12(self, pkcs12_file,
+                      pkcs12_password=None,
+                      pkcs12_password_file=None,
+                      no_user_certs=False,
+                      no_ca_certs=False):
 
         tmpdir = tempfile.mkdtemp()
 
@@ -489,20 +516,35 @@ class NSSDatabase(object):
                 raise Exception('Missing PKCS #12 password')
 
             cmd = [
-                'pk12util',
+                'pki',
                 '-d', self.directory,
-                '-h', self.token,
-                '-k', self.password_file,
-                '-i', pkcs12_file,
-                '-w', password_file
+                '-C', self.password_file
             ]
 
+            if self.token:
+                cmd.extend(['--token', self.token])
+
+            cmd.extend([
+                'pkcs12-import',
+                '--pkcs12-file', pkcs12_file,
+                '--pkcs12-password-file', password_file
+            ])
+
+            if no_user_certs:
+                cmd.extend(['--no-user-certs'])
+
+            if no_ca_certs:
+                cmd.extend(['--no-ca-certs'])
+
             subprocess.check_call(cmd)
 
         finally:
             shutil.rmtree(tmpdir)
 
-    def export_pkcs12(self, pkcs12_file, nickname, pkcs12_password=None, pkcs12_password_file=None):
+    def export_pkcs12(self, pkcs12_file,
+                      pkcs12_password=None,
+                      pkcs12_password_file=None,
+                      nicknames=None):
 
         tmpdir = tempfile.mkdtemp()
 
@@ -519,14 +561,24 @@ class NSSDatabase(object):
                 raise Exception('Missing PKCS #12 password')
 
             cmd = [
-                'pk12util',
+                'pki',
                 '-d', self.directory,
-                '-k', self.password_file,
-                '-o', pkcs12_file,
-                '-w', password_file,
-                '-n', nickname
+                '-C', self.password_file
             ]
 
+            if self.token:
+                cmd.extend(['--token', self.token])
+
+            cmd.extend(['pkcs12-export'])
+
+            cmd.extend([
+                '--pkcs12-file', pkcs12_file,
+                '--pkcs12-password-file', password_file
+            ])
+
+            if nicknames:
+                cmd.extend(nicknames)
+
             subprocess.check_call(cmd)
 
         finally:
diff --git a/base/common/share/etc/logging.properties b/base/common/share/etc/logging.properties
new file mode 100644
index 0000000..bd5b5b6
--- /dev/null
+++ b/base/common/share/etc/logging.properties
@@ -0,0 +1,28 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+# Modifications: configuration parameters
+# --- END COPYRIGHT BLOCK ---
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = ALL
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = %4$s: %5$s%6$s%n
+
+.level = WARNING
diff --git a/base/common/share/etc/pki.conf b/base/common/share/etc/pki.conf
index a43d1d6..57cb83e 100644
--- a/base/common/share/etc/pki.conf
+++ b/base/common/share/etc/pki.conf
@@ -1,2 +1,5 @@
 # JNI jar file location
 JNI_JAR_DIR=/usr/lib/java
+
+# logging configuration location
+LOGGING_CONFIG=/usr/share/pki/etc/logging.properties
diff --git a/base/java-tools/bin/pki b/base/java-tools/bin/pki
index 152bf3f..e476cfc 100644
--- a/base/java-tools/bin/pki
+++ b/base/java-tools/bin/pki
@@ -19,104 +19,240 @@
 # All rights reserved.
 #
 
+from __future__ import absolute_import
+from __future__ import print_function
 import shlex
 import subprocess
 import sys
+import traceback
 
-def run_java_cli(args):
-
-    # read RESTEasy library path
-    value = subprocess.check_output(
-        '. /etc/pki/pki.conf && echo $RESTEASY_LIB',
-        shell=True)
-    resteasy_lib = str(value).strip()
-
-    # construct classpath
-    classpath = [
-        '/usr/share/java/commons-cli.jar',
-        '/usr/share/java/commons-codec.jar',
-        '/usr/share/java/commons-httpclient.jar',
-        '/usr/share/java/commons-io.jar',
-        '/usr/share/java/commons-lang.jar',
-        '/usr/share/java/commons-logging.jar',
-        '/usr/share/java/httpcomponents/httpclient.jar',
-        '/usr/share/java/httpcomponents/httpcore.jar',
-        '/usr/share/java/jackson/jackson-core-asl.jar',
-        '/usr/share/java/jackson/jackson-jaxrs.jar',
-        '/usr/share/java/jackson/jackson-mapper-asl.jar',
-        '/usr/share/java/jackson/jackson-mrbean.jar',
-        '/usr/share/java/jackson/jackson-smile.jar',
-        '/usr/share/java/jackson/jackson-xc.jar',
-        '/usr/share/java/jaxb-api.jar',
-        '/usr/share/java/ldapjdk.jar',
-        '/usr/share/java/servlet.jar',
-        resteasy_lib + '/jaxrs-api.jar',
-        resteasy_lib + '/resteasy-atom-provider.jar',
-        resteasy_lib + '/resteasy-client.jar',
-        resteasy_lib + '/resteasy-jaxb-provider.jar',
-        resteasy_lib + '/resteasy-jaxrs.jar',
-        resteasy_lib + '/resteasy-jaxrs-jandex.jar',
-        resteasy_lib + '/resteasy-jackson-provider.jar',
-        '/usr/share/java/pki/pki-nsutil.jar',
-        '/usr/share/java/pki/pki-cmsutil.jar',
-        '/usr/share/java/pki/pki-certsrv.jar',
-        '/usr/share/java/pki/pki-tools.jar',
-        '/usr/lib64/java/jss4.jar',
-        '/usr/lib/java/jss4.jar'
-    ]
-
-    command = [
-        'java',
-        '-cp',
-        ':'.join(classpath),
-        'com.netscape.cmstools.cli.MainCLI'
-    ]
-
-    command.extend(args)
-
-    rv = subprocess.call(command)
-    exit(rv)
-
-
-# pylint: disable=W0613
-def run_python_cli(args):
-
-    raise Exception('Not implemented')
-
-
-def main(argv):
-
-    # read global options
-    value = subprocess.check_output(
-        '. /etc/pki/pki.conf && echo $PKI_CLI_OPTIONS',
-        shell=True)
-    args = shlex.split(value.strip())
-    args.extend(argv[1:])
-
-    client_type = 'java'
-
-    new_args = []
-
-    # read --client-type parameter and remove it from the argument list
-    i = 0
-    while i < len(args):
-        if args[i] == '--client-type':
-            client_type = args[i + 1]
+import pki.cli
+import pki.cli.pkcs12
+
+
+PYTHON_COMMANDS = ['pkcs12-import']
+
+
+class PKICLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(PKICLI, self).__init__(
+            'pki', 'PKI command-line interface')
+
+        self.database = None
+        self.password = None
+        self.password_file = None
+        self.token = None
+
+        self.add_module(pki.cli.pkcs12.PKCS12CLI())
+
+    def get_full_module_name(self, module_name):
+        return module_name
+
+    def print_help(self):
+        print('Usage: pki [OPTIONS]')
+        print()
+        print('      --client-type <type>     PKI client type (default: java)')
+        print('   -d <path>                   Client security database location ' +
+              '(default: ~/.dogtag/nssdb)')
+        print('   -c <password>               Client security database password ' +
+              '(mutually exclusive to the -C option)')
+        print('   -C <path>                   Client-side password file ' +
+              '(mutually exclusive to the -c option)')
+        print('      --token <name>           Security token name')
+        print()
+        print('  -v, --verbose                Run in verbose mode.')
+        print('      --debug                  Show debug messages.')
+        print('      --help                   Show help message.')
+        print()
+
+        super(PKICLI, self).print_help()
+
+    def execute_java(self, args, stdout=sys.stdout):
+
+        # read RESTEasy library path
+        value = subprocess.check_output(
+            '. /usr/share/pki/etc/pki.conf && . /etc/pki/pki.conf && echo $RESTEASY_LIB',
+            shell=True)
+        resteasy_lib = value.decode(sys.getfilesystemencoding()).strip()
+
+        # read logging configuration path
+        value = subprocess.check_output(
+            '. /usr/share/pki/etc/pki.conf && . /etc/pki/pki.conf && echo $LOGGING_CONFIG',
+            shell=True)
+        logging_config = value.decode(sys.getfilesystemencoding()).strip()
+
+        # construct classpath
+        classpath = [
+            '/usr/share/java/commons-cli.jar',
+            '/usr/share/java/commons-codec.jar',
+            '/usr/share/java/commons-httpclient.jar',
+            '/usr/share/java/commons-io.jar',
+            '/usr/share/java/commons-lang.jar',
+            '/usr/share/java/commons-logging.jar',
+            '/usr/share/java/httpcomponents/httpclient.jar',
+            '/usr/share/java/httpcomponents/httpcore.jar',
+            '/usr/share/java/jackson/jackson-core-asl.jar',
+            '/usr/share/java/jackson/jackson-jaxrs.jar',
+            '/usr/share/java/jackson/jackson-mapper-asl.jar',
+            '/usr/share/java/jackson/jackson-mrbean.jar',
+            '/usr/share/java/jackson/jackson-smile.jar',
+            '/usr/share/java/jackson/jackson-xc.jar',
+            '/usr/share/java/jaxb-api.jar',
+            '/usr/share/java/ldapjdk.jar',
+            '/usr/share/java/servlet.jar',
+            resteasy_lib + '/jaxrs-api.jar',
+            resteasy_lib + '/resteasy-atom-provider.jar',
+            resteasy_lib + '/resteasy-client.jar',
+            resteasy_lib + '/resteasy-jaxb-provider.jar',
+            resteasy_lib + '/resteasy-jaxrs.jar',
+            resteasy_lib + '/resteasy-jaxrs-jandex.jar',
+            resteasy_lib + '/resteasy-jackson-provider.jar',
+            '/usr/share/java/pki/pki-nsutil.jar',
+            '/usr/share/java/pki/pki-cmsutil.jar',
+            '/usr/share/java/pki/pki-certsrv.jar',
+            '/usr/share/java/pki/pki-tools.jar',
+            '/usr/lib64/java/jss4.jar',
+            '/usr/lib/java/jss4.jar'
+        ]
+
+        cmd = [
+            'java',
+            '-cp',
+            ':'.join(classpath),
+            '-Djava.util.logging.config.file=' + logging_config,
+            'com.netscape.cmstools.cli.MainCLI'
+        ]
+
+        # restore options for Java commands
+
+        if self.database:
+            cmd.extend(['-d', self.database])
+
+        if self.password:
+            cmd.extend(['-c', self.password])
+
+        if self.password_file:
+            cmd.extend(['-C', self.password_file])
+
+        if self.token and self.token != 'internal':
+            cmd.extend(['--token', self.token])
+
+        cmd.extend(args)
+
+        if self.verbose:
+            print('Java command: %s' % ' '.join(cmd))
+
+        subprocess.check_call(cmd, stdout=stdout)
+
+    def execute(self, argv):
+
+        # append global options
+        value = subprocess.check_output(
+            '. /usr/share/pki/etc/pki.conf && . /etc/pki/pki.conf && echo $PKI_CLI_OPTIONS',
+            shell=True)
+        value = value.decode(sys.getfilesystemencoding()).strip()
+        args = shlex.split(value)
+        args.extend(argv[1:])
+
+        client_type = 'java'
+
+        pki_options = []
+        command = None
+        cmd_args = []
+
+        # read pki options before the command
+        # remove options for Python module
+
+        i = 0
+        while i < len(args):
+            # if arg is a command, stop
+            if args[i][0] != '-':
+                command = args[i]
+                break
+
+            # get database path
+            if args[i] == '-d':
+                self.database = args[i + 1]
+                pki_options.append(args[i])
+                pki_options.append(args[i + 1])
+                i = i + 2
+
+            # get database password
+            elif args[i] == '-c':
+                self.password = args[i + 1]
+                pki_options.append(args[i])
+                pki_options.append(args[i + 1])
+                i = i + 2
+
+            # get database password file path
+            elif args[i] == '-C':
+                self.password_file = args[i + 1]
+                pki_options.append(args[i])
+                pki_options.append(args[i + 1])
+                i = i + 2
+
+            # get token name
+            elif args[i] == '--token':
+                self.token = args[i + 1]
+                pki_options.append(args[i])
+                pki_options.append(args[i + 1])
+                i = i + 2
+
+            # check verbose option
+            elif args[i] == '-v' or args[i] == '--verbose':
+                self.set_verbose(True)
+                pki_options.append(args[i])
+                i = i + 1
+
+            # check debug option
+            elif args[i] == '--debug':
+                self.set_verbose(True)
+                self.set_debug(True)
+                pki_options.append(args[i])
+                i = i + 1
+
+            # get client type
+            elif args[i] == '--client-type':
+                client_type = args[i + 1]
+                pki_options.append(args[i])
+                pki_options.append(args[i + 1])
+                i = i + 2
+
+            else:  # otherwise, save the arg for the next module
+                cmd_args.append(args[i])
+                i = i + 1
+
+        # save the rest of the args
+        while i < len(args):
+            cmd_args.append(args[i])
             i = i + 1
 
-        else:
-            new_args.append(args[i])
+        if self.verbose:
+            print('PKI options: %s' % ' '.join(pki_options))
+            print('PKI command: %s %s' % (command, ' '.join(cmd_args)))
 
-        i = i + 1
+        if client_type == 'python' or command in PYTHON_COMMANDS:
+            (module, module_args) = self.parse_args(cmd_args)
+            module.execute(module_args)
 
-    if client_type == 'java':
-        run_java_cli(new_args)
+        elif client_type == 'java':
+            self.execute_java(cmd_args)
 
-    elif client_type == 'python':
-        run_python_cli(new_args)
+        else:
+            raise Exception('Unsupported client type: ' + client_type)
 
-    else:
-        raise Exception('Unsupported client type: ' + client_type)
 
 if __name__ == '__main__':
-    main(sys.argv)
+
+    cli = PKICLI()
+
+    try:
+        cli.execute(sys.argv)
+
+    except subprocess.CalledProcessError as e:
+        if cli.verbose:
+            print('ERROR: %s' % e)
+        elif cli.debug:
+            traceback.print_exc()
+        exit(e.returncode)
diff --git a/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java b/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java
index b8999fe..187ff76 100644
--- a/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java
+++ b/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java
@@ -18,39 +18,17 @@
 package com.netscape.cmstools;
 
 import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
 import java.io.FileReader;
-import java.security.MessageDigest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import org.mozilla.jss.CryptoManager;
-import org.mozilla.jss.asn1.ASN1Util;
-import org.mozilla.jss.asn1.ASN1Value;
-import org.mozilla.jss.asn1.BMPString;
-import org.mozilla.jss.asn1.OCTET_STRING;
-import org.mozilla.jss.asn1.SEQUENCE;
-import org.mozilla.jss.asn1.SET;
-import org.mozilla.jss.crypto.Cipher;
-import org.mozilla.jss.crypto.CryptoStore;
 import org.mozilla.jss.crypto.CryptoToken;
-import org.mozilla.jss.crypto.EncryptionAlgorithm;
-import org.mozilla.jss.crypto.IVParameterSpec;
-import org.mozilla.jss.crypto.KeyGenAlgorithm;
-import org.mozilla.jss.crypto.KeyGenerator;
-import org.mozilla.jss.crypto.KeyWrapAlgorithm;
-import org.mozilla.jss.crypto.KeyWrapper;
-import org.mozilla.jss.crypto.PBEAlgorithm;
-import org.mozilla.jss.crypto.SymmetricKey;
-import org.mozilla.jss.crypto.X509Certificate;
-import org.mozilla.jss.pkcs12.AuthenticatedSafes;
-import org.mozilla.jss.pkcs12.CertBag;
-import org.mozilla.jss.pkcs12.PFX;
-import org.mozilla.jss.pkcs12.PasswordConverter;
-import org.mozilla.jss.pkcs12.SafeBag;
-import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
-import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
 import org.mozilla.jss.util.Password;
 
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
 /**
  * Tool for creating PKCS12 file
  *
@@ -61,7 +39,7 @@ import org.mozilla.jss.util.Password;
  */
 public class PKCS12Export {
 
-    boolean debug;
+    private static Logger logger = Logger.getLogger(PKCS12Export.class.getName());
 
     String databaseDirectory;
     String databasePasswordFilename;
@@ -69,14 +47,6 @@ public class PKCS12Export {
     String pkcs12PasswordFilename;
     String pkcs12OutputFilename;
 
-    public boolean isDebug() {
-        return debug;
-    }
-
-    public void setDebug(boolean debug) {
-        this.debug = debug;
-    }
-
     public String getDatabaseDirectory() {
         return databaseDirectory;
     }
@@ -108,152 +78,9 @@ public class PKCS12Export {
         this.pkcs12OutputFilename = pkcs12OutputFilename;
     }
 
-    void debug(String s) {
-        if (debug)
-            System.out.println("PKCS12Export: " + s);
-    }
-
-    byte[] getEncodedKey(org.mozilla.jss.crypto.PrivateKey pkey) throws Exception {
-
-        CryptoManager cm = CryptoManager.getInstance();
-        CryptoToken token = cm.getInternalKeyStorageToken();
-
-        KeyGenerator kg = token.getKeyGenerator(KeyGenAlgorithm.DES3);
-        SymmetricKey sk = kg.generate();
-
-        KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
-        byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
-        IVParameterSpec param = new IVParameterSpec(iv);
-        wrapper.initWrap(sk, param);
-        byte[] enckey = wrapper.wrap(pkey);
-
-        Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
-        c.initDecrypt(sk, param);
-        return c.doFinal(enckey);
-    }
-
-    void addKeyBag(org.mozilla.jss.crypto.PrivateKey pkey, X509Certificate x509cert,
-            Password pass, byte[] localKeyId, SEQUENCE safeContents) throws Exception {
-
-        PasswordConverter passConverter = new PasswordConverter();
-        byte salt[] = { 0x01, 0x01, 0x01, 0x01 };
-        byte[] priData = getEncodedKey(pkey);
-
-        PrivateKeyInfo pki = (PrivateKeyInfo)
-                ASN1Util.decode(PrivateKeyInfo.getTemplate(), priData);
-
-        ASN1Value key = EncryptedPrivateKeyInfo.createPBE(
-                PBEAlgorithm.PBE_SHA1_DES3_CBC,
-                pass, salt, 1, passConverter, pki);
-
-        SET keyAttrs = createBagAttrs(
-                x509cert.getSubjectDN().toString(), localKeyId);
-
-        SafeBag keyBag = new SafeBag(SafeBag.PKCS8_SHROUDED_KEY_BAG,
-                key, keyAttrs);
-
-        safeContents.addElement(keyBag);
-    }
-
-    byte[] addCertBag(X509Certificate x509cert, String nickname,
-            SEQUENCE safeContents) throws Exception {
-
-        ASN1Value cert = new OCTET_STRING(x509cert.getEncoded());
-        byte[] localKeyId = createLocalKeyId(x509cert);
-
-        SET certAttrs = null;
-        if (nickname != null)
-            certAttrs = createBagAttrs(nickname, localKeyId);
-
-        SafeBag certBag = new SafeBag(SafeBag.CERT_BAG,
-                new CertBag(CertBag.X509_CERT_TYPE, cert), certAttrs);
-
-        safeContents.addElement(certBag);
-
-        return localKeyId;
-    }
-
-    byte[] createLocalKeyId(X509Certificate cert) throws Exception {
-
-        // SHA1 hash of the X509Cert der encoding
-        byte certDer[] = cert.getEncoded();
-
-        MessageDigest md = MessageDigest.getInstance("SHA");
-
-        md.update(certDer);
-        return md.digest();
-    }
-
-    SET createBagAttrs(String nickName, byte localKeyId[])
-            throws Exception {
-
-        SET attrs = new SET();
-        SEQUENCE nickNameAttr = new SEQUENCE();
-
-        nickNameAttr.addElement(SafeBag.FRIENDLY_NAME);
-        SET nickNameSet = new SET();
-
-        nickNameSet.addElement(new BMPString(nickName));
-        nickNameAttr.addElement(nickNameSet);
-        attrs.addElement(nickNameAttr);
-        SEQUENCE localKeyAttr = new SEQUENCE();
-
-        localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID);
-        SET localKeySet = new SET();
-
-        localKeySet.addElement(new OCTET_STRING(localKeyId));
-        localKeyAttr.addElement(localKeySet);
-        attrs.addElement(localKeyAttr);
-
-        return attrs;
-    }
-
-    public byte[] generatePKCS12Data(Password password) throws Exception {
-
-        debug("Generating PKCS #12 data");
-
-        CryptoManager cm = CryptoManager.getInstance();
-        CryptoToken token = cm.getInternalKeyStorageToken();
-        CryptoStore store = token.getCryptoStore();
-
-        X509Certificate[] certs = store.getCertificates();
-
-        SEQUENCE encSafeContents = new SEQUENCE();
-        SEQUENCE safeContents = new SEQUENCE();
-
-        for (int i = 0; i < certs.length; i++) {
-            String nickname = certs[i].getNickname();
-            debug(" * Certificate: " + nickname);
-            try {
-                org.mozilla.jss.crypto.PrivateKey prikey = cm.findPrivKeyByCert(certs[i]);
-
-                debug("   Private key exists");
-                byte localKeyId[] =
-                        addCertBag(certs[i], nickname, safeContents);
-                addKeyBag(prikey, certs[i], password, localKeyId, encSafeContents);
-
-            } catch (org.mozilla.jss.crypto.ObjectNotFoundException e) {
-                debug("   Private key does not exist");
-                addCertBag(certs[i], null, safeContents);
-            }
-        }
-
-        AuthenticatedSafes authSafes = new AuthenticatedSafes();
-        authSafes.addSafeContents(safeContents);
-        authSafes.addSafeContents(encSafeContents);
-
-        PFX pfx = new PFX(authSafes);
-        pfx.computeMacData(password, null, 5);
-
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        pfx.encode(bos);
-
-        return bos.toByteArray();
-    }
-
     public void initDatabase() throws Exception {
 
-        debug("Initializing database in " + databaseDirectory);
+        logger.info("Initializing database in " + databaseDirectory);
 
         CryptoManager.InitializationValues vals =
                 new CryptoManager.InitializationValues(
@@ -263,7 +90,7 @@ public class PKCS12Export {
         CryptoManager cm = CryptoManager.getInstance();
         CryptoToken token = cm.getInternalKeyStorageToken();
 
-        debug("Reading database password from " + databasePasswordFilename);
+        logger.info("Reading database password from " + databasePasswordFilename);
 
         String line;
         try (BufferedReader in = new BufferedReader(new FileReader(databasePasswordFilename))) {
@@ -274,7 +101,7 @@ public class PKCS12Export {
         }
         Password password = new Password(line.toCharArray());
 
-        debug("Logging into security token");
+        logger.info("Logging into security token");
 
         try {
             token.login(password);
@@ -285,7 +112,7 @@ public class PKCS12Export {
 
     public void exportData() throws Exception {
 
-        debug("Reading PKCS #12 password from " + pkcs12PasswordFilename);
+        logger.info("Reading PKCS #12 password from " + pkcs12PasswordFilename);
 
         String line;
         try (BufferedReader in = new BufferedReader(new FileReader(pkcs12PasswordFilename))) {
@@ -296,18 +123,17 @@ public class PKCS12Export {
         }
         Password password = new Password(line.toCharArray());
 
-        byte[] data;
+        logger.info("Exporting NSS database into " + pkcs12OutputFilename);
+
         try {
-            data = generatePKCS12Data(password);
+            PKCS12Util util = new PKCS12Util();
+            PKCS12 pkcs12 = new PKCS12();
+            util.loadFromNSS(pkcs12);
+            util.storeIntoFile(pkcs12, pkcs12OutputFilename, password);
+
         } finally {
             password.clear();
         }
-
-        debug("Storing PKCS #12 data into " + pkcs12OutputFilename);
-
-        try (FileOutputStream fos = new FileOutputStream(pkcs12OutputFilename)) {
-            fos.write(data);
-        }
     }
 
     public static void printUsage() {
@@ -355,11 +181,16 @@ public class PKCS12Export {
             }
         }
 
+        if (debug) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
         // TODO: validate parameters
 
         try {
             PKCS12Export tool = new PKCS12Export();
-            tool.setDebug(debug);
             tool.setDatabaseDirectory(databaseDirectory);
             tool.setDatabasePasswordFilename(databasePasswordFilename);
             tool.setPkcs12PasswordFilename(pkcs12PasswordFilename);
@@ -368,11 +199,13 @@ public class PKCS12Export {
             tool.initDatabase();
             tool.exportData();
 
+            System.out.println("Export complete.");
+
         } catch (Exception e) {
             if (debug) {
-                e.printStackTrace();
+                logger.log(Level.SEVERE, "Unable to export PKCS #12 file", e);
             } else {
-                System.err.println("ERROR: " + e);
+                logger.severe("Unable to export PKCS #12 file: " + e.getMessage());
             }
             System.exit(1);
         }
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
index 159e4ac..797f3cb 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
@@ -49,6 +49,7 @@ import com.netscape.cmstools.cert.CertCLI;
 import com.netscape.cmstools.client.ClientCLI;
 import com.netscape.cmstools.group.GroupCLI;
 import com.netscape.cmstools.key.KeyCLI;
+import com.netscape.cmstools.pkcs12.PKCS12CLI;
 import com.netscape.cmstools.system.SecurityDomainCLI;
 import com.netscape.cmstools.user.UserCLI;
 
@@ -85,6 +86,8 @@ public class MainCLI extends CLI {
         addModule(new TKSCLI(this));
         addModule(new TPSCLI(this));
 
+        addModule(new PKCS12CLI(this));
+
         createOptions();
     }
 
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java
new file mode 100644
index 0000000..b841ec0
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java
@@ -0,0 +1,47 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CLI extends CLI {
+
+    public PKCS12CLI(CLI parent) {
+        super("pkcs12", "PKCS #12 utilities", parent);
+
+        addModule(new PKCS12CertCLI(this));
+        addModule(new PKCS12ExportCLI(this));
+        addModule(new PKCS12ImportCLI(this));
+        addModule(new PKCS12KeyCLI(this));
+    }
+
+    public String getFullName() {
+        if (parent instanceof MainCLI) {
+            // do not include MainCLI's name
+            return name;
+        } else {
+            return parent.getFullName() + "-" + name;
+        }
+    }
+
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
new file mode 100644
index 0000000..48e4907
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
@@ -0,0 +1,167 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CertAddCLI extends CLI {
+
+    public PKCS12CertAddCLI(PKCS12CertCLI certCLI) {
+        super("add", "Add certificate into PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <nickname> [OPTIONS...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption(null, "new-file", false, "Create a new PKCS #12 file");
+        options.addOption(null, "no-trust-flags", false, "Do not include trust flags");
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length == 0) {
+            System.err.println("Error: Missing certificate nickname.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String nickname = cmdArgs[0];
+
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        boolean newFile = cmd.hasOption("new-file");
+        boolean includeTrustFlags = !cmd.hasOption("no-trust-flags");
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            util.setTrustFlagsEnabled(includeTrustFlags);
+
+            PKCS12 pkcs12;
+
+            if (newFile || !new File(filename).exists()) {
+                // if new file requested or file does not exist, create a new file
+                pkcs12 = new PKCS12();
+
+            } else {
+                // otherwise, add into the existing file
+                pkcs12 = util.loadFromFile(filename, password);
+            }
+
+            util.loadCertFromNSS(pkcs12, nickname);
+            util.storeIntoFile(pkcs12, filename, password);
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage("Added certificate \"" + nickname + "\"");
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java
new file mode 100644
index 0000000..fe7092c
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java
@@ -0,0 +1,59 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.math.BigInteger;
+
+import com.netscape.certsrv.dbs.certdb.CertId;
+import com.netscape.cmstools.cli.CLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12CertInfo;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CertCLI extends CLI {
+
+    public PKCS12CertCLI(PKCS12CLI parent) {
+        super("cert", "PKCS #12 certificate management commands", parent);
+
+        addModule(new PKCS12CertAddCLI(this));
+        addModule(new PKCS12CertExportCLI(this));
+        addModule(new PKCS12CertFindCLI(this));
+        addModule(new PKCS12CertRemoveCLI(this));
+    }
+
+    public static void printCertInfo(PKCS12 pkcs12, PKCS12CertInfo certInfo) throws Exception {
+
+        BigInteger id = certInfo.getID();
+        System.out.println("  Certificate ID: " + id.toString(16));
+
+        System.out.println("  Serial Number: " + new CertId(certInfo.getCert().getSerialNumber()).toHexString());
+        System.out.println("  Nickname: " + certInfo.getNickname());
+        System.out.println("  Subject DN: " + certInfo.getCert().getSubjectDN());
+        System.out.println("  Issuer DN: " + certInfo.getCert().getIssuerDN());
+
+        if (certInfo.getTrustFlags() != null) {
+            System.out.println("  Trust Flags: " + certInfo.getTrustFlags());
+        }
+
+        System.out.println("  Has Key: " + (pkcs12.getKeyInfoByID(id) != null));
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java
new file mode 100644
index 0000000..8fb526d
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java
@@ -0,0 +1,207 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.PrintStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmsutil.util.Utils;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12CertInfo;
+import netscape.security.pkcs.PKCS12Util;
+import netscape.security.x509.X509CertImpl;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CertExportCLI extends CLI {
+
+    public PKCS12CertExportCLI(PKCS12CertCLI certCLI) {
+        super("export", "Export certificate from PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...] [nickname]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "cert-file", true, "Certificate file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "cert-id", true, "Certificate ID to export");
+        option.setArgName("ID");
+        options.addOption(option);
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+        String id = cmd.getOptionValue("cert-id");
+
+        if (cmdArgs.length < 1 && id == null) {
+            System.err.println("Error: Missing certificate nickname or ID.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmdArgs.length >= 1 && id != null) {
+            System.err.println("Error: Certificate nickname and ID are mutually exclusive.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String nickname = null;
+        BigInteger certID = null;
+
+        if (cmdArgs.length >= 1) {
+            nickname = cmdArgs[0];
+        } else {
+            certID = new BigInteger(id, 16);
+        }
+
+        String pkcs12File = cmd.getOptionValue("pkcs12-file");
+
+        if (pkcs12File == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        String certFile = cmd.getOptionValue("cert-file");
+
+        if (certFile == null) {
+            System.err.println("Error: Missing certificate file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            PKCS12 pkcs12 = util.loadFromFile(pkcs12File, password);
+
+            Collection<PKCS12CertInfo> certInfos = new ArrayList<PKCS12CertInfo>();
+
+            if (nickname != null) {
+                certInfos.addAll(pkcs12.getCertInfosByNickname(nickname));
+
+            } else {
+                PKCS12CertInfo certInfo = pkcs12.getCertInfoByID(certID);
+                if (certInfo != null) {
+                    certInfos.add(certInfo);
+                }
+            }
+
+            if (certInfos.isEmpty()) {
+                System.err.println("Error: Certificate not found.");
+                System.exit(-1);
+            }
+
+            try (PrintStream os = new PrintStream(new FileOutputStream(certFile))) {
+                for (PKCS12CertInfo certInfo : certInfos) {
+                    X509CertImpl cert = certInfo.getCert();
+                    os.println("-----BEGIN CERTIFICATE-----");
+                    os.print(Utils.base64encode(cert.getEncoded()));
+                    os.println("-----END CERTIFICATE-----");
+                }
+            }
+
+        } finally {
+            password.clear();
+        }
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertFindCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertFindCLI.java
new file mode 100644
index 0000000..9bb4ad3
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertFindCLI.java
@@ -0,0 +1,162 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12CertInfo;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CertFindCLI extends CLI {
+
+    public PKCS12CertFindCLI(PKCS12CertCLI certCLI) {
+        super("find", "Find certificates in PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+         options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 0) {
+            System.err.println("Error: Too many arguments specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        PKCS12 pkcs12;
+        try {
+            PKCS12Util util = new PKCS12Util();
+            pkcs12 = util.loadFromFile(filename, password);
+
+        } finally {
+            password.clear();
+        }
+
+        Collection<PKCS12CertInfo> certInfos = pkcs12.getCertInfos();
+
+        MainCLI.printMessage(certInfos.size() + " entries found");
+        if (certInfos.size() == 0) return;
+
+        boolean first = true;
+
+        for (PKCS12CertInfo certInfo : certInfos) {
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            PKCS12CertCLI.printCertInfo(pkcs12, certInfo);
+        }
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertRemoveCLI.java
new file mode 100644
index 0000000..8f7f113
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertRemoveCLI.java
@@ -0,0 +1,149 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12CertRemoveCLI extends CLI {
+
+    public PKCS12CertRemoveCLI(PKCS12CertCLI certCLI) {
+        super("del", "Remove certificate from PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <nickname> [OPTIONS...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length == 0) {
+            System.err.println("Error: Missing certificate nickname.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String nickname = cmdArgs[0];
+
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+
+            PKCS12 pkcs12 = util.loadFromFile(filename, password);
+            pkcs12.removeCertInfoByNickname(nickname);
+            util.storeIntoFile(pkcs12, filename, password);
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage("Deleted certificate \"" + nickname + "\"");
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
new file mode 100644
index 0000000..d42c449
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
@@ -0,0 +1,166 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * Tool for exporting NSS database into PKCS #12 file
+ */
+public class PKCS12ExportCLI extends CLI {
+
+    public PKCS12ExportCLI(PKCS12CLI certCLI) {
+        super("export", "Export NSS database into PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...] [nicknames...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption(null, "new-file", false, "Create a new PKCS #12 file");
+        options.addOption(null, "no-trust-flags", false, "Do not include trust flags");
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args, true);
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] nicknames = cmd.getArgs();
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        boolean newFile = cmd.hasOption("new-file");
+        boolean trustFlagsEnabled = !cmd.hasOption("no-trust-flags");
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            util.setTrustFlagsEnabled(trustFlagsEnabled);
+
+            PKCS12 pkcs12;
+
+            if (newFile || !new File(filename).exists()) {
+                // if new file requested or file does not exist, create a new file
+                pkcs12 = new PKCS12();
+
+            } else {
+                // otherwise, export into the existing file
+                pkcs12 = util.loadFromFile(filename, password);
+            }
+
+            if (nicknames.length == 0) {
+                // load all certificates
+                util.loadFromNSS(pkcs12);
+
+            } else {
+                // load specified certificates
+                for (String nickname : nicknames) {
+                    util.loadCertFromNSS(pkcs12, nickname);
+                }
+            }
+
+            util.storeIntoFile(pkcs12, filename, password);
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage("Export complete");
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
new file mode 100644
index 0000000..ae574d3
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java
@@ -0,0 +1,153 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * Tool for importing NSS database from PKCS #12 file
+ */
+public class PKCS12ImportCLI extends CLI {
+
+    public PKCS12ImportCLI(PKCS12CLI certCLI) {
+        super("import", "Import PKCS #12 file into NSS database", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...] [nicknames...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption(null, "no-trust-flags", false, "Do not include trust flags");
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args, true);
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] nicknames = cmd.getArgs();
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        boolean trustFlagsEnabled = !cmd.hasOption("no-trust-flags");
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            util.setTrustFlagsEnabled(trustFlagsEnabled);
+
+            PKCS12 pkcs12 = util.loadFromFile(filename, password);
+
+            if (nicknames.length == 0) {
+                // store all certificates
+                util.storeIntoNSS(pkcs12);
+
+            } else {
+                // load specified certificates
+                for (String nickname : nicknames) {
+                    util.storeCertIntoNSS(pkcs12, nickname);
+                }
+            }
+
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage("Import complete");
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
new file mode 100644
index 0000000..fbebdda
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java
@@ -0,0 +1,43 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import com.netscape.cmstools.cli.CLI;
+
+import netscape.security.pkcs.PKCS12KeyInfo;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12KeyCLI extends CLI {
+
+    public PKCS12KeyCLI(PKCS12CLI parent) {
+        super("key", "PKCS #12 key management commands", parent);
+
+        addModule(new PKCS12KeyFindCLI(this));
+        addModule(new PKCS12KeyRemoveCLI(this));
+    }
+
+    public static void printKeyInfo(PKCS12KeyInfo keyInfo) throws Exception {
+
+        System.out.println("  Key ID: " + keyInfo.getID().toString(16));
+        System.out.println("  Subject DN: " + keyInfo.getSubjectDN());
+        System.out.println("  Algorithm: " + keyInfo.getPrivateKeyInfo().getAlgorithm());
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyFindCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyFindCLI.java
new file mode 100644
index 0000000..0dc39f4
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyFindCLI.java
@@ -0,0 +1,163 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12KeyInfo;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12KeyFindCLI extends CLI {
+
+    public PKCS12KeyFindCLI(PKCS12KeyCLI certCLI) {
+        super("find", "Find keys in PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " [OPTIONS...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 0) {
+            System.err.println("Error: Too many arguments specified.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        Collection<PKCS12KeyInfo> keyInfos;
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            PKCS12 pkcs12 = util.loadFromFile(filename, password);
+
+            keyInfos = pkcs12.getKeyInfos();
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage(keyInfos.size() + " entries found");
+        if (keyInfos.size() == 0) return;
+
+        boolean first = true;
+
+        for (PKCS12KeyInfo keyInfo : keyInfos) {
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            PKCS12KeyCLI.printKeyInfo(keyInfo);
+        }
+    }
+}
diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyRemoveCLI.java
new file mode 100644
index 0000000..19b3687
--- /dev/null
+++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyRemoveCLI.java
@@ -0,0 +1,150 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cmstools.pkcs12;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.math.BigInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.ParseException;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cmstools.cli.CLI;
+import com.netscape.cmstools.cli.MainCLI;
+
+import netscape.security.pkcs.PKCS12;
+import netscape.security.pkcs.PKCS12Util;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class PKCS12KeyRemoveCLI extends CLI {
+
+    public PKCS12KeyRemoveCLI(PKCS12KeyCLI certCLI) {
+        super("del", "Remove key from PKCS #12 file", certCLI);
+
+        createOptions();
+    }
+
+    public void printHelp() {
+        formatter.printHelp(getFullName() + " <key ID> [OPTIONS...]", options);
+    }
+
+    public void createOptions() {
+        Option option = new Option(null, "pkcs12-file", true, "PKCS #12 file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password", true, "PKCS #12 password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file");
+        option.setArgName("path");
+        options.addOption(option);
+
+        options.addOption("v", "verbose", false, "Run in verbose mode.");
+        options.addOption(null, "debug", false, "Run in debug mode.");
+        options.addOption(null, "help", false, "Show help message.");
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+        } catch (ParseException e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(-1);
+        }
+
+        if (cmd.hasOption("help")) {
+            printHelp();
+            System.exit(0);
+        }
+
+        if (cmd.hasOption("verbose")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.INFO);
+            Logger.getLogger("com.netscape").setLevel(Level.INFO);
+            Logger.getLogger("netscape").setLevel(Level.INFO);
+
+        } else if (cmd.hasOption("debug")) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length == 0) {
+            System.err.println("Error: Missing key ID.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        BigInteger keyID = new BigInteger(cmdArgs[0], 16);
+
+        String filename = cmd.getOptionValue("pkcs12-file");
+
+        if (filename == null) {
+            System.err.println("Error: Missing PKCS #12 file.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        String passwordString = cmd.getOptionValue("pkcs12-password");
+
+        if (passwordString == null) {
+
+            String passwordFile = cmd.getOptionValue("pkcs12-password-file");
+            if (passwordFile != null) {
+                try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) {
+                    passwordString = in.readLine();
+                }
+            }
+        }
+
+        if (passwordString == null) {
+            System.err.println("Error: Missing PKCS #12 password.");
+            printHelp();
+            System.exit(-1);
+        }
+
+        Password password = new Password(passwordString.toCharArray());
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+
+            PKCS12 pkcs12 = util.loadFromFile(filename, password);
+            pkcs12.removeKeyInfoByID(keyID);
+            util.storeIntoFile(pkcs12, filename, password);
+
+        } finally {
+            password.clear();
+        }
+
+        MainCLI.printMessage("Deleted key \"" + keyID.toString(16) + "\"");
+    }
+}
diff --git a/base/java-tools/templates/pki_java_command_wrapper.in b/base/java-tools/templates/pki_java_command_wrapper.in
index 404bcf0..56ca9f1 100644
--- a/base/java-tools/templates/pki_java_command_wrapper.in
+++ b/base/java-tools/templates/pki_java_command_wrapper.in
@@ -124,12 +124,17 @@ CP=/usr/share/java/${PRODUCT}/pki-cmsutil.jar:${CP}
 CP=/usr/share/java/${PRODUCT}/pki-tools.jar:${CP}
 export CP
 
+LOGGING_CONFIG=`source /usr/share/pki/etc/pki.conf && source /etc/pki/pki.conf && echo $LOGGING_CONFIG`
 
 ###############################################################################
 ##  (6) Execute the java command specified by this java command wrapper      ##
 ##      based upon the preset LD_LIBRARY_PATH and CP environment variables.  ##
 ###############################################################################
 
-${JAVA} ${JAVA_OPTIONS} -cp ${CP} com.netscape.cmstools.${COMMAND} "$@"
+${JAVA} ${JAVA_OPTIONS} \
+  -cp ${CP} \
+  -Djava.util.logging.config.file=${LOGGING_CONFIG} \
+  com.netscape.cmstools.${COMMAND} "$@"
+
 exit $?
 
diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py
index b2fffd5..2b9efad 100644
--- a/base/server/python/pki/server/__init__.py
+++ b/base/server/python/pki/server/__init__.py
@@ -20,6 +20,7 @@
 #
 
 from lxml import etree
+import functools
 import getpass
 import grp
 import io
@@ -58,15 +59,15 @@ class PKIServer(object):
         return instances
 
 
+@functools.total_ordering
 class PKISubsystem(object):
 
     def __init__(self, instance, subsystem_name):
 
         self.instance = instance
-        self.name = subsystem_name
-        self.type = instance.type
+        self.name = subsystem_name  # e.g. ca, kra
 
-        if self.type >= 10:
+        if instance.type >= 10:
             self.base_dir = os.path.join(self.instance.base_dir, self.name)
         else:
             self.base_dir = instance.base_dir
@@ -81,12 +82,35 @@ class PKISubsystem(object):
             instance.conf_dir, 'Catalina', 'localhost', self.name + '.xml')
 
         self.config = {}
-        self.type = None
-        self.prefix = None
+        self.type = None    # e.g. CA, KRA
+        self.prefix = None  # e.g. ca, kra
 
         # custom subsystem location
         self.doc_base = os.path.join(self.base_dir, 'webapps', self.name)
 
+    def __eq__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        return (self.name == other.name and
+                self.instance == other.instance and
+                self.type == other.type)
+
+    def __ne__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        return not self.__eq__(other)
+
+    def __lt__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        self_type = self.type if self.type is not None else ''
+        other_type = other.type if other.type is not None else ''
+        return (self.name < other.name or
+                self.instance < other.instance or
+                self_type < other_type)
+
+    __hash__ = None
+
     def load(self):
         self.config.clear()
 
@@ -101,7 +125,7 @@ class PKISubsystem(object):
         self.type = self.config['cs.type']
         self.prefix = self.type.lower()
 
-    def find_subsystem_certs(self):
+    def find_system_certs(self):
         certs = []
 
         cert_ids = self.config['%s.cert.list' % self.name].split(',')
@@ -130,6 +154,116 @@ class PKISubsystem(object):
         self.config['%s.%s.cert' % (self.name, cert_id)] = cert.get('data', None)
         self.config['%s.%s.certreq' % (self.name, cert_id)] = cert.get('request', None)
 
+    def export_system_cert(
+            self,
+            cert_id,
+            pkcs12_file,
+            pkcs12_password_file,
+            new_file=False):
+
+        cert = self.get_subsystem_cert(cert_id)
+        nickname = cert['nickname']
+        token = cert['token']
+        if token == 'Internal Key Storage Token':
+            token = 'internal'
+        nssdb_password = self.instance.get_password(token)
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            nssdb_password_file = os.path.join(tmpdir, 'password.txt')
+            with open(nssdb_password_file, 'w') as f:
+                f.write(nssdb_password)
+
+            # add the certificate, key, and chain
+            cmd = [
+                'pki',
+                '-d', self.instance.nssdb_dir,
+                '-C', nssdb_password_file
+            ]
+
+            if token and token != 'internal':
+                cmd.extend(['--token', token])
+
+            cmd.extend([
+                'pkcs12-cert-add',
+                '--pkcs12-file', pkcs12_file,
+                '--pkcs12-password-file', pkcs12_password_file,
+            ])
+
+            if new_file:
+                cmd.extend(['--new-file'])
+
+            cmd.extend([
+                nickname
+            ])
+
+            subprocess.check_call(cmd)
+
+        finally:
+            shutil.rmtree(tmpdir)
+
+    def export_cert_chain(
+            self,
+            pkcs12_file,
+            pkcs12_password_file):
+
+        # use subsystem certificate to get certificate chain
+        cert = self.get_subsystem_cert('subsystem')
+        nickname = cert['nickname']
+        token = cert['token']
+        if token == 'Internal Key Storage Token':
+            token = 'internal'
+        nssdb_password = self.instance.get_password(token)
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            nssdb_password_file = os.path.join(tmpdir, 'password.txt')
+            with open(nssdb_password_file, 'w') as f:
+                f.write(nssdb_password)
+
+            # export the certificate, key, and chain
+            cmd = [
+                'pki',
+                '-d', self.instance.nssdb_dir,
+                '-C', nssdb_password_file
+            ]
+
+            if token and token != 'internal':
+                cmd.extend(['--token', token])
+
+            cmd.extend([
+                'pkcs12-export',
+                '--pkcs12-file', pkcs12_file,
+                '--pkcs12-password-file', pkcs12_password_file,
+                nickname
+            ])
+
+            subprocess.check_call(cmd)
+
+            # remove the certificate and key, but keep the chain
+            cmd = [
+                'pki',
+                '-d', self.instance.nssdb_dir,
+                '-C', nssdb_password_file
+            ]
+
+            if token and token != 'internal':
+                cmd.extend(['--token', token])
+
+            cmd.extend([
+                'pkcs12-cert-del',
+                '--pkcs12-file', pkcs12_file,
+                '--pkcs12-password-file', pkcs12_password_file,
+                nickname
+            ])
+
+            subprocess.check_call(cmd)
+
+        finally:
+            shutil.rmtree(tmpdir)
+
     def save(self):
         sorted_config = sorted(self.config.items(), key=operator.itemgetter(0))
         with io.open(self.cs_conf, 'wb') as f:
@@ -240,6 +374,25 @@ class PKIInstance(object):
 
         self.subsystems = []
 
+    def __eq__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return (self.name == other.name and
+                self.type == other.type)
+
+    def __ne__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return not self.__eq__(other)
+
+    def __lt__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return (self.name < other.name or
+                self.type < other.type)
+
+    __hash__ = None
+
     def is_valid(self):
         return os.path.exists(self.conf_dir)
 
diff --git a/base/server/python/pki/server/cli/ca.py b/base/server/python/pki/server/cli/ca.py
index 2ad8652..fe8ce2b 100644
--- a/base/server/python/pki/server/cli/ca.py
+++ b/base/server/python/pki/server/cli/ca.py
@@ -23,10 +23,12 @@ from __future__ import absolute_import
 from __future__ import print_function
 import getopt
 import io
+import os
+import shutil
 import sys
+import tempfile
 
 import pki.cli
-import pki.server.ca
 
 
 class CACLI(pki.cli.CLI):
@@ -36,6 +38,7 @@ class CACLI(pki.cli.CLI):
             'ca', 'CA management commands')
 
         self.add_module(CACertCLI())
+        self.add_module(CACloneCLI())
 
 
 class CACertCLI(pki.cli.CLI):
@@ -44,9 +47,106 @@ class CACertCLI(pki.cli.CLI):
         super(CACertCLI, self).__init__(
             'cert', 'CA certificates management commands')
 
+        self.add_module(CACertChainCLI())
         self.add_module(CACertRequestCLI())
 
 
+class CACertChainCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(CACertChainCLI, self).__init__(
+            'chain', 'CA certificate chain management commands')
+
+        self.add_module(CACertChainExportCLI())
+
+
+class CACertChainExportCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(CACertChainExportCLI, self).__init__(
+            'export', 'Export certificate chain')
+
+    def print_help(self):
+        print('Usage: pki-server ca-cert-chain-export [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('ca')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_cert_chain(pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
+
+
 class CACertRequestCLI(pki.cli.CLI):
 
     def __init__(self):
@@ -204,3 +304,106 @@ class CACertRequestShowCLI(pki.cli.CLI):
 
         else:
             CACertRequestCLI.print_request(request, details=True)
+
+
+class CACloneCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(CACloneCLI, self).__init__(
+            'clone', 'CA clone management commands')
+
+        self.add_module(CAClonePrepareCLI())
+
+
+class CAClonePrepareCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(CAClonePrepareCLI, self).__init__(
+            'prepare', 'Prepare CA clone')
+
+    def print_help(self):
+        print('Usage: pki-server ca-clone-prepare [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('ca')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_system_cert(
+                'subsystem', pkcs12_file, pkcs12_password_file, new_file=True)
+            subsystem.export_system_cert(
+                'signing', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'ocsp_signing', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'audit_signing', pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
diff --git a/base/server/python/pki/server/cli/instance.py b/base/server/python/pki/server/cli/instance.py
index 9f0788b..01da2a9 100644
--- a/base/server/python/pki/server/cli/instance.py
+++ b/base/server/python/pki/server/cli/instance.py
@@ -20,10 +20,12 @@
 #
 
 import getopt
+import getpass
 import os
 import sys
 
 import pki.cli
+import pki.nssdb
 import pki.server
 import pki.server.cli.nuxwdog
 
@@ -34,6 +36,7 @@ class InstanceCLI(pki.cli.CLI):
         super(InstanceCLI, self).__init__('instance',
                                           'Instance management commands')
 
+        self.add_module(InstanceCertCLI())
         self.add_module(InstanceFindCLI())
         self.add_module(InstanceShowCLI())
         self.add_module(InstanceStartCLI())
@@ -48,6 +51,101 @@ class InstanceCLI(pki.cli.CLI):
         print '  Active: %s' % instance.is_active()
 
 
+class InstanceCertCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(InstanceCertCLI, self).__init__(
+            'cert', 'Instance certificate management commands')
+
+        self.add_module(InstanceCertExportCLI())
+
+
+class InstanceCertExportCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(InstanceCertExportCLI, self).__init__(
+            'export', 'Export system certificates')
+
+    def print_help(self):  # flake8: noqa
+        print('Usage: pki-server instance-cert-export [OPTIONS] [nicknames...]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           Output file to store the exported certificate and key in PKCS #12 format.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  Input file containing the password for the PKCS #12 file.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, argv):
+
+        try:
+            opts, args = getopt.gnu_getopt(argv, 'i:v', [
+                'instance=',
+                'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        nicknames = args
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+        pkcs12_password_file = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                pkcs12_password_file = a
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: missing output file')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        if not pkcs12_password and not pkcs12_password_file:
+            pkcs12_password = getpass.getpass(prompt='Enter password for PKCS #12 file: ')
+
+        nssdb = instance.open_nssdb()
+        try:
+            nssdb.export_pkcs12(
+                pkcs12_file=pkcs12_file,
+                pkcs12_password=pkcs12_password,
+                pkcs12_password_file=pkcs12_password_file,
+                nicknames=nicknames)
+        finally:
+            nssdb.close()
+
+        self.print_message('Exported certificates')
+
+
 class InstanceFindCLI(pki.cli.CLI):
 
     def __init__(self):
diff --git a/base/server/python/pki/server/cli/kra.py b/base/server/python/pki/server/cli/kra.py
new file mode 100644
index 0000000..7dfa680
--- /dev/null
+++ b/base/server/python/pki/server/cli/kra.py
@@ -0,0 +1,142 @@
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import io
+import os
+import shutil
+import sys
+import tempfile
+
+import pki.cli
+
+
+class KRACLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(KRACLI, self).__init__(
+            'kra', 'KRA management commands')
+
+        self.add_module(KRACloneCLI())
+
+
+class KRACloneCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(KRACloneCLI, self).__init__(
+            'clone', 'KRA clone management commands')
+
+        self.add_module(KRAClonePrepareCLI())
+
+
+class KRAClonePrepareCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(KRAClonePrepareCLI, self).__init__(
+            'prepare', 'Prepare KRA clone')
+
+    def print_help(self):
+        print('Usage: pki-server kra-clone-prepare [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('kra')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_system_cert(
+                'subsystem', pkcs12_file, pkcs12_password_file, new_file=True)
+            subsystem.export_system_cert(
+                'transport', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'storage', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'audit_signing', pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
diff --git a/base/server/python/pki/server/cli/ocsp.py b/base/server/python/pki/server/cli/ocsp.py
new file mode 100644
index 0000000..b913a20
--- /dev/null
+++ b/base/server/python/pki/server/cli/ocsp.py
@@ -0,0 +1,140 @@
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import io
+import os
+import shutil
+import sys
+import tempfile
+
+import pki.cli
+
+
+class OCSPCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(OCSPCLI, self).__init__(
+            'ocsp', 'OCSP management commands')
+
+        self.add_module(OCSPCloneCLI())
+
+
+class OCSPCloneCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(OCSPCloneCLI, self).__init__(
+            'clone', 'OCSP clone management commands')
+
+        self.add_module(OCSPClonePrepareCLI())
+
+
+class OCSPClonePrepareCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(OCSPClonePrepareCLI, self).__init__(
+            'prepare', 'Prepare OCSP clone')
+
+    def print_help(self):
+        print('Usage: pki-server ocsp-clone-prepare [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('ocsp')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_system_cert(
+                'subsystem', pkcs12_file, pkcs12_password_file, new_file=True)
+            subsystem.export_system_cert(
+                'signing', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'audit_signing', pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
diff --git a/base/server/python/pki/server/cli/subsystem.py b/base/server/python/pki/server/cli/subsystem.py
index 3af1926..b6c694f 100644
--- a/base/server/python/pki/server/cli/subsystem.py
+++ b/base/server/python/pki/server/cli/subsystem.py
@@ -300,12 +300,14 @@ class SubsystemCertCLI(pki.cli.CLI):
         self.add_module(SubsystemCertUpdateCLI())
 
     @staticmethod
-    def print_subsystem_cert(cert):
+    def print_subsystem_cert(cert, show_all=False):
         print('  Cert ID: %s' % cert['id'])
         print('  Nickname: %s' % cert['nickname'])
         print('  Token: %s' % cert['token'])
-        print('  Certificate: %s' % cert['data'])
-        print('  Request: %s' % cert['request'])
+
+        if show_all:
+            print('  Certificate: %s' % cert['data'])
+            print('  Request: %s' % cert['request'])
 
 
 class SubsystemCertFindCLI(pki.cli.CLI):
@@ -314,10 +316,11 @@ class SubsystemCertFindCLI(pki.cli.CLI):
         super(SubsystemCertFindCLI, self).__init__(
             'find', 'Find subsystem certificates')
 
-    def usage(self):
+    def print_help(self):
         print('Usage: pki-server subsystem-cert-find [OPTIONS] <subsystem ID>')
         print()
         print('  -i, --instance <instance ID>    Instance ID (default: pki-tomcat).')
+        print('      --show-all                  Show all attributes.')
         print('  -v, --verbose                   Run in verbose mode.')
         print('      --help                      Show help message.')
         print()
@@ -326,26 +329,30 @@ class SubsystemCertFindCLI(pki.cli.CLI):
 
         try:
             opts, args = getopt.gnu_getopt(argv, 'i:v', [
-                'instance=',
+                'instance=', 'show-all',
                 'verbose', 'help'])
 
         except getopt.GetoptError as e:
             print('ERROR: ' + str(e))
-            self.usage()
+            self.print_help()
             sys.exit(1)
 
         if len(args) != 1:
             print('ERROR: missing subsystem ID')
-            self.usage()
+            self.print_help()
             sys.exit(1)
 
         subsystem_name = args[0]
         instance_name = 'pki-tomcat'
+        show_all = False
 
         for o, a in opts:
             if o in ('-i', '--instance'):
                 instance_name = a
 
+            elif o == '--show-all':
+                show_all = True
+
             elif o in ('-v', '--verbose'):
                 self.set_verbose(True)
 
@@ -355,14 +362,14 @@ class SubsystemCertFindCLI(pki.cli.CLI):
 
             else:
                 print('ERROR: unknown option ' + o)
-                self.usage()
+                self.print_help()
                 sys.exit(1)
 
         instance = pki.server.PKIInstance(instance_name)
         instance.load()
 
         subsystem = instance.get_subsystem(subsystem_name)
-        results = subsystem.find_subsystem_certs()
+        results = subsystem.find_system_certs()
 
         self.print_message('%s entries matched' % len(results))
 
@@ -373,7 +380,7 @@ class SubsystemCertFindCLI(pki.cli.CLI):
             else:
                 print()
 
-            SubsystemCertCLI.print_subsystem_cert(cert)
+            SubsystemCertCLI.print_subsystem_cert(cert, show_all)
 
 
 class SubsystemCertShowCLI(pki.cli.CLI):
@@ -447,8 +454,8 @@ class SubsystemCertExportCLI(pki.cli.CLI):
         super(SubsystemCertExportCLI, self).__init__(
             'export', 'Export subsystem certificate')
 
-    def usage(self):
-        print('Usage: pki-server subsystem-cert-export [OPTIONS] <subsystem ID> <cert ID>')
+    def print_help(self):  # flake8: noqa
+        print('Usage: pki-server subsystem-cert-export [OPTIONS] <subsystem ID> [cert ID]')
         print()
         print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
         print('      --cert-file <path>             Output file to store the exported certificate in PEM format.')
@@ -470,21 +477,16 @@ class SubsystemCertExportCLI(pki.cli.CLI):
 
         except getopt.GetoptError as e:
             print('ERROR: ' + str(e))
-            self.usage()
+            self.print_help()
             sys.exit(1)
 
         if len(args) < 1:
             print('ERROR: missing subsystem ID')
-            self.usage()
-            sys.exit(1)
-
-        if len(args) < 2:
-            print('ERROR: missing cert ID')
-            self.usage()
+            self.print_help()
             sys.exit(1)
 
         subsystem_name = args[0]
-        cert_id = args[1]
+
         instance_name = 'pki-tomcat'
         cert_file = None
         csr_file = None
@@ -520,19 +522,28 @@ class SubsystemCertExportCLI(pki.cli.CLI):
 
             else:
                 print('ERROR: unknown option ' + o)
-                self.usage()
+                self.print_help()
                 sys.exit(1)
 
-        if not cert_file and not csr_file and not pkcs12_file:
+        if not pkcs12_file:
             print('ERROR: missing output file')
-            self.usage()
+            self.print_help()
             sys.exit(1)
 
         instance = pki.server.PKIInstance(instance_name)
         instance.load()
 
         subsystem = instance.get_subsystem(subsystem_name)
-        subsystem_cert = subsystem.get_subsystem_cert(cert_id)
+        subsystem_cert = None
+
+        if len(args) >= 2:
+            cert_id = args[1]
+            subsystem_cert = subsystem.get_subsystem_cert(cert_id)
+
+        if (cert_file or csr_file) and not subsystem_cert:
+            print('ERROR: missing cert ID')
+            self.print_help()
+            sys.exit(1)
 
         if cert_file:
 
@@ -551,17 +562,28 @@ class SubsystemCertExportCLI(pki.cli.CLI):
             if not pkcs12_password and not pkcs12_password_file:
                 pkcs12_password = getpass.getpass(prompt='Enter password for PKCS #12 file: ')
 
+            nicknames = []
+
+            if subsystem_cert:
+                nicknames.append(subsystem_cert['nickname'])
+
+            else:
+                subsystem_certs = subsystem.find_system_certs()
+                for subsystem_cert in subsystem_certs:
+                    nicknames.append(subsystem_cert['nickname'])
+
             nssdb = instance.open_nssdb()
             try:
                 nssdb.export_pkcs12(
                     pkcs12_file=pkcs12_file,
-                    nickname=subsystem_cert['nickname'],
                     pkcs12_password=pkcs12_password,
-                    pkcs12_password_file=pkcs12_password_file)
+                    pkcs12_password_file=pkcs12_password_file,
+                    nicknames=nicknames)
+
             finally:
                 nssdb.close()
 
-        self.print_message('Exported %s certificate' % cert_id)
+        self.print_message('Export complete')
 
 
 class SubsystemCertUpdateCLI(pki.cli.CLI):
diff --git a/base/server/python/pki/server/cli/tks.py b/base/server/python/pki/server/cli/tks.py
new file mode 100644
index 0000000..bf96d96
--- /dev/null
+++ b/base/server/python/pki/server/cli/tks.py
@@ -0,0 +1,140 @@
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import io
+import os
+import shutil
+import sys
+import tempfile
+
+import pki.cli
+
+
+class TKSCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TKSCLI, self).__init__(
+            'tks', 'TKS management commands')
+
+        self.add_module(TKSCloneCLI())
+
+
+class TKSCloneCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TKSCloneCLI, self).__init__(
+            'clone', 'TKS clone management commands')
+
+        self.add_module(TKSClonePrepareCLI())
+
+
+class TKSClonePrepareCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TKSClonePrepareCLI, self).__init__(
+            'prepare', 'Prepare TKS clone')
+
+    def print_help(self):
+        print('Usage: pki-server tks-clone-prepare [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('tks')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_system_cert(
+                'subsystem', pkcs12_file, pkcs12_password_file, new_file=True)
+            subsystem.export_system_cert(
+                'signing', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'audit_signing', pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
diff --git a/base/server/python/pki/server/cli/tps.py b/base/server/python/pki/server/cli/tps.py
new file mode 100644
index 0000000..7284eaa
--- /dev/null
+++ b/base/server/python/pki/server/cli/tps.py
@@ -0,0 +1,140 @@
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import getopt
+import io
+import os
+import shutil
+import sys
+import tempfile
+
+import pki.cli
+
+
+class TPSCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TPSCLI, self).__init__(
+            'tps', 'TPS management commands')
+
+        self.add_module(TPSCloneCLI())
+
+
+class TPSCloneCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TPSCloneCLI, self).__init__(
+            'clone', 'TPS clone management commands')
+
+        self.add_module(TPSClonePrepareCLI())
+
+
+class TPSClonePrepareCLI(pki.cli.CLI):
+
+    def __init__(self):
+        super(TPSClonePrepareCLI, self).__init__(
+            'prepare', 'Prepare TPS clone')
+
+    def print_help(self):
+        print('Usage: pki-server tps-clone-prepare [OPTIONS]')
+        print()
+        print('  -i, --instance <instance ID>       Instance ID (default: pki-tomcat).')
+        print('      --pkcs12-file <path>           PKCS #12 file to store certificates and keys.')
+        print('      --pkcs12-password <password>   Password for the PKCS #12 file.')
+        print('      --pkcs12-password-file <path>  File containing the PKCS #12 password.')
+        print('  -v, --verbose                      Run in verbose mode.')
+        print('      --help                         Show help message.')
+        print()
+
+    def execute(self, args):
+
+        try:
+            opts, _ = getopt.gnu_getopt(args, 'i:v', [
+                'instance=', 'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
+                'verbose', 'help'])
+
+        except getopt.GetoptError as e:
+            print('ERROR: ' + str(e))
+            self.print_help()
+            sys.exit(1)
+
+        instance_name = 'pki-tomcat'
+        pkcs12_file = None
+        pkcs12_password = None
+
+        for o, a in opts:
+            if o in ('-i', '--instance'):
+                instance_name = a
+
+            elif o == '--pkcs12-file':
+                pkcs12_file = a
+
+            elif o == '--pkcs12-password':
+                pkcs12_password = a
+
+            elif o == '--pkcs12-password-file':
+                with io.open(a, 'rb') as f:
+                    pkcs12_password = f.read()
+
+            elif o in ('-v', '--verbose'):
+                self.set_verbose(True)
+
+            elif o == '--help':
+                self.print_help()
+                sys.exit()
+
+            else:
+                print('ERROR: unknown option ' + o)
+                self.print_help()
+                sys.exit(1)
+
+        if not pkcs12_file:
+            print('ERROR: Missing PKCS #12 file')
+            self.print_help()
+            sys.exit(1)
+
+        if not pkcs12_password:
+            print('ERROR: Missing PKCS #12 password')
+            self.print_help()
+            sys.exit(1)
+
+        instance = pki.server.PKIInstance(instance_name)
+        instance.load()
+
+        subsystem = instance.get_subsystem('tps')
+
+        tmpdir = tempfile.mkdtemp()
+
+        try:
+            pkcs12_password_file = os.path.join(tmpdir, 'pkcs12_password.txt')
+            with open(pkcs12_password_file, 'w') as f:
+                f.write(pkcs12_password)
+
+            subsystem.export_system_cert(
+                'subsystem', pkcs12_file, pkcs12_password_file, new_file=True)
+            subsystem.export_system_cert(
+                'signing', pkcs12_file, pkcs12_password_file)
+            subsystem.export_system_cert(
+                'audit_signing', pkcs12_file, pkcs12_password_file)
+
+        finally:
+            shutil.rmtree(tmpdir)
diff --git a/base/server/python/pki/server/deployment/scriptlets/configuration.py b/base/server/python/pki/server/deployment/scriptlets/configuration.py
index e7b257f..54f065f 100644
--- a/base/server/python/pki/server/deployment/scriptlets/configuration.py
+++ b/base/server/python/pki/server/deployment/scriptlets/configuration.py
@@ -161,7 +161,7 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
                 external_ca_cert_chain_nickname = deployer.mdict['pki_external_ca_cert_chain_nickname']
                 external_ca_cert_chain_file = deployer.mdict['pki_external_ca_cert_chain_path']
                 if external_ca_cert_chain_file:
-                    cert_chain = nssdb.import_cert_chain(
+                    cert_chain, _nicks = nssdb.import_cert_chain(
                         nickname=external_ca_cert_chain_nickname,
                         cert_chain_file=external_ca_cert_chain_file,
                         trust_attributes='CT,C,C')
diff --git a/base/server/python/pki/server/deployment/scriptlets/security_databases.py b/base/server/python/pki/server/deployment/scriptlets/security_databases.py
index c3d4d9e..f059b09 100644
--- a/base/server/python/pki/server/deployment/scriptlets/security_databases.py
+++ b/base/server/python/pki/server/deployment/scriptlets/security_databases.py
@@ -19,6 +19,10 @@
 # All rights reserved.
 #
 
+from __future__ import absolute_import
+
+import pki.nssdb
+
 # PKI Deployment Imports
 from .. import pkiconfig as config
 from .. import pkimessages as log
@@ -35,8 +39,10 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
             config.pki_log.info(log.SKIP_SECURITY_DATABASES_SPAWN_1, __name__,
                                 extra=config.PKI_INDENTATION_LEVEL_1)
             return self.rv
+
         config.pki_log.info(log.SECURITY_DATABASES_SPAWN_1, __name__,
                             extra=config.PKI_INDENTATION_LEVEL_1)
+
         if config.str2bool(deployer.mdict['pki_hsm_enable']):
             deployer.password.create_hsm_password_conf(
                 deployer.mdict['pki_shared_password_conf'],
@@ -46,6 +52,7 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
             deployer.password.create_password_conf(
                 deployer.mdict['pki_shared_password_conf'],
                 deployer.mdict['pki_pin'])
+
         # Since 'certutil' does NOT strip the 'token=' portion of
         # the 'token=password' entries, create a temporary server 'pfile'
         # which ONLY contains the 'password' for the purposes of
@@ -54,12 +61,14 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
             deployer.mdict['pki_shared_pfile'],
             deployer.mdict['pki_pin'], pin_sans_token=True)
         deployer.file.modify(deployer.mdict['pki_shared_password_conf'])
+
         deployer.certutil.create_security_databases(
             deployer.mdict['pki_database_path'],
             deployer.mdict['pki_cert_database'],
             deployer.mdict['pki_key_database'],
             deployer.mdict['pki_secmod_database'],
             password_file=deployer.mdict['pki_shared_pfile'])
+
         if config.str2bool(deployer.mdict['pki_hsm_enable']):
             deployer.modutil.register_security_module(
                 deployer.mdict['pki_database_path'],
@@ -75,6 +84,25 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
             deployer.mdict['pki_secmod_database'],
             perms=config.PKI_DEPLOYMENT_DEFAULT_SECURITY_DATABASE_PERMISSIONS)
 
+        # import CA certificates from PKCS #12 file for cloning
+        pki_clone_pkcs12_path = deployer.mdict['pki_clone_pkcs12_path']
+
+        if pki_clone_pkcs12_path:
+
+            pki_clone_pkcs12_password = deployer.mdict[
+                'pki_clone_pkcs12_password']
+            if not pki_clone_pkcs12_password:
+                raise Exception('Missing pki_clone_pkcs12_password property.')
+
+            nssdb = pki.nssdb.NSSDatabase(
+                directory=deployer.mdict['pki_database_path'],
+                password_file=deployer.mdict['pki_shared_pfile'])
+
+            nssdb.import_pkcs12(
+                pkcs12_file=pki_clone_pkcs12_path,
+                pkcs12_password=pki_clone_pkcs12_password,
+                no_user_certs=True)
+
         if len(deployer.instance.tomcat_instance_subsystems()) < 2:
             # only create a self signed cert for a new instance
             #
diff --git a/base/server/sbin/pki-server b/base/server/sbin/pki-server
index cdfd98e..cf56d1b 100644
--- a/base/server/sbin/pki-server
+++ b/base/server/sbin/pki-server
@@ -24,6 +24,10 @@ import sys
 
 import pki.cli
 import pki.server.cli.ca
+import pki.server.cli.kra
+import pki.server.cli.ocsp
+import pki.server.cli.tks
+import pki.server.cli.tps
 import pki.server.cli.instance
 import pki.server.cli.subsystem
 import pki.server.cli.migrate
@@ -37,6 +41,11 @@ class PKIServerCLI(pki.cli.CLI):
         super(PKIServerCLI, self).__init__('pki-server', 'PKI server command-line interface')
 
         self.add_module(pki.server.cli.ca.CACLI())
+        self.add_module(pki.server.cli.kra.KRACLI())
+        self.add_module(pki.server.cli.ocsp.OCSPCLI())
+        self.add_module(pki.server.cli.tks.TKSCLI())
+        self.add_module(pki.server.cli.tps.TPSCLI())
+
         self.add_module(pki.server.cli.instance.InstanceCLI())
         self.add_module(pki.server.cli.subsystem.SubsystemCLI())
         self.add_module(pki.server.cli.migrate.MigrateCLI())
diff --git a/base/util/src/netscape/security/pkcs/PKCS12.java b/base/util/src/netscape/security/pkcs/PKCS12.java
new file mode 100644
index 0000000..6c7880a
--- /dev/null
+++ b/base/util/src/netscape/security/pkcs/PKCS12.java
@@ -0,0 +1,205 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package netscape.security.pkcs;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
+
+public class PKCS12 {
+
+    // PKI OID: 2.16.840.1.113730.5
+    public final static OBJECT_IDENTIFIER PKI_OID = new OBJECT_IDENTIFIER("2.16.840.1.113730.5");
+
+    // PKCS #12 OID: 2.16.840.1.113730.5.1
+    public final static OBJECT_IDENTIFIER PKCS12_OID = PKI_OID.subBranch(1);
+
+    // PKCS #12 attributes OID: 2.16.840.1.113730.5.1.1
+    public final static OBJECT_IDENTIFIER PKCS12_ATTRIBUTES_OID = PKCS12_OID.subBranch(1);
+
+    // Certificate trust flags OID: 2.16.840.1.113730.5.1.1.1
+    public final static OBJECT_IDENTIFIER CERT_TRUST_FLAGS_OID = PKCS12_ATTRIBUTES_OID.subBranch(1);
+
+    // based on certdb.h in NSS
+    public final static int TERMINAL_RECORD   = 1 << 0;
+    public final static int TRUSTED           = 1 << 1;
+    public final static int SEND_WARN         = 1 << 2;
+    public final static int VALID_CA          = 1 << 3;
+    public final static int TRUSTED_CA        = 1 << 4;
+    public final static int NS_TRUSTED_CA     = 1 << 5;
+    public final static int USER              = 1 << 6;
+    public final static int TRUSTED_CLIENT_CA = 1 << 7;
+    public final static int INVISIBLE_CA      = 1 << 8;
+    public final static int GOVT_APPROVED_CA  = 1 << 9;
+
+    public static boolean isFlagEnabled(int flag, int flags) {
+        return (flag & flags) > 0;
+    }
+
+    // based on printflags() in secutil.c in NSS
+    public static String encodeFlags(int flags) {
+
+        StringBuffer sb = new StringBuffer();
+
+        if (isFlagEnabled(VALID_CA, flags) && !isFlagEnabled(TRUSTED_CA, flags) && !isFlagEnabled(TRUSTED_CLIENT_CA, flags))
+            sb.append("c");
+
+        if (isFlagEnabled(TERMINAL_RECORD, flags) && !isFlagEnabled(TRUSTED, flags))
+            sb.append("p");
+
+        if (isFlagEnabled(TRUSTED_CA, flags))
+            sb.append("C");
+
+        if (isFlagEnabled(TRUSTED_CLIENT_CA, flags))
+            sb.append("T");
+
+        if (isFlagEnabled(TRUSTED, flags))
+            sb.append("P");
+
+        if (isFlagEnabled(USER, flags))
+            sb.append("u");
+
+        if (isFlagEnabled(SEND_WARN, flags))
+            sb.append("w");
+
+        if (isFlagEnabled(INVISIBLE_CA, flags))
+            sb.append("I");
+
+        if (isFlagEnabled(GOVT_APPROVED_CA, flags))
+            sb.append("G");
+
+        return sb.toString();
+    }
+
+    // based on CERT_DecodeTrustString() in certdb.c in NSS
+    public static int decodeFlags(String flags) throws Exception {
+
+        int value = 0;
+
+        for (char c : flags.toCharArray()) {
+            switch (c) {
+            case 'p':
+                value = value | TERMINAL_RECORD;
+                break;
+
+            case 'P':
+                value = value | TRUSTED | TERMINAL_RECORD;
+                break;
+
+            case 'w':
+                value = value | SEND_WARN;
+                break;
+
+            case 'c':
+                value = value | VALID_CA;
+                break;
+
+            case 'T':
+                value = value | TRUSTED_CLIENT_CA | VALID_CA;
+                break;
+
+            case 'C' :
+                value = value | TRUSTED_CA | VALID_CA;
+                break;
+
+            case 'u':
+                value = value | USER;
+                break;
+
+            case 'i':
+                value = value | INVISIBLE_CA;
+                break;
+            case 'g':
+                value = value | GOVT_APPROVED_CA;
+                break;
+
+            default:
+                throw new Exception("Invalid trust flag: " + c);
+            }
+        }
+
+        return value;
+    }
+
+    Map<BigInteger, PKCS12KeyInfo> keyInfosByID = new LinkedHashMap<BigInteger, PKCS12KeyInfo>();
+
+    Map<BigInteger, PKCS12CertInfo> certInfosByID = new LinkedHashMap<BigInteger, PKCS12CertInfo>();
+
+    public PKCS12() {
+    }
+
+    public Collection<PKCS12KeyInfo> getKeyInfos() {
+        return keyInfosByID.values();
+    }
+
+    public void addKeyInfo(PKCS12KeyInfo keyInfo) {
+        keyInfosByID.put(keyInfo.id, keyInfo);
+    }
+
+    public PKCS12KeyInfo getKeyInfoByID(BigInteger id) {
+        return keyInfosByID.get(id);
+    }
+
+    public PKCS12KeyInfo removeKeyInfoByID(BigInteger id) {
+        return keyInfosByID.remove(id);
+    }
+
+    public Collection<PKCS12CertInfo> getCertInfos() {
+        return certInfosByID.values();
+    }
+
+    public void addCertInfo(PKCS12CertInfo certInfo, boolean replace) {
+        BigInteger id = certInfo.getID();
+
+        if (!replace && certInfosByID.containsKey(id))
+            return;
+
+        certInfosByID.put(id, certInfo);
+    }
+
+    public PKCS12CertInfo getCertInfoByID(BigInteger id) {
+        return certInfosByID.get(id);
+    }
+
+    public Collection<PKCS12CertInfo> getCertInfosByNickname(String nickname) {
+
+        Collection<PKCS12CertInfo> result = new ArrayList<PKCS12CertInfo>();
+
+        for (PKCS12CertInfo certInfo : certInfosByID.values()) {
+            if (!nickname.equals(certInfo.getNickname())) continue;
+            result.add(certInfo);
+        }
+
+        return result;
+    }
+
+    public void removeCertInfoByNickname(String nickname) {
+
+        Collection<PKCS12CertInfo> result = getCertInfosByNickname(nickname);
+
+        for (PKCS12CertInfo certInfo : result) {
+            // remove cert and key
+            certInfosByID.remove(certInfo.getID());
+            keyInfosByID.remove(certInfo.getID());
+        }
+    }
+}
diff --git a/base/util/src/netscape/security/pkcs/PKCS12CertInfo.java b/base/util/src/netscape/security/pkcs/PKCS12CertInfo.java
new file mode 100644
index 0000000..ec7b0e3
--- /dev/null
+++ b/base/util/src/netscape/security/pkcs/PKCS12CertInfo.java
@@ -0,0 +1,65 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package netscape.security.pkcs;
+
+import java.math.BigInteger;
+
+import netscape.security.x509.X509CertImpl;
+
+public class PKCS12CertInfo {
+
+    BigInteger id;
+    X509CertImpl cert;
+    String nickname;
+    String trustFlags;
+
+    public PKCS12CertInfo() {
+    }
+
+    public BigInteger getID() {
+        return id;
+    }
+
+    public void setID(BigInteger id) {
+        this.id = id;
+    }
+
+    public X509CertImpl getCert() {
+        return cert;
+    }
+
+    public void setCert(X509CertImpl cert) {
+        this.cert = cert;
+    }
+
+    public String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getTrustFlags() {
+        return trustFlags;
+    }
+
+    public void setTrustFlags(String trustFlags) {
+        this.trustFlags = trustFlags;
+    }
+}
diff --git a/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
new file mode 100644
index 0000000..c7e84f0
--- /dev/null
+++ b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java
@@ -0,0 +1,56 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package netscape.security.pkcs;
+
+import java.math.BigInteger;
+
+import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
+
+public class PKCS12KeyInfo {
+
+    BigInteger id;
+    PrivateKeyInfo privateKeyInfo;
+    String subjectDN;
+
+    public PKCS12KeyInfo() {
+    }
+
+    public BigInteger getID() {
+        return id;
+    }
+
+    public void setID(BigInteger id) {
+        this.id = id;
+    }
+
+    public PrivateKeyInfo getPrivateKeyInfo() {
+        return privateKeyInfo;
+    }
+
+    public void setPrivateKeyInfo(PrivateKeyInfo privateKeyInfo) {
+        this.privateKeyInfo = privateKeyInfo;
+    }
+
+    public String getSubjectDN() {
+        return subjectDN;
+    }
+
+    public void setSubjectDN(String subjectDN) {
+        this.subjectDN = subjectDN;
+    }
+}
diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java
new file mode 100644
index 0000000..7c9ab2f
--- /dev/null
+++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java
@@ -0,0 +1,642 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2016 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package netscape.security.pkcs;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.asn1.ANY;
+import org.mozilla.jss.asn1.ASN1Util;
+import org.mozilla.jss.asn1.ASN1Value;
+import org.mozilla.jss.asn1.BMPString;
+import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
+import org.mozilla.jss.asn1.OCTET_STRING;
+import org.mozilla.jss.asn1.SEQUENCE;
+import org.mozilla.jss.asn1.SET;
+import org.mozilla.jss.crypto.Cipher;
+import org.mozilla.jss.crypto.CryptoStore;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.crypto.EncryptionAlgorithm;
+import org.mozilla.jss.crypto.IVParameterSpec;
+import org.mozilla.jss.crypto.InternalCertificate;
+import org.mozilla.jss.crypto.KeyGenAlgorithm;
+import org.mozilla.jss.crypto.KeyGenerator;
+import org.mozilla.jss.crypto.KeyWrapAlgorithm;
+import org.mozilla.jss.crypto.KeyWrapper;
+import org.mozilla.jss.crypto.NoSuchItemOnTokenException;
+import org.mozilla.jss.crypto.ObjectNotFoundException;
+import org.mozilla.jss.crypto.PBEAlgorithm;
+import org.mozilla.jss.crypto.PrivateKey;
+import org.mozilla.jss.crypto.SymmetricKey;
+import org.mozilla.jss.crypto.X509Certificate;
+import org.mozilla.jss.pkcs12.AuthenticatedSafes;
+import org.mozilla.jss.pkcs12.CertBag;
+import org.mozilla.jss.pkcs12.PFX;
+import org.mozilla.jss.pkcs12.PasswordConverter;
+import org.mozilla.jss.pkcs12.SafeBag;
+import org.mozilla.jss.pkix.primitive.Attribute;
+import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
+import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
+import org.mozilla.jss.util.Password;
+
+import netscape.ldap.LDAPDN;
+import netscape.security.x509.X509CertImpl;
+
+public class PKCS12Util {
+
+    private static Logger logger = Logger.getLogger(PKCS12Util.class.getName());
+
+    boolean trustFlagsEnabled = true;
+
+    public boolean isTrustFlagsEnabled() {
+        return trustFlagsEnabled;
+    }
+
+    public void setTrustFlagsEnabled(boolean trustFlagsEnabled) {
+        this.trustFlagsEnabled = trustFlagsEnabled;
+    }
+
+    public String getTrustFlags(X509Certificate cert) {
+
+        InternalCertificate icert = (InternalCertificate) cert;
+
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(PKCS12.encodeFlags(icert.getSSLTrust()));
+        sb.append(",");
+        sb.append(PKCS12.encodeFlags(icert.getEmailTrust()));
+        sb.append(",");
+        sb.append(PKCS12.encodeFlags(icert.getObjectSigningTrust()));
+
+        return sb.toString();
+    }
+
+    public void setTrustFlags(X509Certificate cert, String trustFlags) throws Exception {
+
+        InternalCertificate icert = (InternalCertificate) cert;
+
+        String[] flags = trustFlags.split(",");
+        if (flags.length < 3) throw new Exception("Invalid trust flags: " + trustFlags);
+
+        icert.setSSLTrust(PKCS12.decodeFlags(flags[0]));
+        icert.setEmailTrust(PKCS12.decodeFlags(flags[1]));
+        icert.setObjectSigningTrust(PKCS12.decodeFlags(flags[2]));
+    }
+
+    byte[] getEncodedKey(PrivateKey privateKey) throws Exception {
+
+        CryptoManager cm = CryptoManager.getInstance();
+        CryptoToken token = cm.getInternalKeyStorageToken();
+
+        KeyGenerator kg = token.getKeyGenerator(KeyGenAlgorithm.DES3);
+        SymmetricKey sk = kg.generate();
+
+        KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+        byte[] iv = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
+        IVParameterSpec param = new IVParameterSpec(iv);
+        wrapper.initWrap(sk, param);
+        byte[] enckey = wrapper.wrap(privateKey);
+
+        Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
+        c.initDecrypt(sk, param);
+        return c.doFinal(enckey);
+    }
+
+    public void addKeyBag(PKCS12KeyInfo keyInfo, Password password,
+            SEQUENCE encSafeContents) throws Exception {
+
+        logger.fine("Creating key bag for " + keyInfo.subjectDN);
+
+        PasswordConverter passConverter = new PasswordConverter();
+        byte salt[] = { 0x01, 0x01, 0x01, 0x01 };
+
+        EncryptedPrivateKeyInfo encPrivateKeyInfo = EncryptedPrivateKeyInfo.createPBE(
+                PBEAlgorithm.PBE_SHA1_DES3_CBC,
+                password, salt, 1, passConverter, keyInfo.privateKeyInfo);
+
+        SET keyAttrs = createKeyBagAttrs(keyInfo);
+
+        SafeBag safeBag = new SafeBag(SafeBag.PKCS8_SHROUDED_KEY_BAG, encPrivateKeyInfo, keyAttrs);
+        encSafeContents.addElement(safeBag);
+    }
+
+    public void addCertBag(PKCS12CertInfo certInfo,
+            SEQUENCE safeContents) throws Exception {
+
+        logger.fine("Creating cert bag for " + certInfo.nickname);
+
+        ASN1Value cert = new OCTET_STRING(certInfo.cert.getEncoded());
+        CertBag certBag = new CertBag(CertBag.X509_CERT_TYPE, cert);
+
+        SET certAttrs = createCertBagAttrs(certInfo);
+
+        SafeBag safeBag = new SafeBag(SafeBag.CERT_BAG, certBag, certAttrs);
+        safeContents.addElement(safeBag);
+    }
+
+    BigInteger createLocalID(X509Certificate cert) throws Exception {
+
+        // SHA1 hash of the X509Cert DER encoding
+        byte[] certDer = cert.getEncoded();
+
+        MessageDigest md = MessageDigest.getInstance("SHA");
+
+        md.update(certDer);
+        return new BigInteger(1, md.digest());
+    }
+
+    SET createKeyBagAttrs(PKCS12KeyInfo keyInfo) throws Exception {
+
+        SET attrs = new SET();
+
+        SEQUENCE subjectAttr = new SEQUENCE();
+        subjectAttr.addElement(SafeBag.FRIENDLY_NAME);
+
+        SET subjectSet = new SET();
+        subjectSet.addElement(new BMPString(keyInfo.subjectDN));
+        subjectAttr.addElement(subjectSet);
+
+        attrs.addElement(subjectAttr);
+
+        SEQUENCE localKeyAttr = new SEQUENCE();
+        localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID);
+
+        SET localKeySet = new SET();
+        localKeySet.addElement(new OCTET_STRING(keyInfo.id.toByteArray()));
+        localKeyAttr.addElement(localKeySet);
+
+        attrs.addElement(localKeyAttr);
+
+        return attrs;
+    }
+
+    SET createCertBagAttrs(PKCS12CertInfo certInfo) throws Exception {
+
+        SET attrs = new SET();
+
+        SEQUENCE nicknameAttr = new SEQUENCE();
+        nicknameAttr.addElement(SafeBag.FRIENDLY_NAME);
+
+        SET nicknameSet = new SET();
+        nicknameSet.addElement(new BMPString(certInfo.nickname));
+        nicknameAttr.addElement(nicknameSet);
+
+        attrs.addElement(nicknameAttr);
+
+        if (certInfo.getID() != null) {
+            SEQUENCE localKeyAttr = new SEQUENCE();
+            localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID);
+
+            SET localKeySet = new SET();
+            localKeySet.addElement(new OCTET_STRING(certInfo.id.toByteArray()));
+            localKeyAttr.addElement(localKeySet);
+
+            attrs.addElement(localKeyAttr);
+        }
+
+        if (certInfo.trustFlags != null && trustFlagsEnabled) {
+            SEQUENCE trustFlagsAttr = new SEQUENCE();
+            trustFlagsAttr.addElement(PKCS12.CERT_TRUST_FLAGS_OID);
+
+            SET trustFlagsSet = new SET();
+            trustFlagsSet.addElement(new BMPString(certInfo.trustFlags));
+            trustFlagsAttr.addElement(trustFlagsSet);
+
+            attrs.addElement(trustFlagsAttr);
+        }
+
+        return attrs;
+    }
+
+    public void loadFromNSS(PKCS12 pkcs12) throws Exception {
+
+        logger.info("Loading all certificate and keys from NSS database");
+
+        CryptoManager cm = CryptoManager.getInstance();
+        CryptoToken token = cm.getInternalKeyStorageToken();
+        CryptoStore store = token.getCryptoStore();
+
+        for (X509Certificate cert : store.getCertificates()) {
+            loadCertChainFromNSS(pkcs12, cert);
+        }
+    }
+
+    public void loadCertFromNSS(PKCS12 pkcs12, String nickname) throws Exception {
+
+        CryptoManager cm = CryptoManager.getInstance();
+
+        X509Certificate[] certs = cm.findCertsByNickname(nickname);
+        for (X509Certificate cert : certs) {
+            loadCertChainFromNSS(pkcs12, cert);
+        }
+    }
+
+    public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id, boolean replace) throws Exception {
+
+        String nickname = cert.getNickname();
+        logger.info("Loading certificate \"" + nickname + "\" from NSS database");
+
+        PKCS12CertInfo certInfo = new PKCS12CertInfo();
+        certInfo.id = id;
+        certInfo.nickname = nickname;
+        certInfo.cert = new X509CertImpl(cert.getEncoded());
+        certInfo.trustFlags = getTrustFlags(cert);
+
+        pkcs12.addCertInfo(certInfo, replace);
+    }
+
+    public void loadCertKeyFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id) throws Exception {
+
+        String nickname = cert.getNickname();
+        logger.info("Loading private key for certificate \"" + nickname + "\" from NSS database");
+
+        CryptoManager cm = CryptoManager.getInstance();
+
+        try {
+            PrivateKey privateKey = cm.findPrivKeyByCert(cert);
+            logger.fine("Certificate \"" + nickname + "\" has private key");
+
+            PKCS12KeyInfo keyInfo = new PKCS12KeyInfo();
+            keyInfo.id = id;
+            keyInfo.subjectDN = cert.getSubjectDN().toString();
+
+            byte[] privateData = getEncodedKey(privateKey);
+            keyInfo.privateKeyInfo = (PrivateKeyInfo)
+                    ASN1Util.decode(PrivateKeyInfo.getTemplate(), privateData);
+
+            pkcs12.addKeyInfo(keyInfo);
+
+        } catch (ObjectNotFoundException e) {
+            logger.fine("Certificate \"" + nickname + "\" has no private key");
+        }
+    }
+
+    public void loadCertChainFromNSS(PKCS12 pkcs12, X509Certificate cert) throws Exception {
+
+        CryptoManager cm = CryptoManager.getInstance();
+
+        BigInteger id = createLocalID(cert);
+
+        // load cert key if exists
+        loadCertKeyFromNSS(pkcs12, cert, id);
+
+        // load cert
+        loadCertFromNSS(pkcs12, cert, id, true);
+
+        // load parent certs without key
+        X509Certificate[] certChain = cm.buildCertificateChain(cert);
+        for (int i = 1; i < certChain.length; i++) {
+            X509Certificate c = certChain[i];
+            BigInteger cid = createLocalID(c);
+            loadCertFromNSS(pkcs12, c, cid, false);
+        }
+    }
+
+    public void storeIntoFile(PKCS12 pkcs12, String filename, Password password) throws Exception {
+
+        logger.info("Storing data into PKCS #12 file");
+
+        SEQUENCE safeContents = new SEQUENCE();
+
+        for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
+            addCertBag(certInfo, safeContents);
+        }
+
+        SEQUENCE encSafeContents = new SEQUENCE();
+
+        for (PKCS12KeyInfo keyInfo : pkcs12.getKeyInfos()) {
+            addKeyBag(keyInfo, password, encSafeContents);
+        }
+
+        AuthenticatedSafes authSafes = new AuthenticatedSafes();
+        authSafes.addSafeContents(safeContents);
+        authSafes.addSafeContents(encSafeContents);
+
+        PFX pfx = new PFX(authSafes);
+        pfx.computeMacData(password, null, 5);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        pfx.encode(bos);
+        byte[] data = bos.toByteArray();
+
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            fos.write(data);
+        }
+    }
+
+    public PKCS12KeyInfo getKeyInfo(SafeBag bag, Password password) throws Exception {
+
+        PKCS12KeyInfo keyInfo = new PKCS12KeyInfo();
+
+        // get private key info
+        EncryptedPrivateKeyInfo encPrivateKeyInfo = (EncryptedPrivateKeyInfo) bag.getInterpretedBagContent();
+        keyInfo.privateKeyInfo = encPrivateKeyInfo.decrypt(password, new PasswordConverter());
+
+        // get key attributes
+        SET bagAttrs = bag.getBagAttributes();
+
+        for (int i = 0; i < bagAttrs.size(); i++) {
+
+            Attribute attr = (Attribute) bagAttrs.elementAt(i);
+            OBJECT_IDENTIFIER oid = attr.getType();
+
+            if (oid.equals(SafeBag.FRIENDLY_NAME)) {
+
+                SET values = attr.getValues();
+                ANY value = (ANY) values.elementAt(0);
+
+                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
+                BMPString subjectDN = (BMPString) new BMPString.Template().decode(bis);
+
+                keyInfo.subjectDN = subjectDN.toString();
+                logger.fine("Subject DN: " + keyInfo.subjectDN);
+
+            } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {
+
+                SET values = attr.getValues();
+                ANY value = (ANY) values.elementAt(0);
+
+                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
+                OCTET_STRING keyID = (OCTET_STRING) new OCTET_STRING.Template().decode(bis);
+
+                keyInfo.id = new BigInteger(1, keyID.toByteArray());
+                logger.fine("ID: " + keyInfo.id.toString(16));
+            }
+        }
+
+        logger.fine("Found private key " + keyInfo.subjectDN);
+
+        return keyInfo;
+    }
+
+    public PKCS12CertInfo getCertInfo(SafeBag bag) throws Exception {
+
+        PKCS12CertInfo certInfo = new PKCS12CertInfo();
+
+        CertBag certBag = (CertBag) bag.getInterpretedBagContent();
+
+        OCTET_STRING certStr = (OCTET_STRING) certBag.getInterpretedCert();
+        byte[] x509cert = certStr.toByteArray();
+
+        certInfo.cert = new X509CertImpl(x509cert);
+        logger.fine("Found certificate " + certInfo.cert.getSubjectDN());
+
+        SET bagAttrs = bag.getBagAttributes();
+        if (bagAttrs == null) return certInfo;
+
+        for (int i = 0; i < bagAttrs.size(); i++) {
+
+            Attribute attr = (Attribute) bagAttrs.elementAt(i);
+            OBJECT_IDENTIFIER oid = attr.getType();
+
+            if (oid.equals(SafeBag.FRIENDLY_NAME)) {
+
+                SET values = attr.getValues();
+                ANY value = (ANY) values.elementAt(0);
+
+                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
+                BMPString nickname = (BMPString) (new BMPString.Template()).decode(bis);
+
+                certInfo.nickname = nickname.toString();
+                logger.fine("Nickname: " + certInfo.nickname);
+
+
+            } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {
+
+                SET values = attr.getValues();
+                ANY value = (ANY) values.elementAt(0);
+
+                ByteArrayInputStream bis = new ByteArrayInputStream(value.getEncoded());
+                OCTET_STRING keyID = (OCTET_STRING) new OCTET_STRING.Template().decode(bis);
+
+                certInfo.id = new BigInteger(1, keyID.toByteArray());
+                logger.fine("ID: " + certInfo.id.toString(16));
+
+            } else if (oid.equals(PKCS12.CERT_TRUST_FLAGS_OID) && trustFlagsEnabled) {
+
+                SET values = attr.getValues();
+                ANY value = (ANY) values.elementAt(0);
+
+                ByteArrayInputStream is = new ByteArrayInputStream(value.getEncoded());
+                BMPString trustFlags = (BMPString) (new BMPString.Template()).decode(is);
+
+                certInfo.trustFlags = trustFlags.toString();
+                logger.fine("Trust flags: " + certInfo.trustFlags);
+            }
+        }
+
+        return certInfo;
+    }
+
+    public void getKeyInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {
+
+        logger.fine("Getting private keys");
+
+        AuthenticatedSafes safes = pfx.getAuthSafes();
+
+        for (int i = 0; i < safes.getSize(); i++) {
+
+            SEQUENCE contents = safes.getSafeContentsAt(password, i);
+
+            for (int j = 0; j < contents.size(); j++) {
+
+                SafeBag bag = (SafeBag) contents.elementAt(j);
+                OBJECT_IDENTIFIER oid = bag.getBagType();
+
+                if (!oid.equals(SafeBag.PKCS8_SHROUDED_KEY_BAG)) continue;
+
+                PKCS12KeyInfo keyInfo = getKeyInfo(bag, password);
+                pkcs12.addKeyInfo(keyInfo);
+            }
+        }
+    }
+
+    public void getCertInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {
+
+        logger.fine("Getting certificates");
+
+        AuthenticatedSafes safes = pfx.getAuthSafes();
+
+        for (int i = 0; i < safes.getSize(); i++) {
+
+            SEQUENCE contents = safes.getSafeContentsAt(password, i);
+
+            for (int j = 0; j < contents.size(); j++) {
+
+                SafeBag bag = (SafeBag) contents.elementAt(j);
+                OBJECT_IDENTIFIER oid = bag.getBagType();
+
+                if (!oid.equals(SafeBag.CERT_BAG)) continue;
+
+                PKCS12CertInfo certInfo = getCertInfo(bag);
+                pkcs12.addCertInfo(certInfo, true);
+            }
+        }
+    }
+
+    public PKCS12 loadFromFile(String filename, Password password) throws Exception {
+
+        logger.info("Loading PKCS #12 file");
+
+        Path path = Paths.get(filename);
+        byte[] b = Files.readAllBytes(path);
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(b);
+
+        PFX pfx = (PFX) (new PFX.Template()).decode(bis);
+
+        PKCS12 pkcs12 = new PKCS12();
+
+        StringBuffer reason = new StringBuffer();
+        boolean valid = pfx.verifyAuthSafes(password, reason);
+
+        if (!valid) {
+            throw new Exception("Invalid PKCS #12 password: " + reason);
+        }
+
+        getKeyInfos(pkcs12, pfx, password);
+        getCertInfos(pkcs12, pfx, password);
+
+        return pkcs12;
+    }
+
+    public PKCS12 loadFromFile(String filename) throws Exception {
+        return loadFromFile(filename, null);
+    }
+
+    public PrivateKey.Type getPrivateKeyType(PublicKey publicKey) {
+        if (publicKey.getAlgorithm().equals("EC")) {
+            return PrivateKey.Type.EC;
+        }
+        return PrivateKey.Type.RSA;
+    }
+
+    public PKCS12CertInfo getCertBySubjectDN(PKCS12 pkcs12, String subjectDN)
+            throws CertificateException {
+
+        for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
+            Principal certSubjectDN = certInfo.cert.getSubjectDN();
+            if (LDAPDN.equals(certSubjectDN.toString(), subjectDN)) return certInfo;
+        }
+
+        return null;
+    }
+
+    public void importKey(
+            PKCS12 pkcs12,
+            PKCS12KeyInfo keyInfo) throws Exception {
+
+        logger.fine("Importing private key " + keyInfo.subjectDN);
+
+        PrivateKeyInfo privateKeyInfo = keyInfo.privateKeyInfo;
+
+        // encode private key
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        privateKeyInfo.encode(bos);
+        byte[] privateKey = bos.toByteArray();
+
+        PKCS12CertInfo certInfo = getCertBySubjectDN(pkcs12, keyInfo.subjectDN);
+        if (certInfo == null) {
+            logger.fine("Private key nas no certificate, ignore");
+            return;
+        }
+
+        CryptoManager cm = CryptoManager.getInstance();
+        CryptoToken token = cm.getInternalKeyStorageToken();
+        CryptoStore store = token.getCryptoStore();
+
+        X509Certificate cert = cm.importCACertPackage(certInfo.cert.getEncoded());
+
+        // get public key
+        PublicKey publicKey = cert.getPublicKey();
+
+        // delete the cert again
+        try {
+            store.deleteCert(cert);
+        } catch (NoSuchItemOnTokenException e) {
+            // this is OK
+        }
+
+        // encrypt private key
+        KeyGenerator kg = token.getKeyGenerator(KeyGenAlgorithm.DES3);
+        SymmetricKey sk = kg.generate();
+        byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
+        IVParameterSpec param = new IVParameterSpec(iv);
+        Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
+        c.initEncrypt(sk, param);
+        byte[] encpkey = c.doFinal(privateKey);
+
+        // unwrap private key to load into database
+        KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+        wrapper.initUnwrap(sk, param);
+        wrapper.unwrapPrivate(encpkey, getPrivateKeyType(publicKey), publicKey);
+    }
+
+    public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo) throws Exception {
+
+        CryptoManager cm = CryptoManager.getInstance();
+
+        X509Certificate cert;
+        BigInteger id = certInfo.getID();
+        PKCS12KeyInfo keyInfo = pkcs12.getKeyInfoByID(id);
+
+        if (keyInfo != null) { // cert has key
+            logger.fine("Importing user key for " + certInfo.nickname);
+            importKey(pkcs12, keyInfo);
+
+            logger.fine("Importing user certificate " + certInfo.nickname);
+            cert = cm.importUserCACertPackage(certInfo.cert.getEncoded(), certInfo.nickname);
+
+        } else { // cert has no key
+            logger.fine("Importing CA certificate " + certInfo.nickname);
+            // Note: JSS does not preserve CA certificate nickname
+            cert = cm.importCACertPackage(certInfo.cert.getEncoded());
+        }
+
+        if (certInfo.trustFlags != null && trustFlagsEnabled)
+            setTrustFlags(cert, certInfo.trustFlags);
+    }
+
+    public void storeCertIntoNSS(PKCS12 pkcs12, String nickname) throws Exception {
+        Collection<PKCS12CertInfo> certInfos = pkcs12.getCertInfosByNickname(nickname);
+        for (PKCS12CertInfo certInfo : certInfos) {
+            storeCertIntoNSS(pkcs12, certInfo);
+        }
+    }
+
+    public void storeIntoNSS(PKCS12 pkcs12) throws Exception {
+
+        logger.info("Storing data into NSS database");
+
+        for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) {
+            storeCertIntoNSS(pkcs12, certInfo);
+        }
+    }
+}
-- 
2.5.5