Blob Blame History Raw
From 81240024c49ae0f47d51bbd0501264399b595051 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
---
 base/common/CMakeLists.txt                         |   1 +
 base/common/python/pki/{cli.py => cli/__init__.py} |   0
 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                            | 318 +++++++---
 .../src/com/netscape/cmstools/PKCS12Export.java    | 387 +++++--------
 .../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     |  78 ++-
 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 +++++++++++++++++++++
 36 files changed, 4375 insertions(+), 452 deletions(-)
 rename base/common/python/pki/{cli.py => cli/__init__.py} (100%)
 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 ee401f201429167b67348c35c9c89c3d52b3c3fd..121392512de0b4695c5508fa432bca4f18d167cf 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/__init__.py
similarity index 100%
rename from base/common/python/pki/cli.py
rename to base/common/python/pki/cli/__init__.py
diff --git a/base/common/python/pki/cli/pkcs12.py b/base/common/python/pki/cli/pkcs12.py
new file mode 100644
index 0000000000000000000000000000000000000000..eaca3c6f886f273414c571af1ba3c4d5f508cb30
--- /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 44e286853c252675bff9e25c32377aaa86f8cf18..9d276332aacb5a74b36c20406028e03a21c51b72 100644
--- a/base/common/python/pki/nssdb.py
+++ b/base/common/python/pki/nssdb.py
@@ -18,6 +18,7 @@
 # All rights reserved.
 #
 
+from __future__ import absolute_import
 import base64
 import os
 import shutil
@@ -36,7 +37,6 @@ PKCS7_FOOTER = '-----END PKCS7-----'
 
 
 def convert_data(data, input_format, output_format, header=None, footer=None):
-
     if input_format == output_format:
         return data
 
@@ -46,7 +46,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)
@@ -67,20 +67,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):
-
     return convert_data(csr_data, input_format, output_format, CSR_HEADER, CSR_FOOTER)
 
+
 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):
-
     return convert_data(pkcs7_data, input_format, output_format, PKCS7_HEADER, PKCS7_FOOTER)
 
+
 def get_file_type(filename):
-
     with open(filename, 'r') as f:
         data = f.read()
 
@@ -98,7 +98,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
 
@@ -118,42 +122,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',
@@ -161,15 +167,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:
@@ -188,13 +188,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])
@@ -229,19 +234,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',
@@ -254,9 +260,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 = ''
 
@@ -333,12 +340,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)
 
@@ -352,38 +364,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')
@@ -392,17 +412,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()
 
@@ -418,6 +439,7 @@ class NSSDatabase(object):
             # parse PEM output into separate PEM certificates
             certs = []
             lines = []
+            nicks = []
             state = 'header'
 
             for line in output.splitlines():
@@ -459,6 +481,7 @@ class NSSDatabase(object):
                     n = '%s #%d' % (nickname, counter)
 
                 self.add_cert(n, cert_file, trust_attributes)
+                nicks.append(n)
 
                 counter += 1
 
@@ -466,12 +489,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()
 
@@ -488,20 +515,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()
 
@@ -518,14 +560,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 0000000000000000000000000000000000000000..bd5b5b627903e0daa2c8b70a0569a5cc78321765
--- /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 a43d1d6c144379d655da06f77e8b5056bc787e6a..57cb83e5a5087f4d8efea2743a1b3b7cc95c0489 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 152bf3f62c593aa6441a2082c33e311f583a4534..e476cfcfe0b4476820354ef1ec8a9ddfbf0f734c 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
 
+        if self.verbose:
+            print('PKI options: %s' % ' '.join(pki_options))
+            print('PKI command: %s %s' % (command, ' '.join(cmd_args)))
+
+        if client_type == 'python' or command in PYTHON_COMMANDS:
+            (module, module_args) = self.parse_args(cmd_args)
+            module.execute(module_args)
+
+        elif client_type == 'java':
+            self.execute_java(cmd_args)
+
         else:
-            new_args.append(args[i])
+            raise Exception('Unsupported client type: ' + client_type)
 
-        i = i + 1
-
-    if client_type == 'java':
-        run_java_cli(new_args)
-
-    elif client_type == 'python':
-        run_python_cli(new_args)
-
-    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 9ab2f85052435b77685c42819d35942d1a8d376e..187ff766745130dd140f61fceebc6d1707088e2c 100644
--- a/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java
+++ b/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java
@@ -18,40 +18,17 @@
 package com.netscape.cmstools;
 
 import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
 import java.io.FileReader;
-import java.io.IOException;
-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
  *
@@ -62,262 +39,174 @@ import org.mozilla.jss.util.Password;
  */
 public class PKCS12Export {
 
-    private static boolean debugMode = false;
+    private static Logger logger = Logger.getLogger(PKCS12Export.class.getName());
 
-    private static void debug(String s) {
-        if (debugMode)
-            System.out.println("PKCS12Export debug: " + s);
+    String databaseDirectory;
+    String databasePasswordFilename;
+
+    String pkcs12PasswordFilename;
+    String pkcs12OutputFilename;
+
+    public String getDatabaseDirectory() {
+        return databaseDirectory;
+    }
+
+    public void setDatabaseDirectory(String databaseDirectory) {
+        this.databaseDirectory = databaseDirectory;
+    }
+    public String getDatabasePasswordFilename() {
+        return databasePasswordFilename;
+    }
+
+    public void setDatabasePasswordFilename(String databasePasswordFilename) {
+        this.databasePasswordFilename = databasePasswordFilename;
+    }
+
+    public String getPkcs12PasswordFilename() {
+        return pkcs12PasswordFilename;
+    }
+
+    public void setPkcs12PasswordFilename(String pkcs12PasswordFilename) {
+        this.pkcs12PasswordFilename = pkcs12PasswordFilename;
+    }
+
+    public String getPkcs12OutputFilename() {
+        return pkcs12OutputFilename;
+    }
+
+    public void setPkcs12OutputFilename(String pkcs12OutputFilename) {
+        this.pkcs12OutputFilename = pkcs12OutputFilename;
+    }
+
+    public void initDatabase() throws Exception {
+
+        logger.info("Initializing database in " + databaseDirectory);
+
+        CryptoManager.InitializationValues vals =
+                new CryptoManager.InitializationValues(
+                        databaseDirectory, "", "", "secmod.db");
+        CryptoManager.initialize(vals);
+
+        CryptoManager cm = CryptoManager.getInstance();
+        CryptoToken token = cm.getInternalKeyStorageToken();
+
+        logger.info("Reading database password from " + databasePasswordFilename);
+
+        String line;
+        try (BufferedReader in = new BufferedReader(new FileReader(databasePasswordFilename))) {
+            line = in.readLine();
+            if (line == null) {
+                line = "";
+            }
+        }
+        Password password = new Password(line.toCharArray());
+
+        logger.info("Logging into security token");
+
+        try {
+            token.login(password);
+        } finally {
+            password.clear();
+        }
+    }
+
+    public void exportData() throws Exception {
+
+        logger.info("Reading PKCS #12 password from " + pkcs12PasswordFilename);
+
+        String line;
+        try (BufferedReader in = new BufferedReader(new FileReader(pkcs12PasswordFilename))) {
+            line = in.readLine();
+            if (line == null) {
+                line = "";
+            }
+        }
+        Password password = new Password(line.toCharArray());
+
+        logger.info("Exporting NSS database into " + pkcs12OutputFilename);
+
+        try {
+            PKCS12Util util = new PKCS12Util();
+            PKCS12 pkcs12 = new PKCS12();
+            util.loadFromNSS(pkcs12);
+            util.storeIntoFile(pkcs12, pkcs12OutputFilename, password);
+
+        } finally {
+            password.clear();
+        }
     }
 
-    private static void printUsage() {
+    public static void printUsage() {
         System.out.println(
                 "Usage: PKCS12Export -d <cert/key db directory> -p <file containing password for keydb> -w <file containing pkcs12 password> -o <output file for pkcs12>");
-        System.out.println("");
+        System.out.println();
         System.out.println("If you want to turn on debug, do the following:");
         System.out.println(
                 "Usage: PKCS12Export -debug -d <cert/key db directory> -p <file containing password for keydb> -w <file containing pkcs12 password> -o <output file for pkcs12>");
     }
 
-    private static byte[] getEncodedKey(org.mozilla.jss.crypto.PrivateKey pkey) {
-        try {
-            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);
-            byte[] recovered = c.doFinal(enckey);
-            return recovered;
-        } catch (Exception e) {
-            debug("PKCS12Export  getEncodedKey: Exception=" + e.toString());
-            System.exit(1);
-        }
+    public static void main(String args[]) throws Exception {
 
-        return null;
-    }
-
-    private static void addKeyBag(org.mozilla.jss.crypto.PrivateKey pkey, X509Certificate x509cert,
-            Password pass, byte[] localKeyId, SEQUENCE safeContents) {
-        try {
-            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);
-        } catch (Exception e) {
-            debug("PKCS12Export addKeyBag: Exception=" + e.toString());
-            System.exit(1);
-        }
-    }
-
-    private static byte[] addCertBag(X509Certificate x509cert, String nickname,
-            SEQUENCE safeContents) throws IOException {
-        byte[] localKeyId = null;
-        try {
-            ASN1Value cert = new OCTET_STRING(x509cert.getEncoded());
-            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);
-        } catch (Exception e) {
-            debug("PKCS12Export addCertBag: " + e.toString());
-            System.exit(1);
-        }
-
-        return localKeyId;
-    }
-
-    private static byte[] createLocalKeyId(X509Certificate cert) {
-        try {
-            // SHA1 hash of the X509Cert der encoding
-            byte certDer[] = cert.getEncoded();
-
-            MessageDigest md = MessageDigest.getInstance("SHA");
-
-            md.update(certDer);
-            return md.digest();
-        } catch (Exception e) {
-            debug("PKCS12Export createLocalKeyId: Exception: " + e.toString());
-            System.exit(1);
-        }
-
-        return null;
-    }
-
-    private static SET createBagAttrs(String nickName, byte localKeyId[])
-            throws IOException {
-        try {
-            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;
-        } catch (Exception e) {
-            debug("PKCS12Export createBagAttrs: Exception=" + e.toString());
-            System.exit(1);
-        }
-
-        return null;
-    }
-
-    public static void main(String args[]) {
         if (args.length < 8) {
             printUsage();
             System.exit(1);
         }
 
-        String pwdfile = null;
-        String dir = null;
-        String pk12pwdfile = null;
-        String pk12output = null;
+        boolean debug = false;
+        String databaseDirectory = null;
+        String databasePasswordFilename = null;
+        String pkcs12PasswordFilename = null;
+        String pkcs12OutputFilename = null;
+
+        // TODO: get parameters using getopt
+
         for (int i = 0; i < args.length; i++) {
             if (args[i].equals("-d")) {
-                dir = args[i + 1];
+                databaseDirectory = args[i + 1];
+
             } else if (args[i].equals("-p")) {
-                pwdfile = args[i + 1];
+                databasePasswordFilename = args[i + 1];
+
             } else if (args[i].equals("-s")) {
                 // snickname = args[i + 1];
+
             } else if (args[i].equals("-w")) {
-                pk12pwdfile = args[i + 1];
+                pkcs12PasswordFilename = args[i + 1];
+
             } else if (args[i].equals("-o")) {
-                pk12output = args[i + 1];
+                pkcs12OutputFilename = args[i + 1];
+
             } else if (args[i].equals("-debug")) {
-                debugMode = true;
+                debug = true;
             }
         }
 
-        debug("The directory for certdb/keydb is " + dir);
-        debug("The password file for keydb is " + pwdfile);
-
-        // get password
-        String pwd = null;
-        BufferedReader in = null;
-        try {
-            in = new BufferedReader(new FileReader(pwdfile));
-            pwd = in.readLine();
-            if (pwd == null) {
-                pwd = "";
-            }
-        } catch (Exception e) {
-            debug("Failed to read the keydb password from the file. Exception: " + e.toString());
-            System.exit(1);
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
+        if (debug) {
+            Logger.getLogger("org.dogtagpki").setLevel(Level.FINE);
+            Logger.getLogger("com.netscape").setLevel(Level.FINE);
+            Logger.getLogger("netscape").setLevel(Level.FINE);
         }
-        String pk12pwd = null;
 
-        try {
-            in = new BufferedReader(new FileReader(pk12pwdfile));
-            pk12pwd = in.readLine();
-            if (pk12pwd == null) {
-                pk12pwd = "";
-            }
-        } catch (Exception e) {
-            debug("Failed to read the keydb password from the file. Exception: " + e.toString());
-            System.exit(1);
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
-        }
+        // TODO: validate parameters
 
-        CryptoManager cm = null;
         try {
-            CryptoManager.InitializationValues vals =
-                    new CryptoManager.InitializationValues(dir, "", "", "secmod.db");
-            CryptoManager.initialize(vals);
-            cm = CryptoManager.getInstance();
-        } catch (Exception e) {
-            debug("Failed to initialize the certdb.");
-            System.exit(1);
-        }
+            PKCS12Export tool = new PKCS12Export();
+            tool.setDatabaseDirectory(databaseDirectory);
+            tool.setDatabasePasswordFilename(databasePasswordFilename);
+            tool.setPkcs12PasswordFilename(pkcs12PasswordFilename);
+            tool.setPkcs12OutputFilename(pkcs12OutputFilename);
 
-        SEQUENCE encSafeContents = new SEQUENCE();
-        SEQUENCE safeContents = new SEQUENCE();
-        try {
-            CryptoToken token = cm.getInternalKeyStorageToken();
-            Password pass = new Password(pwd.toCharArray());
-            token.login(pass);
-            CryptoStore store = token.getCryptoStore();
-            X509Certificate[] certs = store.getCertificates();
-            debug("Number of user certificates = " + certs.length);
-            Password pass12 = new Password(pk12pwd.toCharArray());
-            for (int i = 0; i < certs.length; i++) {
-                String nickname = certs[i].getNickname();
-                debug("Certificate nickname = " + nickname);
-                org.mozilla.jss.crypto.PrivateKey prikey = null;
-                try {
-                    prikey = cm.findPrivKeyByCert(certs[i]);
-                } catch (Exception e) {
-                    debug("PKCS12Export Exception: " + e.toString());
-                }
+            tool.initDatabase();
+            tool.exportData();
 
-                if (prikey == null) {
-                    debug("Private key is null");
-                    addCertBag(certs[i], null, safeContents);
-                } else {
-                    debug("Private key is not null");
-                    byte localKeyId[] =
-                            addCertBag(certs[i], nickname, safeContents);
-                    addKeyBag(prikey, certs[i], pass12, localKeyId, encSafeContents);
-                }
-            }
+            System.out.println("Export complete.");
 
-            AuthenticatedSafes authSafes = new AuthenticatedSafes();
-            authSafes.addSafeContents(safeContents);
-            authSafes.addSafeContents(encSafeContents);
-            PFX pfx = new PFX(authSafes);
-            pfx.computeMacData(pass12, null, 5);
-            ByteArrayOutputStream bos = new ByteArrayOutputStream();
-            pfx.encode(bos);
-            FileOutputStream fos = new FileOutputStream(pk12output);
-            fos.write(bos.toByteArray());
-            fos.flush();
-            fos.close();
-            pass.clear();
-            pass12.clear();
         } catch (Exception e) {
-            debug("PKCS12Export Exception: " + e.toString());
+            if (debug) {
+                logger.log(Level.SEVERE, "Unable to export PKCS #12 file", e);
+            } else {
+                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 4d63d9bc12c012bc1db207f7a31a0b50cf5bc2af..1e38113ca13e6c7dc2ff7216ff8f702db5586b24 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
@@ -46,6 +46,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;
 
@@ -82,6 +83,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 0000000000000000000000000000000000000000..b841ec0ee16ebdf3d3a66e1fe1fbdc0d4971b909
--- /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 0000000000000000000000000000000000000000..48e4907cf7c21604465cfb303c6d35edd9489f60
--- /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 0000000000000000000000000000000000000000..fe7092c00ef3e4ee202d47b479ec22caea5f36c1
--- /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 0000000000000000000000000000000000000000..8fb526d489fea7b482e8af489879f7657f816996
--- /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 0000000000000000000000000000000000000000..9bb4ad3ba2113e2426e8d9a250c0041647e1e562
--- /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 0000000000000000000000000000000000000000..8f7f11398c8fd9760ca0e709d19fd0af2a4b689b
--- /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 0000000000000000000000000000000000000000..d42c449b40c1b1fc97e921f9635b9e6a8a1e922b
--- /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 0000000000000000000000000000000000000000..ae574d3870a610cdf009b852732644675424a700
--- /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 0000000000000000000000000000000000000000..fbebddabb918f12c0f94943d1ecf8fe102d01121
--- /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 0000000000000000000000000000000000000000..0dc39f470279016373f0d159824d5b8ab7695c32
--- /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 0000000000000000000000000000000000000000..19b368765d0ea93a15b4c2ecd037f545a75b03ea
--- /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 404bcf0a12914f714b59f9e8fe286fd75a42367a..56ca9f1fcbdd95a2e5e2be11e1ff42c0d1f0b22b 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 4493b59df00adc0dad9b6808fb10c1769634335c..22b6fcfc3b874e129c55e11d385a09ad3ce31d14 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 2ad8652f4fddd032779941cb7e2ae4e643c25e0a..fe8ce2bc302145570fa96a5767790a6981cdd354 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 becad1447e5eebd45f09afe8ac37e495c38c6276..ebde6f19b2080dd827298d84aca9adaa263b89bd 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 0000000000000000000000000000000000000000..7dfa680cf67141b8d7dc6518f9d8a9e78a05a906
--- /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 0000000000000000000000000000000000000000..b913a20c5fb4371f86cf677ffee2b0182a9789b8
--- /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 9f82dff756bf98b0a14783fdd48c25d9d406f50b..09ece80a72213764f0f69ffad894f15eef3474f5 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()
@@ -325,27 +328,31 @@ class SubsystemCertFindCLI(pki.cli.CLI):
     def execute(self, argv):
 
         try:
-            opts, args = getopt.getopt(argv, 'i:v', [
-                'instance=',
+            opts, args = getopt.gnu_getopt(argv, 'i:v', [
+                '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 0000000000000000000000000000000000000000..bf96d96103470c59edd3828cfe33a2770fed33bc
--- /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 0000000000000000000000000000000000000000..7284eaa842d27b4f795a3e46466343f0915fff92
--- /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 e7b257f7d3eb1178827ed2ef1e211e4a21455d1b..54f065f094c811cdbf944a0ac14e019d0d4d6145 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 3f8623af159d4e52195dae010625c8e5cfbaefc8..5c28580ce861743d797f3026b8689f2e4c131fdb 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 cdfd98ee16346dbbd12adb20da934d32cdc14226..cf56d1bc4320bfe6e88f6436023d50e0c7ebba24 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 0000000000000000000000000000000000000000..6c7880aa8039e3f568285fe55adc0adb15ebeb22
--- /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 0000000000000000000000000000000000000000..ec7b0e332f8909c4a7aefae8ed58c9117e485a89
--- /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 0000000000000000000000000000000000000000..c7e84f01ffa19a0fc116f1f3fddf2bf3dfe9de9e
--- /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 0000000000000000000000000000000000000000..7c9ab2fb4e9554b18c3bf09c9e7c90f8349918fd
--- /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.4.3