diff --git a/SOURCES/pki-core-Added-support-for-cloning-3rd-party-CA-certificates.patch b/SOURCES/pki-core-Added-support-for-cloning-3rd-party-CA-certificates.patch
new file mode 100644
index 0000000..efdfcb0
--- /dev/null
+++ b/SOURCES/pki-core-Added-support-for-cloning-3rd-party-CA-certificates.patch
@@ -0,0 +1,5703 @@
+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
+
diff --git a/SOURCES/pki-core-Added-support-for-existing-CA-case.patch b/SOURCES/pki-core-Added-support-for-existing-CA-case.patch
new file mode 100644
index 0000000..bc1da13
--- /dev/null
+++ b/SOURCES/pki-core-Added-support-for-existing-CA-case.patch
@@ -0,0 +1,1698 @@
+From 0a7411f3bf1e65d2e0897c2504f376d8cf0c59af Mon Sep 17 00:00:00 2001
+From: "Endi S. Dewata" <edewata@redhat.com>
+Date: Thu, 12 Nov 2015 00:23:26 +0100
+Subject: [PATCH] Added support for existing CA case.
+
+The deployment procedure for external CA has been modified
+such that it generates the CA CSR before starting the server.
+This allows the same procedure to be used to import CA
+certificate from an existing server. It also removes the
+requirement to keep the server running while waiting to get
+the CSR signed by an external CA.
+
+The pki ca-cert-request-submit command has been modified to
+provide options to specify the profile name and the CSR which
+will be used to create and populate the request object. This
+way it's no longer necessary to download the request template
+and insert the CSR manually.
+
+A new pki-server subsystem-cert-export command has been added
+to export a system certificate, the CSR, and the key. This
+command can be used to migrate a system certificate into another
+instance.
+
+The man page have been updated to reflect these changes.
+
+The installation code for external CA case has been fixed such
+that IPA can detect step 1 completion properly.
+
+The code that handles certificate data conversion has been fixed
+to reformat base-64 data for PEM output properly.
+
+The installation summary for step 1 has been updated to provide
+more accurate information.
+
+https://fedorahosted.org/pki/ticket/456
+---
+ base/common/python/pki/nssdb.py                    | 532 +++++++++++++++++++++
+ .../certsrv/system/ConfigurationRequest.java       |  12 +
+ base/java-tools/man/man1/pki-cert.1                |  23 +-
+ .../cmstools/cert/CertRequestSubmitCLI.java        | 170 ++++++-
+ .../cms/servlet/csadmin/ConfigurationUtils.java    | 101 ++++
+ .../dogtagpki/server/rest/SystemConfigService.java |  38 +-
+ base/server/etc/default.cfg                        |  10 +-
+ base/server/python/pki/server/__init__.py          |   7 +
+ base/server/python/pki/server/cli/subsystem.py     | 126 +++++
+ .../python/pki/server/deployment/pkihelper.py      |  77 +--
+ .../server/deployment/scriptlets/configuration.py  | 132 ++++-
+ .../server/deployment/scriptlets/finalization.py   |  12 +-
+ base/server/sbin/pkispawn                          |  39 +-
+ 13 files changed, 1211 insertions(+), 68 deletions(-)
+ create mode 100644 base/common/python/pki/nssdb.py
+
+diff --git a/base/common/python/pki/nssdb.py b/base/common/python/pki/nssdb.py
+new file mode 100644
+index 0000000000000000000000000000000000000000..44e286853c252675bff9e25c32377aaa86f8cf18
+--- /dev/null
++++ b/base/common/python/pki/nssdb.py
+@@ -0,0 +1,532 @@
++# Authors:
++#     Endi S. Dewata <edewata@redhat.com>
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; version 2 of the License.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License along
++# with this program; if not, write to the Free Software Foundation, Inc.,
++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++#
++# Copyright (C) 2015 Red Hat, Inc.
++# All rights reserved.
++#
++
++import base64
++import os
++import shutil
++import subprocess
++import tempfile
++
++
++CSR_HEADER = '-----BEGIN NEW CERTIFICATE REQUEST-----'
++CSR_FOOTER = '-----END NEW CERTIFICATE REQUEST-----'
++
++CERT_HEADER = '-----BEGIN CERTIFICATE-----'
++CERT_FOOTER = '-----END CERTIFICATE-----'
++
++PKCS7_HEADER = '-----BEGIN PKCS7-----'
++PKCS7_FOOTER = '-----END PKCS7-----'
++
++
++def convert_data(data, input_format, output_format, header=None, footer=None):
++
++    if input_format == output_format:
++        return data
++
++    if input_format == 'base64' and output_format == 'pem':
++
++        # join base-64 data into a single line
++        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)]
++
++        # add header and footer
++        return '%s\n%s\n%s\n' % (header, '\n'.join(lines), footer)
++
++    if input_format == 'pem' and output_format == 'base64':
++
++        # join multiple lines into a single line
++        lines = []
++        for line in data.splitlines():
++            line = line.rstrip('\r\n')
++            if line == header:
++                continue
++            if line == footer:
++                continue
++            lines.append(line)
++
++        return ''.join(lines)
++
++    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()
++
++    if data.startswith(CSR_HEADER):
++        return 'csr'
++
++    if data.startswith(CERT_HEADER):
++        return 'cert'
++
++    if data.startswith(PKCS7_HEADER):
++        return 'pkcs7'
++
++    return None
++
++
++class NSSDatabase(object):
++
++    def __init__(self, directory, token='internal', password=None, password_file=None):
++        self.directory = directory
++        self.token = token
++
++        self.tmpdir = tempfile.mkdtemp()
++
++        if password:
++            self.password_file = os.path.join(self.tmpdir, 'password.txt')
++            with open(self.password_file, 'w') as f:
++                f.write(password)
++
++        elif password_file:
++            self.password_file = password_file
++
++        else:
++            raise Exception('Missing NSS database password')
++
++    def close(self):
++        shutil.rmtree(self.tmpdir)
++
++    def add_cert(self,
++        nickname,
++        cert_file,
++        trust_attributes=',,'):
++
++        cmd = [
++            'certutil',
++            '-A',
++            '-d', self.directory,
++            '-h', self.token,
++            '-f', self.password_file,
++            '-n', nickname,
++            '-i', cert_file,
++            '-t', trust_attributes
++        ]
++
++        subprocess.check_call(cmd)
++
++    def modify_cert(self,
++        nickname,
++        trust_attributes):
++
++        cmd = [
++            'certutil',
++            '-M',
++            '-d', self.directory,
++            '-h', self.token,
++            '-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',
++            '-out', noise_file,
++            str(size)
++        ])
++
++    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:
++            if not noise_file:
++                noise_file = os.path.join(tmpdir, 'noise.bin')
++                if key_size:
++                    size = key_size
++                else:
++                    size = 2048
++                self.create_noise(
++                    noise_file=noise_file,
++                    size=size)
++
++            binary_request_file = os.path.join(tmpdir, 'request.bin')
++
++            cmd = [
++                'certutil',
++                '-R',
++                '-d', self.directory,
++                '-h', self.token,
++                '-f', self.password_file,
++                '-s', subject_dn,
++                '-o', binary_request_file,
++                '-z', noise_file
++            ]
++
++            if key_type:
++                cmd.extend(['-k', key_type])
++
++            if key_size:
++                cmd.extend(['-g', str(key_size)])
++
++            if curve:
++                cmd.extend(['-q', curve])
++
++            if hash_alg:
++                cmd.extend(['-Z', hash_alg])
++
++            # generate binary request
++            subprocess.check_call(cmd)
++
++            # encode binary request in base-64
++            b64_request_file = os.path.join(tmpdir, 'request.b64')
++            subprocess.check_call([
++                'BtoA', binary_request_file, b64_request_file])
++
++            # read base-64 request
++            with open(b64_request_file, 'r') as f:
++                b64_request = f.read()
++
++            # add header and footer
++            with open(request_file, 'w') as f:
++                f.write('-----BEGIN NEW CERTIFICATE REQUEST-----\n')
++                f.write(b64_request)
++                f.write('-----END NEW CERTIFICATE REQUEST-----\n')
++
++        finally:
++            shutil.rmtree(tmpdir)
++
++    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,
++            '-f', self.password_file,
++            '-c', subject_dn,
++            '-a',
++            '-i', request_file,
++            '-o', cert_file,
++            '-m', serial,
++            '-v', str(validity),
++            '--keyUsage', 'digitalSignature,nonRepudiation,certSigning,crlSigning,critical',
++            '-2',
++            '-3',
++            '--extSKID',
++            '--extAIA'
++        ]
++
++        p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
++
++        keystroke = ''
++
++        # Is this a CA certificate [y/N]?
++        keystroke += 'y\n'
++
++        # Enter the path length constraint, enter to skip [<0 for unlimited path]:
++        keystroke += '\n'
++
++        # Is this a critical extension [y/N]?
++        keystroke += 'y\n'
++
++        # Enter value for the authKeyID extension [y/N]?
++        keystroke += 'y\n'
++
++        # TODO: generate SHA1 ID (see APolicyRule.formSHA1KeyId())
++        # Enter value for the key identifier fields,enter to omit:
++        keystroke += '2d:7e:83:37:75:5a:fd:0e:8d:52:a3:70:16:93:36:b8:4a:d6:84:9f\n'
++
++        # Select one of the following general name type:
++        keystroke += '0\n'
++
++        # Enter value for the authCertSerial field, enter to omit:
++        keystroke += '\n'
++
++        # Is this a critical extension [y/N]?
++        keystroke += '\n'
++
++        # TODO: generate SHA1 ID (see APolicyRule.formSHA1KeyId())
++        # Adding Subject Key ID extension.
++        # Enter value for the key identifier fields,enter to omit:
++        keystroke += '2d:7e:83:37:75:5a:fd:0e:8d:52:a3:70:16:93:36:b8:4a:d6:84:9f\n'
++
++        # Is this a critical extension [y/N]?
++        keystroke += '\n'
++
++        # Enter access method type for Authority Information Access extension:
++        keystroke += '2\n'
++
++        # Select one of the following general name type:
++        keystroke += '7\n'
++
++        # TODO: replace with actual hostname name and port number
++        # Enter data:
++        keystroke += 'http://server.example.com:8080/ca/ocsp\n'
++
++        # Select one of the following general name type:
++        keystroke += '0\n'
++
++        # Add another location to the Authority Information Access extension [y/N]
++        keystroke += '\n'
++
++        # Is this a critical extension [y/N]?
++        keystroke += '\n'
++
++        p.communicate(keystroke)
++
++        rc = p.wait()
++
++        if rc:
++            raise Exception('Failed to generate self-signed CA certificate. RC: %d' % rc)
++
++    def get_cert(self, nickname, output_format='pem'):
++
++        if output_format == 'pem':
++            output_format_option = '-a'
++
++        elif output_format == 'base64':
++            output_format_option = '-r'
++
++        else:
++            raise Exception('Unsupported output format: %s' % output_format)
++
++        cmd = [
++            'certutil',
++            '-L',
++            '-d', self.directory,
++            '-h', self.token,
++            '-f', self.password_file,
++            '-n', nickname,
++            output_format_option
++        ]
++
++        cert_data = subprocess.check_output(cmd)
++
++        if output_format == 'base64':
++            cert_data = base64.b64encode(cert_data)
++
++        return cert_data
++
++    def remove_cert(self, nickname):
++
++        cmd = [
++            'certutil',
++            '-D',
++            '-d', self.directory,
++            '-h', self.token,
++            '-f', self.password_file,
++            '-n', nickname
++        ]
++
++        subprocess.check_call(cmd)
++
++    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
++                self.add_cert(
++                    nickname=nickname,
++                    cert_file=cert_chain_file,
++                    trust_attributes=trust_attributes)
++                return self.get_cert(
++                    nickname=nickname,
++                    output_format='base64')
++
++            elif file_type == 'pkcs7': # import PKCS #7 cert chain
++                return self.import_pkcs7(
++                    pkcs7_file=cert_chain_file,
++                    nickname=nickname,
++                    trust_attributes=trust_attributes,
++                    output_format='base64')
++
++            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')
++
++                tmp_cert_chain_file = os.path.join(tmpdir, 'cert_chain.p7b')
++                with open(tmp_cert_chain_file, 'w') as f:
++                    f.write(pkcs7_data)
++
++                self.import_pkcs7(
++                    pkcs7_file=tmp_cert_chain_file,
++                    nickname=nickname,
++                    trust_attributes=trust_attributes)
++
++                return base64_data
++
++        finally:
++            shutil.rmtree(tmpdir)
++
++    def import_pkcs7(self, pkcs7_file, nickname, trust_attributes=None, output_format='pem'):
++
++        tmpdir = tempfile.mkdtemp()
++
++        try:
++            # export certs from PKCS #7 into PEM output
++            output = subprocess.check_output([
++                'openssl',
++                'pkcs7',
++                '-print_certs',
++                '-in', pkcs7_file
++            ])
++
++            # parse PEM output into separate PEM certificates
++            certs = []
++            lines = []
++            state = 'header'
++
++            for line in output.splitlines():
++
++                if state == 'header':
++                    if line != CERT_HEADER:
++                        # ignore header lines
++                        pass
++                    else:
++                        # save cert header
++                        lines.append(line)
++                        state = 'body'
++
++                elif state == 'body':
++                    if line != CERT_FOOTER:
++                        # save cert body
++                        lines.append(line)
++                    else:
++                        # save cert footer
++                        lines.append(line)
++
++                        # construct PEM cert
++                        cert = '\n'.join(lines)
++                        certs.append(cert)
++                        lines = []
++                        state = 'header'
++
++            # import PEM certs into NSS database
++            counter = 1
++            for cert in certs:
++
++                cert_file = os.path.join(tmpdir, 'cert%d.pem' % counter)
++                with open(cert_file, 'w') as f:
++                    f.write(cert)
++
++                if counter == 1:
++                    n = nickname
++                else:
++                    n = '%s #%d' % (nickname, counter)
++
++                self.add_cert(n, cert_file, trust_attributes)
++
++                counter += 1
++
++            # convert PKCS #7 data to the requested format
++            with open(pkcs7_file, 'r') as f:
++                data = f.read()
++
++            return convert_pkcs7(data, 'pem', output_format)
++
++        finally:
++            shutil.rmtree(tmpdir)
++
++    def import_pkcs12(self, pkcs12_file, pkcs12_password=None, pkcs12_password_file=None):
++
++        tmpdir = tempfile.mkdtemp()
++
++        try:
++            if pkcs12_password:
++                password_file = os.path.join(tmpdir, 'password.txt')
++                with open(password_file, 'w') as f:
++                    f.write(pkcs12_password)
++
++            elif pkcs12_password_file:
++                password_file = pkcs12_password_file
++
++            else:
++                raise Exception('Missing PKCS #12 password')
++
++            cmd = [
++                'pk12util',
++                '-d', self.directory,
++                '-h', self.token,
++                '-k', self.password_file,
++                '-i', pkcs12_file,
++                '-w', password_file
++            ]
++
++            subprocess.check_call(cmd)
++
++        finally:
++            shutil.rmtree(tmpdir)
++
++    def export_pkcs12(self, pkcs12_file, nickname, pkcs12_password=None, pkcs12_password_file=None):
++
++        tmpdir = tempfile.mkdtemp()
++
++        try:
++            if pkcs12_password:
++                password_file = os.path.join(tmpdir, 'password.txt')
++                with open(password_file, 'w') as f:
++                    f.write(pkcs12_password)
++
++            elif pkcs12_password_file:
++                password_file = pkcs12_password_file
++
++            else:
++                raise Exception('Missing PKCS #12 password')
++
++            cmd = [
++                'pk12util',
++                '-d', self.directory,
++                '-k', self.password_file,
++                '-o', pkcs12_file,
++                '-w', password_file,
++                '-n', nickname
++            ]
++
++            subprocess.check_call(cmd)
++
++        finally:
++            shutil.rmtree(tmpdir)
+diff --git a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
+index 0682ac98f151f5405764636e77971974a91eed8c..f3f0f5072208ffd5dd5983b4b122c3e909c48db3 100644
+--- a/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
++++ b/base/common/src/com/netscape/certsrv/system/ConfigurationRequest.java
+@@ -175,6 +175,9 @@ public class ConfigurationRequest {
+     protected String adminCert;
+ 
+     @XmlElement
++    protected Boolean external;
++
++    @XmlElement
+     protected String standAlone;
+ 
+     @XmlElement
+@@ -739,6 +742,14 @@ public class ConfigurationRequest {
+         this.adminCert = adminCert;
+     }
+ 
++    public Boolean isExternal() {
++        return external;
++    }
++
++    public void setExternal(Boolean external) {
++        this.external = external;
++    }
++
+     public boolean getStandAlone() {
+         return (standAlone != null && standAlone.equalsIgnoreCase("true"));
+     }
+@@ -930,6 +941,7 @@ public class ConfigurationRequest {
+                ", adminCert=" + adminCert +
+                ", importAdminCert=" + importAdminCert +
+                ", generateServerCert=" + generateServerCert +
++               ", external=" + external +
+                ", standAlone=" + standAlone +
+                ", stepTwo=" + stepTwo +
+                ", authdbBaseDN=" + authdbBaseDN +
+diff --git a/base/java-tools/man/man1/pki-cert.1 b/base/java-tools/man/man1/pki-cert.1
+index ffa1fea5d0a29a5b425b1fcaa497e8b831251f7f..7ece1ad7bfc277a4093acdee9592d8671b00b6bd 100644
+--- a/base/java-tools/man/man1/pki-cert.1
++++ b/base/java-tools/man/man1/pki-cert.1
+@@ -191,23 +191,32 @@ To release a certificate that has been placed on hold:
+ .B pki <agent authentication> ca-cert-release-hold <certificate ID>
+ 
+ .SS Certificate Requests
+-To request a certificate, first generate a certificate request in PKCS #10 or CRMF, and store this request in the XML template file, of the profile type the request relates to.
+ 
+-The list of profiles can be viewed using the CLI command:
++To request a certificate, first generate a certificate signing request (CSR),
++then submit it with a certificate profile. The list of available profiles can
++be viewed using the following command:
+ 
+ .B pki ca-cert-request-profile-find
+ 
+-The XML template file for a profile type can be created by calling the ca-cert-request-profile-show CLI command. For example:
++To generate a CSR, use the certutil, PKCS10Client, or
++CRMFPopClient, and store it into a file.
+ 
+-\fBpki ca-cert-request-profile-show <profileID> \-\-output <file to store the XML template>\fP
++Basic requests can be submitted using the following command:
+ 
+-will store the XML template of the request in the specified output file.
++.B pki ca-cert-request-submit --profile <profile ID> --request-type <type> --csr-file <CSR file> --subject <subject DN>
+ 
+-Then, fill in the values in the XML file and submit the request for review.  This can be done without authentication.
++To submit more advanced requests, download a template of the request file for
++a particular profile using the following command:
++
++.B pki ca-cert-request-profile-show <profile ID> \-\-output <request file>
++
++Then, edit the request file, fill in the input attributes required by the
++profile, and submit the request using the following command:
+ 
+ .B pki ca-cert-request-submit <request file>
+ 
+-Then, an agent needs to review the request by running the following command:
++Depending on the profile, an agent may need to review the request by running
++the following command:
+ 
+ .B pki <agent authentication> ca-cert-request-review <request ID> --file <file to store the certificate request>
+ 
+diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
+index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..7a1f08ff3d51e785bef8b625c17082ed81efbdfd 100644
+--- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
++++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java
+@@ -3,17 +3,22 @@ package com.netscape.cmstools.cert;
+ import java.io.File;
+ import java.io.FileNotFoundException;
+ import java.util.Arrays;
++import java.util.HashMap;
++import java.util.Map;
+ import java.util.Scanner;
+-
+-import javax.xml.bind.JAXBException;
++import java.util.Vector;
+ 
+ import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.Option;
+ import org.apache.commons.cli.ParseException;
+ 
+ import com.netscape.certsrv.cert.CertEnrollmentRequest;
+-import com.netscape.certsrv.cert.CertRequestInfos;
++import com.netscape.certsrv.profile.ProfileAttribute;
++import com.netscape.certsrv.profile.ProfileInput;
+ import com.netscape.cmstools.cli.CLI;
+-import com.netscape.cmstools.cli.MainCLI;
++
++import netscape.ldap.util.DN;
++import netscape.ldap.util.RDN;
+ 
+ public class CertRequestSubmitCLI extends CLI {
+ 
+@@ -22,6 +27,37 @@ public class CertRequestSubmitCLI extends CLI {
+     public CertRequestSubmitCLI(CertCLI certCLI) {
+         super("request-submit", "Submit certificate request", certCLI);
+         this.certCLI = certCLI;
++
++        Option option = new Option(null, "issuer-id", true, "Authority ID (host authority if omitted)");
++        option.setArgName("ID");
++        options.addOption(option);
++
++        option = new Option(null, "issuer-dn", true, "Authority DN (host authority if omitted)");
++        option.setArgName("DN");
++        options.addOption(option);
++
++        option = new Option(null, "username", true, "Username for request authentication");
++        option.setArgName("username");
++        options.addOption(option);
++
++        option = new Option(null, "password", false, "Prompt password for request authentication");
++        options.addOption(option);
++
++        option = new Option(null, "profile", true, "Certificate profile");
++        option.setArgName("profile");
++        options.addOption(option);
++
++        option = new Option(null, "request-type", true, "Request type (default: pkcs10)");
++        option.setArgName("type");
++        options.addOption(option);
++
++        option = new Option(null, "csr-file", true, "File containing the CSR");
++        option.setArgName("path");
++        options.addOption(option);
++
++        option = new Option(null, "subject", true, "Subject DN");
++        option.setArgName("DN");
++        options.addOption(option);
+     }
+ 
+     public void printHelp() {
+@@ -29,7 +65,7 @@ public class CertRequestSubmitCLI extends CLI {
+     }
+ 
+     @Override
+-    public void execute(String[] args) {
++    public void execute(String[] args) throws Exception {
+         // Always check for "--help" prior to parsing
+         if (Arrays.asList(args).contains("--help")) {
+             // Display usage
+@@ -49,32 +85,124 @@ public class CertRequestSubmitCLI extends CLI {
+ 
+         String[] cmdArgs = cmd.getArgs();
+ 
+-        if (cmdArgs.length < 1) {
+-            System.err.println("Error: No filename specified.");
++        String requestFilename = cmdArgs.length > 0 ? cmdArgs[0] : null;
++        String profileID = cmd.getOptionValue("profile");
++
++        if (requestFilename == null && profileID == null) {
++            System.err.println("Error: Missing request file or profile ID.");
+             printHelp();
+             System.exit(-1);
+         }
+ 
+-        try {
+-            CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]);
+-            CertRequestInfos cri = certCLI.certClient.enrollRequest(erd);
+-            MainCLI.printMessage("Submitted certificate request");
+-            CertCLI.printCertRequestInfos(cri);
+-
+-        } catch (FileNotFoundException e) {
+-            System.err.println("Error: " + e.getMessage());
++        if (requestFilename != null && profileID != null) {
++            System.err.println("Error: Request file and profile ID are mutually exclusive.");
++            printHelp();
+             System.exit(-1);
++        }
+ 
+-        } catch (JAXBException e) {
+-            System.err.println("Error: " + e.getMessage());
+-            System.exit(-1);
++        String requestType = cmd.getOptionValue("request-type");
++
++        CertEnrollmentRequest request;
++        if (requestFilename == null) { // if no request file specified, generate new request from profile
++
++            if (verbose) {
++                System.out.println("Retrieving " + profileID + " profile.");
++            }
++
++            request = certCLI.certClient.getEnrollmentTemplate(profileID);
++
++            // set default request type for new request
++            if (requestType == null) requestType = "pkcs10";
++
++        } else { // otherwise, load request from file
++
++            if (verbose) {
++                System.out.println("Loading request from " + requestFilename + ".");
++            }
++
++            String xml = loadFile(requestFilename);
++            request = CertEnrollmentRequest.fromXML(xml);
++        }
++
++        if (requestType != null) {
++
++            if (verbose) {
++                System.out.println("Request type: " + requestType);
++            }
++
++            for (ProfileInput input : request.getInputs()) {
++                ProfileAttribute typeAttr = input.getAttribute("cert_request_type");
++                if (typeAttr != null) {
++                    typeAttr.setValue(requestType);
++                }
++            }
++        }
++
++        String csrFilename = cmd.getOptionValue("csr-file");
++        if (csrFilename != null) {
++
++            String csr = loadFile(csrFilename);
++
++            if (verbose) {
++                System.out.println("CSR:");
++                System.out.println(csr);
++            }
++
++            for (ProfileInput input : request.getInputs()) {
++                ProfileAttribute csrAttr = input.getAttribute("cert_request");
++                if (csrAttr != null) {
++                    csrAttr.setValue(csr);
++                }
++            }
++        }
++
++        String subjectDN = cmd.getOptionValue("subject");
++        if (subjectDN != null) {
++            DN dn = new DN(subjectDN);
++            Vector<?> rdns = dn.getRDNs();
++
++            Map<String, String> subjectAttributes = new HashMap<String, String>();
++            for (int i=0; i< rdns.size(); i++) {
++                RDN rdn = (RDN)rdns.elementAt(i);
++                String type = rdn.getTypes()[0].toLowerCase();
++                String value = rdn.getValues()[0];
++                subjectAttributes.put(type, value);
++            }
++
++            ProfileInput sn = request.getInput("Subject Name");
++            if (sn != null) {
++                if (verbose) System.out.println("Subject Name:");
++
++                for (ProfileAttribute attribute : sn.getAttributes()) {
++                    String name = attribute.getName();
++                    String value = null;
++
++                    if (name.equals("subject")) {
++                        // get the whole subject DN
++                        value = subjectDN;
++
++                    } else if (name.startsWith("sn_")) {
++                        // get value from subject DN
++                        value = subjectAttributes.get(name.substring(3));
++
++                    } else {
++                        // unknown attribute, ignore
++                        if (verbose) System.out.println(" - " + name);
++                        continue;
++                    }
++
++                    if (value == null) continue;
++
++                    if (verbose) System.out.println(" - " + name + ": " + value);
++                    attribute.setValue(value);
++                }
++            }
+         }
+     }
+ 
+-    private CertEnrollmentRequest getEnrollmentRequest(String fileName) throws JAXBException, FileNotFoundException {
++    private String loadFile(String fileName) throws FileNotFoundException {
+         try (Scanner scanner = new Scanner(new File(fileName))) {
+-            String xml = scanner.useDelimiter("\\A").next();
+-            return CertEnrollmentRequest.fromXML(xml);
++            return scanner.useDelimiter("\\A").next();
+         }
+     }
+ }
+diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+index d99929f20996f1aa7ddbf21d29dfe7b54905354d..f092eacb1b5d0b340dcac06b20793334bf9baa70 100644
+--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
++++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+@@ -127,6 +127,7 @@ import com.netscape.certsrv.base.EBaseException;
+ import com.netscape.certsrv.base.EPropertyNotFound;
+ import com.netscape.certsrv.base.IConfigStore;
+ import com.netscape.certsrv.base.ISubsystem;
++import com.netscape.certsrv.base.MetaInfo;
+ import com.netscape.certsrv.base.PKIException;
+ import com.netscape.certsrv.base.ResourceNotFoundException;
+ import com.netscape.certsrv.ca.ICertificateAuthority;
+@@ -134,6 +135,8 @@ import com.netscape.certsrv.client.ClientConfig;
+ import com.netscape.certsrv.client.PKIClient;
+ import com.netscape.certsrv.client.PKIConnection;
+ import com.netscape.certsrv.dbs.IDBSubsystem;
++import com.netscape.certsrv.dbs.certdb.ICertRecord;
++import com.netscape.certsrv.dbs.certdb.ICertificateRepository;
+ import com.netscape.certsrv.dbs.crldb.ICRLIssuingPointRecord;
+ import com.netscape.certsrv.key.KeyData;
+ import com.netscape.certsrv.ldap.ILdapConnFactory;
+@@ -2270,6 +2273,54 @@ public class ConfigurationUtils {
+         certObj.setCertChain(certChainStr);
+     }
+ 
++    public static KeyPair loadKeyPair(String nickname) throws Exception {
++
++        CMS.debug("ConfigurationUtils: loadKeyPair(" + nickname + ")");
++
++        CryptoManager cm = CryptoManager.getInstance();
++
++        X509Certificate cert = cm.findCertByNickname(nickname);
++        PublicKey publicKey = cert.getPublicKey();
++        PrivateKey privateKey = cm.findPrivKeyByCert(cert);
++
++        return new KeyPair(publicKey, privateKey);
++    }
++
++    public static void storeKeyPair(IConfigStore config, String tag, KeyPair pair)
++            throws TokenException, EBaseException {
++
++        CMS.debug("ConfigurationUtils: storeKeyPair(" + tag + ")");
++
++        PublicKey publicKey = pair.getPublic();
++
++        if (publicKey instanceof RSAPublicKey) {
++
++            RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
++
++            byte modulus[] = rsaPublicKey.getModulus().toByteArray();
++            config.putString(PCERT_PREFIX + tag + ".pubkey.modulus",
++                    CryptoUtil.byte2string(modulus));
++
++            byte exponent[] = rsaPublicKey.getPublicExponent().toByteArray();
++            config.putString(PCERT_PREFIX + tag + ".pubkey.exponent",
++                    CryptoUtil.byte2string(exponent));
++
++        } else { // ECC
++
++            CMS.debug("ConfigurationUtils: Public key class: " + publicKey.getClass().getName());
++            byte encoded[] = publicKey.getEncoded();
++            config.putString(PCERT_PREFIX + tag + ".pubkey.encoded", CryptoUtil.byte2string(encoded));
++        }
++
++        PrivateKey privateKey = (PrivateKey) pair.getPrivate();
++        byte id[] = privateKey.getUniqueID();
++        String kid = CryptoUtil.byte2string(id);
++        config.putString(PCERT_PREFIX + tag + ".privkey.id", kid);
++
++        String keyAlgo = config.getString(PCERT_PREFIX + tag + ".signingalgorithm");
++        setSigningAlgorithm(tag, keyAlgo, config);
++    }
++
+     public static void createECCKeyPair(String token, String curveName, IConfigStore config, String ct)
+             throws NoSuchAlgorithmException, NoSuchTokenException, TokenException,
+             CryptoManager.NotInitializedException, EPropertyNotFound, EBaseException {
+@@ -2832,6 +2883,20 @@ public class ConfigurationUtils {
+         }
+     }
+ 
++    public static void loadCertRequest(IConfigStore config, String tag, Cert cert) throws Exception {
++
++        CMS.debug("ConfigurationUtils.loadCertRequest(" + tag + ")");
++
++        String subjectDN = config.getString(PCERT_PREFIX + tag + ".dn");
++        cert.setDN(subjectDN);
++
++        String subsystem = config.getString(PCERT_PREFIX + tag + ".subsystem");
++        String certreq = config.getString(subsystem + "." + tag + ".certreq");
++        String formattedCertreq = CryptoUtil.reqFormat(certreq);
++
++        cert.setRequest(formattedCertreq);
++    }
++
+     public static void handleCertRequest(IConfigStore config, String certTag, Cert cert) throws EPropertyNotFound,
+             EBaseException, InvalidKeyException, NotInitializedException, TokenException, NoSuchAlgorithmException,
+             NoSuchProviderException, CertificateException, SignatureException, IOException {
+@@ -2973,6 +3038,42 @@ public class ConfigurationUtils {
+         return pubk;
+     }
+ 
++    public static void loadCert(IConfigStore config, Cert cert) throws Exception {
++
++        String tag = cert.getCertTag();
++        CMS.debug("ConfigurationUtils: loadCert(" + tag + ")");
++
++        CryptoManager cm = CryptoManager.getInstance();
++        X509Certificate x509Cert = cm.findCertByNickname(cert.getNickname());
++
++        if (!x509Cert.getSubjectDN().equals(x509Cert.getIssuerDN())) {
++            CMS.debug("ConfigurationUtils: " + tag + " cert is not self-signed");
++
++            String subsystem = config.getString(PCERT_PREFIX + tag + ".subsystem");
++            String certChain = config.getString(subsystem + ".external_ca_chain.cert");
++            cert.setCertChain(certChain);
++
++            return;
++        }
++
++        CMS.debug("ConfigurationUtils: " + tag + " cert is self-signed");
++
++        // When importing existing self-signed CA certificate, create a
++        // certificate record to reserve the serial number. Otherwise it
++        // might conflict with system certificates to be created later.
++
++        X509CertImpl x509CertImpl = new X509CertImpl(x509Cert.getEncoded());
++
++        ICertificateAuthority ca = (ICertificateAuthority) CMS.getSubsystem(ICertificateAuthority.ID);
++        ICertificateRepository cr = ca.getCertificateRepository();
++
++        BigInteger serialNo = x509Cert.getSerialNumber();
++        MetaInfo meta = new MetaInfo();
++
++        ICertRecord record = cr.createCertRecord(serialNo, x509CertImpl, meta);
++        cr.addCertificateRecord(record);
++    }
++
+     public static int handleCerts(Cert cert) throws IOException, EBaseException, CertificateException,
+             NotInitializedException, TokenException, InvalidKeyException {
+         String certTag = cert.getCertTag();
+diff --git a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
+index e7a99601b473aca6a466873d4a776044d5e16e22..45ab5feaeb00c727157b3ac69a93059eee50c83b 100644
+--- a/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
++++ b/base/server/cms/src/org/dogtagpki/server/rest/SystemConfigService.java
+@@ -20,6 +20,7 @@ package org.dogtagpki.server.rest;
+ import java.math.BigInteger;
+ import java.net.MalformedURLException;
+ import java.net.URL;
++import java.security.KeyPair;
+ import java.security.NoSuchAlgorithmException;
+ import java.security.PublicKey;
+ import java.util.ArrayList;
+@@ -421,7 +422,13 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
+                 }
+                 cs.commit(false);
+ 
+-                if (!request.getStepTwo()) {
++                if (request.isExternal() && tag.equals("signing")) { // external/existing CA
++                    // load key pair for existing and externally-signed signing cert
++                    CMS.debug("SystemConfigService: loading signing cert key pair");
++                    KeyPair pair = ConfigurationUtils.loadKeyPair(certData.getNickname());
++                    ConfigurationUtils.storeKeyPair(cs, tag, pair);
++
++                } else if (!request.getStepTwo()) {
+                     if (keytype.equals("ecc")) {
+                         String curvename = certData.getKeyCurveName() != null ?
+                                 certData.getKeyCurveName() : cs.getString("keys.ecc.curve.default");
+@@ -444,7 +451,15 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
+                 cert.setSubsystem(cs.getString("preop.cert." + tag + ".subsystem"));
+                 cert.setType(cs.getString("preop.cert." + tag + ".type"));
+ 
+-                if (!request.getStepTwo()) {
++                if (request.isExternal() && tag.equals("signing")) { // external/existing CA
++
++                    // update configuration for existing or externally-signed signing certificate
++                    String certStr = cs.getString("ca." + tag + ".cert" );
++                    cert.setCert(certStr);
++                    CMS.debug("SystemConfigService: certificate " + tag + ": " + certStr);
++                    ConfigurationUtils.updateConfig(cs, tag);
++
++                } else if (!request.getStepTwo()) {
+                     ConfigurationUtils.configCert(null, null, null, cert, null);
+ 
+                 } else {
+@@ -466,8 +481,16 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
+                     CMS.debug("Step 2:  certStr for '" + tag + "' is " + certStr);
+                 }
+ 
+-                // Handle Cert Requests for everything EXCEPT Stand-alone PKI (Step 2)
+-                if (request.getStandAlone()) {
++                if (request.isExternal() && tag.equals("signing")) { // external/existing CA
++
++                    CMS.debug("SystemConfigService: Loading cert request for " + tag + " cert");
++                    ConfigurationUtils.loadCertRequest(cs, tag, cert);
++
++                    CMS.debug("SystemConfigService: Loading cert " + tag);
++                    ConfigurationUtils.loadCert(cs, cert);
++
++                } else if (request.getStandAlone()) {
++                    // Handle Cert Requests for everything EXCEPT Stand-alone PKI (Step 2)
+                     if (!request.getStepTwo()) {
+                         // Stand-alone PKI (Step 1)
+                         ConfigurationUtils.handleCertRequest(cs, tag, cert);
+@@ -490,6 +513,13 @@ public class SystemConfigService extends PKIService implements SystemConfigResou
+                     ConfigurationUtils.updateCloneConfig();
+                 }
+ 
++                if (request.isExternal() && tag.equals("signing")) { // external/existing CA
++                    CMS.debug("SystemConfigService: External CA has signing cert");
++                    hasSigningCert.setValue(true);
++                    certs.add(cert);
++                    continue;
++                }
++
+                 // to determine if we have the signing cert when using an external ca
+                 // this will only execute on a ca or stand-alone pki
+                 String b64 = certData.getCert();
+diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg
+index 58f338692ac4f1c2637b575e27db2bec6254905b..c6b9686db9b2e0245f99db36c34188716e827d83 100644
+--- a/base/server/etc/default.cfg
++++ b/base/server/etc/default.cfg
+@@ -22,6 +22,7 @@ sensitive_parameters=
+     pki_client_pkcs12_password
+     pki_clone_pkcs12_password
+     pki_ds_password
++    pki_external_pkcs12_password
+     pki_one_time_pin
+     pki_pin
+     pki_replication_password
+@@ -363,10 +364,13 @@ pki_req_ext_add=False
+ pki_req_ext_oid=1.3.6.1.4.1.311.20.2
+ pki_req_ext_critical=False
+ pki_req_ext_data=1E0A00530075006200430041
+-pki_external_csr_path=%(pki_instance_configuration_path)s/ca_signing.csr
++pki_external_csr_path=
+ pki_external_step_two=False
+-pki_external_ca_cert_chain_path=%(pki_instance_configuration_path)s/external_ca_chain.cert
+-pki_external_ca_cert_path=%(pki_instance_configuration_path)s/external_ca.cert
++pki_external_ca_cert_chain_path=
++pki_external_ca_cert_chain_nickname=caSigningCert External CA
++pki_external_ca_cert_path=
++pki_external_pkcs12_path=
++pki_external_pkcs12_password=
+ pki_import_admin_cert=False
+ pki_ocsp_signing_key_algorithm=SHA256withRSA
+ pki_ocsp_signing_key_size=2048
+diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py
+index 89d4acfd5a36d5e58b6e1bbacb4d267d6f1e20c8..4493b59df00adc0dad9b6808fb10c1769634335c 100644
+--- a/base/server/python/pki/server/__init__.py
++++ b/base/server/python/pki/server/__init__.py
+@@ -33,6 +33,7 @@ import subprocess
+ import tempfile
+ 
+ import pki
++import pki.nssdb
+ 
+ INSTANCE_BASE_DIR = '/var/lib/pki'
+ REGISTRY_DIR = '/etc/sysconfig/pki'
+@@ -303,6 +304,12 @@ class PKIInstance(object):
+ 
+         return password
+ 
++    def open_nssdb(self, token='internal'):
++        return pki.nssdb.NSSDatabase(
++            directory=self.nssdb_dir,
++            token=token,
++            password=self.get_password(token))
++
+     def get_subsystem(self, name):
+         for subsystem in self.subsystems:
+             if name == subsystem.name:
+diff --git a/base/server/python/pki/server/cli/subsystem.py b/base/server/python/pki/server/cli/subsystem.py
+index 19db203c0d8a09817131abaabd4e3ac26e288e95..9f82dff756bf98b0a14783fdd48c25d9d406f50b 100644
+--- a/base/server/python/pki/server/cli/subsystem.py
++++ b/base/server/python/pki/server/cli/subsystem.py
+@@ -23,11 +23,13 @@ from __future__ import absolute_import
+ from __future__ import print_function
+ import base64
+ import getopt
++import getpass
+ import nss.nss as nss
+ import string
+ import sys
+ 
+ import pki.cli
++import pki.nssdb
+ import pki.server
+ 
+ 
+@@ -294,6 +296,7 @@ class SubsystemCertCLI(pki.cli.CLI):
+ 
+         self.add_module(SubsystemCertFindCLI())
+         self.add_module(SubsystemCertShowCLI())
++        self.add_module(SubsystemCertExportCLI())
+         self.add_module(SubsystemCertUpdateCLI())
+ 
+     @staticmethod
+@@ -438,6 +441,129 @@ class SubsystemCertShowCLI(pki.cli.CLI):
+         SubsystemCertCLI.print_subsystem_cert(subsystem_cert)
+ 
+ 
++class SubsystemCertExportCLI(pki.cli.CLI):
++
++    def __init__(self):
++        super(SubsystemCertExportCLI, self).__init__(
++            'export', 'Export subsystem certificate')
++
++    def usage(self):
++        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.')
++        print('      --csr-file <path>              Output file to store the exported CSR in PEM format.')
++        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=', 'cert-file=', 'csr-file=',
++                'pkcs12-file=', 'pkcs12-password=', 'pkcs12-password-file=',
++                'verbose', 'help'])
++
++        except getopt.GetoptError as e:
++            print('ERROR: ' + str(e))
++            self.usage()
++            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()
++            sys.exit(1)
++
++        subsystem_name = args[0]
++        cert_id = args[1]
++        instance_name = 'pki-tomcat'
++        cert_file = None
++        csr_file = None
++        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 == '--cert-file':
++                cert_file = a
++
++            elif o == '--csr-file':
++                csr_file = 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.usage()
++                sys.exit(1)
++
++        if not cert_file and not csr_file and not pkcs12_file:
++            print('ERROR: missing output file')
++            self.usage()
++            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)
++
++        if cert_file:
++
++            cert_data = pki.nssdb.convert_cert(subsystem_cert['data'], 'base64', 'pem')
++            with open(cert_file, 'w') as f:
++                f.write(cert_data)
++
++        if csr_file:
++
++            csr_data = pki.nssdb.convert_csr(subsystem_cert['request'], 'base64', 'pem')
++            with open(csr_file, 'w') as f:
++                f.write(csr_data)
++
++        if pkcs12_file:
++
++            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,
++                    nickname=subsystem_cert['nickname'],
++                    pkcs12_password=pkcs12_password,
++                    pkcs12_password_file=pkcs12_password_file)
++            finally:
++                nssdb.close()
++
++        self.print_message('Exported %s certificate' % cert_id)
++
++
+ class SubsystemCertUpdateCLI(pki.cli.CLI):
+ 
+     def __init__(self):
+diff --git a/base/server/python/pki/server/deployment/pkihelper.py b/base/server/python/pki/server/deployment/pkihelper.py
+index 5bc4ffab814891aadf0508d0ae20bb8cc315bc90..fcef5a48bbd72eecf5e04975fc7c2401ab41b22c 100644
+--- a/base/server/python/pki/server/deployment/pkihelper.py
++++ b/base/server/python/pki/server/deployment/pkihelper.py
+@@ -490,15 +490,18 @@ class ConfigurationFile:
+         # generic extension support in CSR - for external CA
+         self.add_req_ext = config.str2bool(
+             self.mdict['pki_req_ext_add'])
++
+         self.external = config.str2bool(self.mdict['pki_external'])
++        self.external_step_one = not config.str2bool(self.mdict['pki_external_step_two'])
++        self.external_step_two = not self.external_step_one
++
+         if self.external:
+             # generic extension support in CSR - for external CA
+             if self.add_req_ext:
+                 self.req_ext_oid = self.mdict['pki_req_ext_oid']
+                 self.req_ext_critical = self.mdict['pki_req_ext_critical']
+                 self.req_ext_data = self.mdict['pki_req_ext_data']
+-        self.external_step_two = config.str2bool(
+-            self.mdict['pki_external_step_two'])
++
+         self.skip_configuration = config.str2bool(
+             self.mdict['pki_skip_configuration'])
+         self.standalone = config.str2bool(self.mdict['pki_standalone'])
+@@ -744,8 +747,7 @@ class ConfigurationFile:
+             # External CA
+             if not self.external_step_two:
+                 # External CA (Step 1)
+-                self.confirm_data_exists("pki_external_csr_path")
+-                self.confirm_missing_file("pki_external_csr_path")
++                # The pki_external_csr_path is optional.
+                 # generic extension support in CSR - for external CA
+                 if self.add_req_ext:
+                     self.confirm_data_exists("pki_req_ext_oid")
+@@ -753,10 +755,9 @@ class ConfigurationFile:
+                     self.confirm_data_exists("pki_req_ext_data")
+             else:
+                 # External CA (Step 2)
+-                self.confirm_data_exists("pki_external_ca_cert_chain_path")
+-                self.confirm_file_exists("pki_external_ca_cert_chain_path")
+-                self.confirm_data_exists("pki_external_ca_cert_path")
+-                self.confirm_file_exists("pki_external_ca_cert_path")
++                # The pki_external_ca_cert_chain_path and
++                # pki_external_ca_cert_path are optional.
++                pass
+         elif not self.skip_configuration and self.standalone:
+             if not self.external_step_two:
+                 # Stand-alone PKI Admin CSR (Step 1)
+@@ -3813,17 +3814,7 @@ class ConfigClient:
+             if not isinstance(certs, types.ListType):
+                 certs = [certs]
+             for cdata in certs:
+-                if (self.subsystem == "CA" and self.external and
+-                        not self.external_step_two):
+-                    # External CA (Step 1)
+-                    if cdata['tag'].lower() == "signing":
+-                        # Save 'External CA Signing Certificate' CSR (Step 1)
+-                        self.save_system_csr(
+-                            cdata['request'],
+-                            log.PKI_CONFIG_EXTERNAL_CSR_SAVE,
+-                            self.mdict['pki_external_csr_path'])
+-                        return
+-                elif self.standalone and not self.external_step_two:
++                if self.standalone and not self.external_step_two:
+                     # Stand-alone PKI (Step 1)
+                     if cdata['tag'].lower() == "audit_signing":
+                         # Save Stand-alone PKI 'Audit Signing Certificate' CSR
+@@ -3990,8 +3981,17 @@ class ConfigClient:
+             data.token = self.mdict['pki_token_name']
+             data.tokenPassword = self.mdict['pki_token_password']
+         data.subsystemName = self.mdict['pki_subsystem_name']
++
++        data.external = self.external
+         data.standAlone = self.standalone
+-        data.stepTwo = self.external_step_two
++
++        if self.standalone:
++            # standalone installation uses two-step process (ticket #1698)
++            data.stepTwo = self.external_step_two
++
++        else:
++            # other installations use only one step in the configuration servlet
++            data.stepTwo = False
+ 
+         # Cloning parameters
+         if self.mdict['pki_instance_type'] == "Tomcat":
+@@ -4119,25 +4119,46 @@ class ConfigClient:
+                             self.mdict['pki_req_ext_critical']
+                         cert1.req_ext_data = \
+                             self.mdict['pki_req_ext_data']
+-                if self.external_step_two:
+-                    # External CA (Step 2) or Stand-alone PKI (Step 2)
+-                    if not self.subsystem == "CA":
+-                        # Stand-alone PKI (Step 2)
+-                        cert1 = pki.system.SystemCertData()
+-                        cert1.tag = self.mdict['pki_ca_signing_tag']
+-                    # Load the External CA or Stand-alone PKI
++
++                if self.external and self.external_step_two: # external/existing CA step 2
++
++                    # If specified, load the externally-signed CA cert
++                    if self.mdict['pki_external_ca_cert_path']:
++                        self.load_system_cert(
++                            cert1,
++                            log.PKI_CONFIG_EXTERNAL_CA_LOAD,
++                            self.mdict['pki_external_ca_cert_path'])
++
++                    # If specified, load the external CA cert chain
++                    if self.mdict['pki_external_ca_cert_chain_path']:
++                        self.load_system_cert_chain(
++                            cert1,
++                            log.PKI_CONFIG_EXTERNAL_CA_CHAIN_LOAD,
++                            self.mdict['pki_external_ca_cert_chain_path'])
++
++                    systemCerts.append(cert1)
++
++                elif self.standalone and self.external_step_two: # standalone KRA/OCSP step 2
++
++                    cert1 = pki.system.SystemCertData()
++                    cert1.tag = self.mdict['pki_ca_signing_tag']
++
++                    # Load the stand-alone PKI
+                     # 'External CA Signing Certificate' (Step 2)
+                     self.load_system_cert(
+                         cert1,
+                         log.PKI_CONFIG_EXTERNAL_CA_LOAD,
+                         self.mdict['pki_external_ca_cert_path'])
+-                    # Load the External CA or Stand-alone PKI
++
++                    # Load the stand-alone PKI
+                     # 'External CA Signing Certificate Chain' (Step 2)
+                     self.load_system_cert_chain(
+                         cert1,
+                         log.PKI_CONFIG_EXTERNAL_CA_CHAIN_LOAD,
+                         self.mdict['pki_external_ca_cert_chain_path'])
++
+                     systemCerts.append(cert1)
++
+                 elif self.subsystem == "CA":
+                     # PKI CA or Subordinate CA
+                     systemCerts.append(cert1)
+diff --git a/base/server/python/pki/server/deployment/scriptlets/configuration.py b/base/server/python/pki/server/deployment/scriptlets/configuration.py
+index fbcb1ccaa21001ec57f0fd9e7ffb00e062349ecc..e7b257f7d3eb1178827ed2ef1e211e4a21455d1b 100644
+--- a/base/server/python/pki/server/deployment/scriptlets/configuration.py
++++ b/base/server/python/pki/server/deployment/scriptlets/configuration.py
+@@ -20,13 +20,18 @@
+ #
+ 
+ import json
++import re
+ 
+ # PKI Deployment Imports
+ from .. import pkiconfig as config
+ from .. import pkimessages as log
+ from .. import pkiscriptlet
+-import pki.system
++
+ import pki.encoder
++import pki.nssdb
++import pki.server
++import pki.system
++import pki.util
+ 
+ 
+ # PKI Deployment Configuration Scriptlet
+@@ -80,6 +85,131 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
+             deployer.mdict['pki_client_secmod_database'],
+             password_file=deployer.mdict['pki_client_password_conf'])
+ 
++        instance = pki.server.PKIInstance(deployer.mdict['pki_instance_name'])
++        instance.load()
++
++        subsystem = instance.get_subsystem(deployer.mdict['pki_subsystem'].lower())
++
++        token = deployer.mdict['pki_token_name']
++        nssdb = instance.open_nssdb(token)
++
++        external = deployer.configuration_file.external
++        step_one = deployer.configuration_file.external_step_one
++        step_two = deployer.configuration_file.external_step_two
++
++        try:
++            if external and step_one: # external/existing CA step 1
++
++                key_type = deployer.mdict['pki_ca_signing_key_type']
++                key_alg = deployer.mdict['pki_ca_signing_key_algorithm']
++
++                if key_type == 'rsa':
++                    key_size = int(deployer.mdict['pki_ca_signing_key_size'])
++                    curve = None
++
++                    m = re.match(r'(.*)withRSA', key_alg)
++                    if not m:
++                        raise Exception('Invalid key algorithm: %s' % key_alg)
++                    hash_alg = m.group(1)
++
++                elif key_type == 'ec' or key_type == 'ecc':
++                    key_type = 'ec'
++                    key_size = None
++                    curve = deployer.mdict['pki_ca_signing_key_size']
++
++                    m = re.match(r'(.*)withEC', key_alg)
++                    if not m:
++                        raise Exception('Invalid key algorithm: %s' % key_alg)
++                    hash_alg = m.group(1)
++
++                else:
++                    raise Exception('Invalid key type: %s' % key_type)
++
++                # If filename specified, generate CA cert request and
++                # import it into CS.cfg.
++                external_csr_path = deployer.mdict['pki_external_csr_path']
++                if external_csr_path:
++                    nssdb.create_request(
++                        subject_dn=deployer.mdict['pki_ca_signing_subject_dn'],
++                        request_file=external_csr_path,
++                        key_type=key_type,
++                        key_size=key_size,
++                        curve=curve,
++                        hash_alg=hash_alg)
++                    with open(external_csr_path) as f:
++                        signing_csr = f.read()
++                    signing_csr = pki.nssdb.convert_csr(signing_csr, 'pem', 'base64')
++                    subsystem.config['ca.signing.certreq'] = signing_csr
++
++                # This is needed by IPA to detect step 1 completion.
++                # See is_step_one_done() in ipaserver/install/cainstance.py.
++                subsystem.config['preop.ca.type'] = 'otherca'
++
++                subsystem.save()
++
++            elif external and step_two: # external/existing CA step 2
++
++                # If specified, import existing CA cert request into CS.cfg.
++                external_csr_path = deployer.mdict['pki_external_csr_path']
++                if external_csr_path:
++                    with open(external_csr_path) as f:
++                        signing_csr = f.read()
++                    signing_csr = pki.nssdb.convert_csr(signing_csr, 'pem', 'base64')
++                    subsystem.config['ca.signing.certreq'] = signing_csr
++
++                # If specified, import external CA cert into NSS database.
++                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(
++                        nickname=external_ca_cert_chain_nickname,
++                        cert_chain_file=external_ca_cert_chain_file,
++                        trust_attributes='CT,C,C')
++                    subsystem.config['ca.external_ca_chain.cert'] = cert_chain
++
++                # If specified, import externally-signed CA cert into NSS database.
++                signing_nickname = deployer.mdict['pki_ca_signing_nickname']
++                signing_cert_file = deployer.mdict['pki_external_ca_cert_path']
++                if signing_cert_file:
++                    nssdb.add_cert(
++                        nickname=signing_nickname,
++                        cert_file=signing_cert_file,
++                        trust_attributes='CT,C,C')
++
++                # If specified, import CA cert and key from PKCS #12 file into NSS database.
++                pkcs12_file = deployer.mdict['pki_external_pkcs12_path']
++                if pkcs12_file:
++                    pkcs12_password = deployer.mdict['pki_external_pkcs12_password']
++                    nssdb.import_pkcs12(pkcs12_file, pkcs12_password)
++
++                # Export CA cert from NSS database and import it into CS.cfg.
++                signing_cert_data = nssdb.get_cert(
++                    nickname=signing_nickname,
++                    output_format='base64')
++                subsystem.config['ca.signing.nickname'] = signing_nickname
++                subsystem.config['ca.signing.tokenname'] = deployer.mdict['pki_ca_signing_token']
++                subsystem.config['ca.signing.cert'] = signing_cert_data
++                subsystem.config['ca.signing.cacertnickname'] = signing_nickname
++                subsystem.config['ca.signing.defaultSigningAlgorithm'] = deployer.mdict['pki_ca_signing_signing_algorithm']
++
++                subsystem.save()
++
++            else: # self-signed CA
++
++                # To be implemented in ticket #1692.
++
++                # Generate CA cert request.
++                # Self sign CA cert.
++                # Import self-signed CA cert into NSS database.
++
++                pass
++
++        finally:
++            nssdb.close()
++
++        if external and step_one:
++            return self.rv
++
+         # Start/Restart this Tomcat PKI Process
+         # Optionally prepare to enable a java debugger
+         # (e. g. - 'eclipse'):
+diff --git a/base/server/python/pki/server/deployment/scriptlets/finalization.py b/base/server/python/pki/server/deployment/scriptlets/finalization.py
+index b92965929ffa845ce10a667ff412e4dbcaf2393c..4c98cc4996e74083d1d240913dbaea7951cc593f 100644
+--- a/base/server/python/pki/server/deployment/scriptlets/finalization.py
++++ b/base/server/python/pki/server/deployment/scriptlets/finalization.py
+@@ -65,9 +65,15 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
+         if len(deployer.instance.tomcat_instance_subsystems()) == 1:
+             # Modify contents of 'serverCertNick.conf' (if necessary)
+             deployer.servercertnick_conf.modify()
+-        # Optionally, programmatically 'restart' the configured PKI instance
+-        if config.str2bool(deployer.mdict['pki_restart_configured_instance']):
+-            deployer.systemd.restart()
++
++        external = config.str2bool(deployer.mdict['pki_external'])
++        step_one = not config.str2bool(deployer.mdict['pki_external_step_two'])
++
++        if not (external and step_one):
++            # Optionally, programmatically 'restart' the configured PKI instance
++            if config.str2bool(deployer.mdict['pki_restart_configured_instance']):
++                deployer.systemd.restart()
++
+         # Optionally, 'purge' the entire temporary client infrastructure
+         # including the client NSS security databases and password files
+         #
+diff --git a/base/server/sbin/pkispawn b/base/server/sbin/pkispawn
+index bebbf0b771a8a5b24347ffe5f97e0ce713a53e98..6b51acf920ec93c49c69dad8cc34e26ed352fb70 100755
+--- a/base/server/sbin/pkispawn
++++ b/base/server/sbin/pkispawn
+@@ -611,7 +611,17 @@ def main(argv):
+     config.pki_log.debug(pkilogging.log_format(parser.mdict),
+                          extra=config.PKI_INDENTATION_LEVEL_0)
+ 
+-    print_install_information(parser.mdict)
++    external = deployer.configuration_file.external
++    step_one = deployer.configuration_file.external_step_one
++
++    if external and step_one:
++        external_csr_path = deployer.mdict['pki_external_csr_path']
++        if external_csr_path:
++            print_external_ca_step_one_information(parser.mdict)
++        else:
++            print_existing_ca_step_one_information(parser.mdict)
++    else:
++        print_install_information(parser.mdict)
+ 
+ 
+ def set_port(parser, tag, prompt, existing_data):
+@@ -621,6 +631,33 @@ def set_port(parser, tag, prompt, existing_data):
+         parser.read_text(prompt, config.pki_subsystem, tag)
+ 
+ 
++def print_external_ca_step_one_information(mdict):
++
++    print(log.PKI_SPAWN_INFORMATION_HEADER)
++    print("      The %s subsystem of the '%s' instance is still incomplete." %
++          (config.pki_subsystem, mdict['pki_instance_name']))
++    print()
++    print("      A CSR for the CA certificate has been generated at:\n"
++          "            %s"
++          % mdict['pki_external_csr_path'])
++    print()
++    print("      Submit the CSR to an external CA to generate a CA certificate\n"
++          "      for this subsystem. Import the CA certificate and the certificate\n"
++          "      chain, then continue the installation.")
++    print(log.PKI_SPAWN_INFORMATION_FOOTER)
++
++
++def print_existing_ca_step_one_information(mdict):
++
++    print(log.PKI_SPAWN_INFORMATION_HEADER)
++    print("      The %s subsystem of the '%s' instance is still incomplete." %
++          (config.pki_subsystem, mdict['pki_instance_name']))
++    print()
++    print("      Import an existing CA certificate with the key and the CSR, and\n"
++          "      the certificate chain if available, then continue the installation.")
++    print(log.PKI_SPAWN_INFORMATION_FOOTER)
++
++
+ def print_install_information(mdict):
+ 
+     skip_configuration = config.str2bool(mdict['pki_skip_configuration'])
+-- 
+2.4.3
+
diff --git a/SOURCES/pki-core-Fix-escaping-of-password-fields-to-prevent-interpolation.patch b/SOURCES/pki-core-Fix-escaping-of-password-fields-to-prevent-interpolation.patch
new file mode 100644
index 0000000..c8aa30d
--- /dev/null
+++ b/SOURCES/pki-core-Fix-escaping-of-password-fields-to-prevent-interpolation.patch
@@ -0,0 +1,43 @@
+From f20517215d336e778c34dd96afae01fe34fc2f88 Mon Sep 17 00:00:00 2001
+From: Matthew Harmsen <mharmsen@redhat.com>
+Date: Tue, 29 Mar 2016 12:29:18 -0600
+Subject: [PATCH] Fix escaping of password fields to prevent interpolation
+
+Some password and pin fields are missing from the no_interpolation list.
+One entry is misspelled. A '%' in password field such as
+pki_clone_pkcs12_password causes an installation error.
+
+https://fedorahosted.org/pki/ticket/1703
+
+Signed-off-by: Christian Heimes <cheimes@redhat.com>
+
+Based off of 'master' check-in:  2fe50f3098aba9d604a812604cecb75279f445bf
+---
+ base/server/python/pki/server/deployment/pkiparser.py | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
+index 4b3dabb..6b4420b 100644
+--- a/base/server/python/pki/server/deployment/pkiparser.py
++++ b/base/server/python/pki/server/deployment/pkiparser.py
+@@ -330,11 +330,15 @@ class PKIConfigParser:
+                     'pki_admin_password',
+                     'pki_backup_password',
+                     'pki_client_database_password',
++                    'pki_client_pin',
+                     'pki_client_pkcs12_password',
++                    'pki_clone_pkcs12_password',
+                     'pki_ds_password',
++                    'pki_one_time_pin',
+                     'pki_pin',
+-                    'pki_replicationdb_password',
+-                    'pki_security_domain_password')
++                    'pki_replication_password',
++                    'pki_security_domain_password',
++                    'pki_token_password')
+ 
+                 print 'Loading deployment configuration from ' + \
+                       config.user_deployment_cfg + '.'
+-- 
+1.8.3.1
+
diff --git a/SOURCES/pki-core-Fix-to-determine-supported-javadoc-options.patch b/SOURCES/pki-core-Fix-to-determine-supported-javadoc-options.patch
new file mode 100644
index 0000000..c943807
--- /dev/null
+++ b/SOURCES/pki-core-Fix-to-determine-supported-javadoc-options.patch
@@ -0,0 +1,86 @@
+From db7055457d421b796c1af6f0549a746a65939a05 Mon Sep 17 00:00:00 2001
+From: Matthew Harmsen <mharmsen@redhat.com>
+Date: Thu, 18 Feb 2016 17:53:26 -0700
+Subject: [PATCH] Fix to determine supported javadoc options
+
+- PKI TRAC Ticket #2040 - Determine supported javadoc options
+---
+ base/javadoc/CMakeLists.txt | 61 +++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 59 insertions(+), 2 deletions(-)
+
+diff --git a/base/javadoc/CMakeLists.txt b/base/javadoc/CMakeLists.txt
+index fa524be..09aa410 100644
+--- a/base/javadoc/CMakeLists.txt
++++ b/base/javadoc/CMakeLists.txt
+@@ -1,9 +1,66 @@
+ project(pki-javadoc)
+ 
++# It is important to identify the version of 'javadoc' being utilized since
++# different versions support different options.
++#
++# While 'cmake' contains numerous built-in references to the 'java' version,
++# it contains no built-in references to either the 'javac' or 'javadoc'
++# versions, and unfortunately, the specified version of 'java' may be
++# different from the specified versions of 'javac' and 'javadoc'.
++#
++# Additionally, although 'javadoc' contains no command-line option to identify
++# its version, it is important to note that 'javadoc' is supplied by the same
++# package that supplies 'javac', and although multiple versions of these
++# executables could co-exist on the same system, it is relatively safe to
++# assert that the currently specified 'javac' and 'javadoc' will be the same
++# version.
++#
++# As an example in support of this assertion, on systems which utilize
++# '/usr/sbin/alternatives', setting the 'javac' version will also
++# automatically set the 'javadoc' version to match the 'javac' version, and
++# 'usr/sbin/alternatives' cannot be used to set a specific 'javadoc' version.
++#
++# Therefore, regardless of the 'java' version, this 'CMakeLists.txt' file will
++# programmatically utilize the invoked 'javac' version information (output is
++# to stderr) in order to correctly identify the supported 'javadoc' options:
++#
++#     # javac -version 2>&1 | awk -F \. '{printf $2}'
++#
++# NOTE:  Used 'cut' instead of 'awk' due to 'cmake' parsing limitations:
++#
++#     # javac -version 2>&1 | cut -f2 -d.
++#
++message( STATUS "Java_VERSION_STRING = '${Java_VERSION_STRING}'" )
++execute_process(
++    COMMAND
++        javac -version
++    ERROR_VARIABLE
++        Javac_VERSION_OUTPUT
++    OUTPUT_VARIABLE
++        Javac_VERSION_OUTPUT
++    ERROR_STRIP_TRAILING_WHITESPACE
++    OUTPUT_STRIP_TRAILING_WHITESPACE
++)
++message( STATUS "Javac_VERSION_OUTPUT = '${Javac_VERSION_OUTPUT}'" )
++execute_process(
++    COMMAND
++        echo ${Javac_VERSION_OUTPUT}
++    COMMAND
++        cut -f2 -d.
++    OUTPUT_VARIABLE
++        Javadoc_VERSION_MINOR
++    OUTPUT_STRIP_TRAILING_WHITESPACE
++)
++message( STATUS "Javadoc_VERSION_MINOR = '${Javadoc_VERSION_MINOR}'" )
++
++# REMINDER:  Eventually, it would almost certainly be safer to obtain the
++#            'Javadoc_VERSION_MAJOR' number as well and perform the check
++#            on "'Javadoc_VERSION_MAJOR'.'Javadoc_VERSION_MINOR'".
++#
+ set(doclintstr "")
+-if(${Java_VERSION_MINOR} VERSION_EQUAL 8 OR ${Java_VERSION_MINOR} VERSION_GREATER 8)
++if(NOT (${Javadoc_VERSION_MINOR} LESS 8))
+     set(doclintstr "-Xdoclint:none")
+-endif(${Java_VERSION_MINOR} VERSION_EQUAL 8 OR ${Java_VERSION_MINOR} VERSION_GREATER 8)
++endif(NOT (${Javadoc_VERSION_MINOR} LESS 8))
+ 
+ javadoc(pki-javadoc
+     SOURCEPATH
+-- 
+1.8.3.1
+
diff --git a/SOURCES/pki-core-Fixed-KRA-install-problem.patch b/SOURCES/pki-core-Fixed-KRA-install-problem.patch
new file mode 100644
index 0000000..229ce67
--- /dev/null
+++ b/SOURCES/pki-core-Fixed-KRA-install-problem.patch
@@ -0,0 +1,129 @@
+From 78d42fcb8def1c21dc9a82251b760ab1b7a23f88 Mon Sep 17 00:00:00 2001
+From: Matthew Harmsen <mharmsen@redhat.com>
+Date: Wed, 30 Mar 2016 15:16:06 -0600
+Subject: [PATCH] Fixed KRA install problem.
+
+Currently when installing an additional subsystem to an existing
+instance the install tool always generates a new random password in
+the pki_pin property which would not work with the existing NSS
+database. The code has been modified to load the existing NSS
+database password from the instance if the instance already exists.
+
+The PKIInstance class has been modified to allow loading partially
+created instance to help the installation.
+
+https://fedorahosted.org/pki/ticket/2247
+
+Altered from 'master' (10.3.0) so that it could be applied
+to 'DOGTAG_10_2_5_RHEL_BRANCH' (10.2.5).
+---
+ base/server/python/pki/server/__init__.py          | 54 ++++++++++++----------
+ .../python/pki/server/deployment/pkiparser.py      | 18 ++++++--
+ 2 files changed, 44 insertions(+), 28 deletions(-)
+
+diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py
+index 22b6fcf..971a3f6 100644
+--- a/base/server/python/pki/server/__init__.py
++++ b/base/server/python/pki/server/__init__.py
+@@ -413,40 +413,44 @@ class PKIInstance(object):
+ 
+     def load(self):
+         # load UID and GID
+-        with open(self.registry_file, 'r') as registry:
+-            lines = registry.readlines()
++        if os.path.exists(self.registry_file):
+ 
+-        for line in lines:
++            with open(self.registry_file, 'r') as registry:
++                lines = registry.readlines()
+ 
+-            m = re.search('^PKI_USER=(.*)$', line)
+-            if m:
+-                self.user = m.group(1)
+-                self.uid = pwd.getpwnam(self.user).pw_uid
++            for line in lines:
++                m = re.search('^PKI_USER=(.*)$', line)
++                if m:
++                    self.user = m.group(1)
++                    self.uid = pwd.getpwnam(self.user).pw_uid
+ 
+-            m = re.search('^PKI_GROUP=(.*)$', line)
+-            if m:
+-                self.group = m.group(1)
+-                self.gid = grp.getgrnam(self.group).gr_gid
++                m = re.search('^PKI_GROUP=(.*)$', line)
++                if m:
++                    self.group = m.group(1)
++                    self.gid = grp.getgrnam(self.group).gr_gid
+ 
+         # load passwords
+         self.passwords.clear()
+-        lines = open(self.password_conf).read().splitlines()
++        if os.path.exists(self.password_conf):
+ 
+-        for line in lines:
+-            parts = line.split('=', 1)
+-            name = parts[0]
+-            value = parts[1]
+-            self.passwords[name] = value
++            lines = open(self.password_conf).read().splitlines()
++
++            for line in lines:
++                parts = line.split('=', 1)
++                name = parts[0]
++                value = parts[1]
++                self.passwords[name] = value
+ 
+         # load subsystems
+-        for subsystem_name in os.listdir(self.registry_dir):
+-            if subsystem_name in SUBSYSTEM_TYPES:
+-                if subsystem_name in SUBSYSTEM_CLASSES:
+-                    subsystem = SUBSYSTEM_CLASSES[subsystem_name](self)
+-                else:
+-                    subsystem = PKISubsystem(self, subsystem_name)
+-                subsystem.load()
+-                self.subsystems.append(subsystem)
++        if os.path.exists(self.registry_dir):
++            for subsystem_name in os.listdir(self.registry_dir):
++                if subsystem_name in SUBSYSTEM_TYPES:
++                    if subsystem_name in SUBSYSTEM_CLASSES:
++                        subsystem = SUBSYSTEM_CLASSES[subsystem_name](self)
++                    else:
++                        subsystem = PKISubsystem(self, subsystem_name)
++                    subsystem.load()
++                    self.subsystems.append(subsystem)
+ 
+     def get_password(self, name):
+         if name in self.passwords:
+diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
+index 14fe519..a5aaa97 100644
+--- a/base/server/python/pki/server/deployment/pkiparser.py
++++ b/base/server/python/pki/server/deployment/pkiparser.py
+@@ -569,9 +569,21 @@ class PKIConfigParser:
+             pin_low = 100000000000
+             pin_high = 999999999999
+ 
+-            # use user-provided PIN if specified
+-            if 'pki_pin' not in self.mdict:
+-                # otherwise generate a random password
++            instance = pki.server.PKIInstance(self.mdict['pki_instance_name'])
++            instance.load()
++
++            internal_password = self.mdict['pki_self_signed_token']
++
++            # if instance already exists and has password, reuse the password
++            if internal_password in instance.passwords:
++                self.mdict['pki_pin'] = instance.passwords.get(internal_password)
++
++            # otherwise, use user-provided password if specified
++            elif 'pki_pin' in self.mdict:
++                pass
++
++            # otherwise, generate a random password
++            else:
+                 self.mdict['pki_pin'] = \
+                     random.randint(pin_low, pin_high)
+ 
+-- 
+1.8.3.1
+
diff --git a/SOURCES/pki-core-Fixed-certificate-chain-import-problem.patch b/SOURCES/pki-core-Fixed-certificate-chain-import-problem.patch
new file mode 100644
index 0000000..874bdff
--- /dev/null
+++ b/SOURCES/pki-core-Fixed-certificate-chain-import-problem.patch
@@ -0,0 +1,62 @@
+From c2d66070b05c0230d7d508458223337a0d2571c4 Mon Sep 17 00:00:00 2001
+From: "Endi S. Dewata" <edewata@redhat.com>
+Date: Fri, 25 Mar 2016 03:33:05 +0100
+Subject: [PATCH] Fixed certificate chain import problem.
+
+In the external CA case if the externally-signed CA certificate
+is included in the certificate chain the CA certificate may get
+imported with an incorrect nickname.
+
+The code has been modified such that the certificate chain is
+imported after the CA certificate is imported with the proper
+nickname.
+
+https://fedorahosted.org/pki/ticket/2022
+---
+ .../server/deployment/scriptlets/configuration.py  | 22 ++++++++++++----------
+ 1 file changed, 12 insertions(+), 10 deletions(-)
+
+diff --git a/base/server/python/pki/server/deployment/scriptlets/configuration.py b/base/server/python/pki/server/deployment/scriptlets/configuration.py
+index 54f065f094c811cdbf944a0ac14e019d0d4d6145..e344e9652fdefc97300cb78ee6f269cfd89b805e 100644
+--- a/base/server/python/pki/server/deployment/scriptlets/configuration.py
++++ b/base/server/python/pki/server/deployment/scriptlets/configuration.py
+@@ -157,17 +157,9 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
+                     signing_csr = pki.nssdb.convert_csr(signing_csr, 'pem', 'base64')
+                     subsystem.config['ca.signing.certreq'] = signing_csr
+ 
+-                # If specified, import external CA cert into NSS database.
+-                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, _nicks = nssdb.import_cert_chain(
+-                        nickname=external_ca_cert_chain_nickname,
+-                        cert_chain_file=external_ca_cert_chain_file,
+-                        trust_attributes='CT,C,C')
+-                    subsystem.config['ca.external_ca_chain.cert'] = cert_chain
+-
+                 # If specified, import externally-signed CA cert into NSS database.
++                # Note: CA cert must be imported before the cert chain to ensure that
++                # the CA cert is imported with the correct nickname.
+                 signing_nickname = deployer.mdict['pki_ca_signing_nickname']
+                 signing_cert_file = deployer.mdict['pki_external_ca_cert_path']
+                 if signing_cert_file:
+@@ -182,6 +174,16 @@ class PkiScriptlet(pkiscriptlet.AbstractBasePkiScriptlet):
+                     pkcs12_password = deployer.mdict['pki_external_pkcs12_password']
+                     nssdb.import_pkcs12(pkcs12_file, pkcs12_password)
+ 
++                # If specified, import cert chain into NSS database.
++                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, _nicks = nssdb.import_cert_chain(
++                        nickname=external_ca_cert_chain_nickname,
++                        cert_chain_file=external_ca_cert_chain_file,
++                        trust_attributes='CT,C,C')
++                    subsystem.config['ca.external_ca_chain.cert'] = cert_chain
++
+                 # Export CA cert from NSS database and import it into CS.cfg.
+                 signing_cert_data = nssdb.get_cert(
+                     nickname=signing_nickname,
+-- 
+2.4.3
+
diff --git a/SOURCES/pki-core-Fixed-mismatching-certificate-validity-calculation.patch b/SOURCES/pki-core-Fixed-mismatching-certificate-validity-calculation.patch
new file mode 100644
index 0000000..9a85df9
--- /dev/null
+++ b/SOURCES/pki-core-Fixed-mismatching-certificate-validity-calculation.patch
@@ -0,0 +1,180 @@
+From 8ef4f6fc86753cafef9256e8102926d6effbbb0b Mon Sep 17 00:00:00 2001
+From: "Endi S. Dewata" <edewata@redhat.com>
+Date: Sun, 20 Dec 2015 21:46:56 +0100
+Subject: [PATCH] Fixed mismatching certificate validity calculation.
+
+The CAValidityDefault has been modified to use Calendar API to
+calculate the certificate validity range to be consistent with
+the ValidityConstraint and ValidityDefault.
+
+https://fedorahosted.org/pki/ticket/1682
+---
+ .../cms/profile/def/CAValidityDefault.java         | 79 ++++++++++++++++++----
+ base/server/cmsbundle/src/UserMessages.properties  |  2 +-
+ 2 files changed, 67 insertions(+), 14 deletions(-)
+
+diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAValidityDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAValidityDefault.java
+index 44ffd474f8aa23abff922f6fc37e92cd12536dec..a98b2c28c12c78ac6ffa420c880ba0c317f5f94b 100644
+--- a/base/server/cms/src/com/netscape/cms/profile/def/CAValidityDefault.java
++++ b/base/server/cms/src/com/netscape/cms/profile/def/CAValidityDefault.java
+@@ -20,14 +20,10 @@ package com.netscape.cms.profile.def;
+ import java.io.IOException;
+ import java.text.ParsePosition;
+ import java.text.SimpleDateFormat;
++import java.util.Calendar;
+ import java.util.Date;
+ import java.util.Locale;
+ 
+-import netscape.security.x509.BasicConstraintsExtension;
+-import netscape.security.x509.CertificateValidity;
+-import netscape.security.x509.PKIXExtensions;
+-import netscape.security.x509.X509CertInfo;
+-
+ import com.netscape.certsrv.apps.CMS;
+ import com.netscape.certsrv.base.IConfigStore;
+ import com.netscape.certsrv.ca.ICertificateAuthority;
+@@ -38,6 +34,11 @@ import com.netscape.certsrv.property.EPropertyException;
+ import com.netscape.certsrv.property.IDescriptor;
+ import com.netscape.certsrv.request.IRequest;
+ 
++import netscape.security.x509.BasicConstraintsExtension;
++import netscape.security.x509.CertificateValidity;
++import netscape.security.x509.PKIXExtensions;
++import netscape.security.x509.X509CertInfo;
++
+ /**
+  * This class implements a CA signing cert enrollment default policy
+  * that populates a server-side configurable validity
+@@ -46,6 +47,7 @@ import com.netscape.certsrv.request.IRequest;
+  */
+ public class CAValidityDefault extends EnrollDefault {
+     public static final String CONFIG_RANGE = "range";
++    public static final String CONFIG_RANGE_UNIT = "rangeUnit";
+     public static final String CONFIG_START_TIME = "startTime";
+     public static final String CONFIG_BYPASS_CA_NOTAFTER = "bypassCAnotafter";
+ 
+@@ -61,6 +63,7 @@ public class CAValidityDefault extends EnrollDefault {
+     public CAValidityDefault() {
+         super();
+         addConfigName(CONFIG_RANGE);
++        addConfigName(CONFIG_RANGE_UNIT);
+         addConfigName(CONFIG_START_TIME);
+         addConfigName(CONFIG_BYPASS_CA_NOTAFTER);
+ 
+@@ -103,6 +106,12 @@ public class CAValidityDefault extends EnrollDefault {
+                     "7305", /* 20 years */
+                     CMS.getUserMessage(locale,
+                             "CMS_PROFILE_VALIDITY_RANGE"));
++        } else if (name.equals(CONFIG_RANGE_UNIT)) {
++            return new Descriptor(IDescriptor.STRING,
++                    null,
++                    "day",
++                    CMS.getUserMessage(locale,
++                            "CMS_PROFILE_VALIDITY_RANGE_UNIT"));
+         } else if (name.equals(CONFIG_START_TIME)) {
+             return new Descriptor(IDescriptor.STRING,
+                     null,
+@@ -299,6 +308,28 @@ public class CAValidityDefault extends EnrollDefault {
+         return CMS.getUserMessage(locale, "CMS_PROFILE_DEF_VALIDITY", params);
+     }
+ 
++    public int convertRangeUnit(String unit) throws Exception {
++
++        if (unit.equals("year")) {
++            return Calendar.YEAR;
++
++        } else if (unit.equals("month")) {
++            return Calendar.MONTH;
++
++        } else if (unit.equals("day")) {
++            return Calendar.DAY_OF_YEAR;
++
++        } else if (unit.equals("hour")) {
++            return Calendar.HOUR_OF_DAY;
++
++        } else if (unit.equals("minute")) {
++            return Calendar.MINUTE;
++
++        } else {
++            throw new Exception("Invalid range unit: " + unit);
++        }
++    }
++
+     /**
+      * Populates the request with this policy default.
+      */
+@@ -307,6 +338,7 @@ public class CAValidityDefault extends EnrollDefault {
+ 
+         // always + 60 seconds
+         String startTimeStr = getConfig(CONFIG_START_TIME);
++        CMS.debug("CAValidityDefault: start time: " + startTimeStr);
+         try {
+             startTimeStr = mapPattern(request, startTimeStr);
+         } catch (IOException e) {
+@@ -317,21 +349,42 @@ public class CAValidityDefault extends EnrollDefault {
+             startTimeStr = "60";
+         }
+         int startTime = Integer.parseInt(startTimeStr);
++
+         Date notBefore = new Date(CMS.getCurrentDate().getTime() + (1000 * startTime));
+-        long notAfterVal = 0;
++        CMS.debug("CAValidityDefault: not before: " + notBefore);
+ 
++        String rangeStr = getConfig(CONFIG_RANGE, "7305");
++        CMS.debug("CAValidityDefault: range: " + rangeStr);
++
++        int range;
+         try {
+-            String rangeStr = getConfig(CONFIG_RANGE);
+             rangeStr = mapPattern(request, rangeStr);
+-            notAfterVal = notBefore.getTime() +
+-                    (mDefault * Integer.parseInt(rangeStr));
+-        } catch (Exception e) {
+-            // configured value is not correct
+-            CMS.debug("CAValidityDefault: populate " + e.toString());
++            range = Integer.parseInt(rangeStr);
++        } catch (IOException e) {
++            CMS.debug(e);
+             throw new EProfileException(CMS.getUserMessage(
+                         getLocale(request), "CMS_INVALID_PROPERTY", CONFIG_RANGE));
+         }
+-        Date notAfter = new Date(notAfterVal);
++
++        String rangeUnitStr = getConfig(CONFIG_RANGE_UNIT, "day");
++        CMS.debug("CAValidityDefault: range unit: " + rangeUnitStr);
++
++        int rangeUnit;
++        try {
++            rangeUnit = convertRangeUnit(rangeUnitStr);
++        } catch (Exception e) {
++            CMS.debug(e);
++            throw new EProfileException(CMS.getUserMessage(
++                        getLocale(request), "CMS_INVALID_PROPERTY", CONFIG_RANGE_UNIT));
++        }
++
++        // calculate the end of validity range
++        Calendar date = Calendar.getInstance();
++        date.setTime(notBefore);
++        date.add(rangeUnit, range);
++
++        Date notAfter = date.getTime();
++        CMS.debug("CAValidityDefault: not after: " + notAfter);
+ 
+         CertificateValidity validity =
+                 new CertificateValidity(notBefore, notAfter);
+diff --git a/base/server/cmsbundle/src/UserMessages.properties b/base/server/cmsbundle/src/UserMessages.properties
+index 6b4dc69b5a6787309f02b0e5e79d93b1f2918f88..7c5c77d5b589886d0a8c6323436a1fdcdd4ce8f9 100644
+--- a/base/server/cmsbundle/src/UserMessages.properties
++++ b/base/server/cmsbundle/src/UserMessages.properties
+@@ -835,7 +835,7 @@ CMS_PROFILE_VALIDITY_CHECK_NOT_BEFORE=Check Not Before against current time
+ CMS_PROFILE_VALIDITY_CHECK_NOT_AFTER=Check Not After against Not Before
+ CMS_PROFILE_VALIDITY_NOT_BEFORE_GRACE_PERIOD=Grace period for Not Before being set in the future (in seconds).
+ CMS_PROFILE_VALIDITY_RANGE=Validity Range
+-CMS_PROFILE_VALIDITY_RANGE_UNIT=Validity Range Unit (default: day)
++CMS_PROFILE_VALIDITY_RANGE_UNIT=Validity Range Unit: year, month, day (default), hour, minute
+ CMS_PROFILE_VALIDITY_START_TIME=Relative Start Time (in seconds)
+ CMS_PROFILE_NOT_BEFORE_RANDOM_BITS=Not Before Random Bits
+ CMS_PROFILE_NOT_AFTER_RANDOM_BITS=Not After Random Bits
+-- 
+2.4.3
+
diff --git a/SOURCES/pki-core-Fixed-missing-trust-flags-in-certificate-backup.patch b/SOURCES/pki-core-Fixed-missing-trust-flags-in-certificate-backup.patch
new file mode 100644
index 0000000..42f6aaa
--- /dev/null
+++ b/SOURCES/pki-core-Fixed-missing-trust-flags-in-certificate-backup.patch
@@ -0,0 +1,452 @@
+From b216472ddd80f64b136b3ac367d3415b526c97d4 Mon Sep 17 00:00:00 2001
+From: Matthew Harmsen <mharmsen@redhat.com>
+Date: Fri, 1 Apr 2016 14:57:07 -0600
+Subject: [PATCH] Fixed missing trust flags in certificate backup.
+
+The ConfigurationUtils.backupKeys() has been modified to use
+PKCS12Util to export the certificates and their trust flags into
+a PKCS #12 file such that the file can be used for cloning.
+
+The code to generate PFX object has been refactored from the
+PKCS12Util.storeIntoFile() into a separate generatePFX() method.
+
+The PKCS12Util.loadCertFromNSS() has been modified to provide
+options to load a certificate from NSS database without the key
+or the certificate chain. The CLIs have been modified to provide
+the same options.
+
+The PKCS12Util.getCertInfo() has modified to ignore missing
+certificate attributes in the PKCS #12 file and generate a new
+local ID.
+
+https://fedorahosted.org/pki/ticket/2257
+---
+ base/java-tools/bin/pki                            |   3 +
+ .../netscape/cmstools/pkcs12/PKCS12CertAddCLI.java |   7 +-
+ .../netscape/cmstools/pkcs12/PKCS12ExportCLI.java  |  12 ++-
+ .../cms/servlet/csadmin/ConfigurationUtils.java    |  39 ++++----
+ .../src/netscape/security/pkcs/PKCS12Util.java     | 108 ++++++++++++---------
+ 5 files changed, 97 insertions(+), 72 deletions(-)
+
+diff --git a/base/java-tools/bin/pki b/base/java-tools/bin/pki
+index e476cfc..88490f7 100644
+--- a/base/java-tools/bin/pki
++++ b/base/java-tools/bin/pki
+@@ -138,6 +138,9 @@ class PKICLI(pki.cli.CLI):
+         if self.token and self.token != 'internal':
+             cmd.extend(['--token', self.token])
+ 
++        if self.verbose:
++            cmd.extend(['--verbose'])
++
+         cmd.extend(args)
+ 
+         if self.verbose:
+diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
+index 48e4907..a422b20 100644
+--- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
++++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertAddCLI.java
+@@ -65,6 +65,8 @@ public class PKCS12CertAddCLI extends CLI {
+ 
+         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(null, "no-key", false, "Do not include private key");
++        options.addOption(null, "no-chain", false, "Do not include certificate chain");
+ 
+         options.addOption("v", "verbose", false, "Run in verbose mode.");
+         options.addOption(null, "debug", false, "Run in debug mode.");
+@@ -139,6 +141,8 @@ public class PKCS12CertAddCLI extends CLI {
+ 
+         boolean newFile = cmd.hasOption("new-file");
+         boolean includeTrustFlags = !cmd.hasOption("no-trust-flags");
++        boolean includeKey = !cmd.hasOption("no-key");
++        boolean includeChain = !cmd.hasOption("no-chain");
+ 
+         try {
+             PKCS12Util util = new PKCS12Util();
+@@ -155,7 +159,8 @@ public class PKCS12CertAddCLI extends CLI {
+                 pkcs12 = util.loadFromFile(filename, password);
+             }
+ 
+-            util.loadCertFromNSS(pkcs12, nickname);
++            // load the specified certificate
++            util.loadCertFromNSS(pkcs12, nickname, includeKey, includeChain);
+             util.storeIntoFile(pkcs12, filename, password);
+ 
+         } finally {
+diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
+index d42c449..fab5ecd 100644
+--- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
++++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java
+@@ -63,6 +63,8 @@ public class PKCS12ExportCLI extends CLI {
+ 
+         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(null, "no-key", false, "Do not include private key");
++        options.addOption(null, "no-chain", false, "Do not include certificate chain");
+ 
+         options.addOption("v", "verbose", false, "Run in verbose mode.");
+         options.addOption(null, "debug", false, "Run in debug mode.");
+@@ -127,11 +129,13 @@ public class PKCS12ExportCLI extends CLI {
+         Password password = new Password(passwordString.toCharArray());
+ 
+         boolean newFile = cmd.hasOption("new-file");
+-        boolean trustFlagsEnabled = !cmd.hasOption("no-trust-flags");
++        boolean includeTrustFlags = !cmd.hasOption("no-trust-flags");
++        boolean includeKey = !cmd.hasOption("no-key");
++        boolean includeChain = !cmd.hasOption("no-chain");
+ 
+         try {
+             PKCS12Util util = new PKCS12Util();
+-            util.setTrustFlagsEnabled(trustFlagsEnabled);
++            util.setTrustFlagsEnabled(includeTrustFlags);
+ 
+             PKCS12 pkcs12;
+ 
+@@ -149,9 +153,9 @@ public class PKCS12ExportCLI extends CLI {
+                 util.loadFromNSS(pkcs12);
+ 
+             } else {
+-                // load specified certificates
++                // load the specified certificates
+                 for (String nickname : nicknames) {
+-                    util.loadCertFromNSS(pkcs12, nickname);
++                    util.loadCertFromNSS(pkcs12, nickname, includeKey, includeChain);
+                 }
+             }
+ 
+diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+index f092eac..713e1b0 100644
+--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
++++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+@@ -32,7 +32,6 @@ import java.net.ConnectException;
+ import java.net.URI;
+ import java.net.URISyntaxException;
+ import java.net.URLEncoder;
+-import java.security.DigestException;
+ import java.security.InvalidAlgorithmParameterException;
+ import java.security.InvalidKeyException;
+ import java.security.KeyPair;
+@@ -171,6 +170,8 @@ import netscape.ldap.LDAPSearchResults;
+ import netscape.ldap.LDAPv3;
+ import netscape.security.pkcs.ContentInfo;
+ import netscape.security.pkcs.PKCS10;
++import netscape.security.pkcs.PKCS12;
++import netscape.security.pkcs.PKCS12Util;
+ import netscape.security.pkcs.PKCS7;
+ import netscape.security.pkcs.SignerInfo;
+ import netscape.security.util.DerOutputStream;
+@@ -3352,11 +3353,8 @@ public class ConfigurationUtils {
+         }
+     }
+ 
+-    public static void backupKeys(String pwd, String fname) throws EPropertyNotFound, EBaseException,
+-            NotInitializedException, ObjectNotFoundException, TokenException, DigestException,
+-            InvalidKeyException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidBERException,
+-            CertificateEncodingException, IllegalStateException, IllegalBlockSizeException, BadPaddingException,
+-            IOException {
++    public static void backupKeys(String pwd, String fname) throws Exception {
++
+         CMS.debug("backupKeys(): start");
+         IConfigStore cs = CMS.getConfigStore();
+         String certlist = cs.getString("preop.cert.list");
+@@ -3365,9 +3363,13 @@ public class ConfigurationUtils {
+         CryptoManager cm = CryptoManager.getInstance();
+ 
+         Password pass = new org.mozilla.jss.util.Password(pwd.toCharArray());
+-        SEQUENCE encSafeContents = new SEQUENCE();
+-        SEQUENCE safeContents = new SEQUENCE();
++
++        PKCS12Util util = new PKCS12Util();
++        PKCS12 pkcs12 = new PKCS12();
++
++        // load system certificate (with key but without chain)
+         while (st.hasMoreTokens()) {
++
+             String t = st.nextToken();
+             if (t.equals("sslserver"))
+                 continue;
+@@ -3377,27 +3379,20 @@ public class ConfigurationUtils {
+             if (!modname.equals("Internal Key Storage Token"))
+                 nickname = modname + ":" + nickname;
+ 
+-            X509Certificate x509cert = cm.findCertByNickname(nickname);
+-            byte localKeyId[] = addCertBag(x509cert, nickname, safeContents);
+-            PrivateKey pkey = cm.findPrivKeyByCert(x509cert);
+-            addKeyBag(pkey, x509cert, pass, localKeyId, encSafeContents);
++            util.loadCertFromNSS(pkcs12, nickname, true, false);
+         }
+ 
+-        X509Certificate[] cacerts = cm.getCACerts();
+-
+-        for (int i = 0; i < cacerts.length; i++) {
+-            String nickname = null;
+-            addCertBag(cacerts[i], nickname, safeContents);
++        // load CA certificates (without keys or chains)
++        for (X509Certificate caCert : cm.getCACerts()) {
++            util.loadCertFromNSS(pkcs12, caCert, false, false);
+         }
+ 
+-        AuthenticatedSafes authSafes = new AuthenticatedSafes();
+-        authSafes.addSafeContents(safeContents);
+-        authSafes.addSafeContents(encSafeContents);
+-        PFX pfx = new PFX(authSafes);
+-        pfx.computeMacData(pass, null, 5);
++        PFX pfx = util.generatePFX(pkcs12, pass);
++
+         ByteArrayOutputStream bos = new ByteArrayOutputStream();
+         pfx.encode(bos);
+         byte[] output = bos.toByteArray();
++
+         cs.putString("preop.pkcs12", CryptoUtil.byte2string(output));
+         pass.clear();
+         cs.commit(false);
+diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java
+index 7c9ab2f..967479b 100644
+--- a/base/util/src/netscape/security/pkcs/PKCS12Util.java
++++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java
+@@ -162,13 +162,14 @@ public class PKCS12Util {
+     }
+ 
+     BigInteger createLocalID(X509Certificate cert) throws Exception {
+-
+         // SHA1 hash of the X509Cert DER encoding
+-        byte[] certDer = cert.getEncoded();
++        return createLocalID(cert.getEncoded());
++    }
+ 
+-        MessageDigest md = MessageDigest.getInstance("SHA");
++    BigInteger createLocalID(byte[] bytes) throws Exception {
+ 
+-        md.update(certDer);
++        MessageDigest md = MessageDigest.getInstance("SHA");
++        md.update(bytes);
+         return new BigInteger(1, md.digest());
+     }
+ 
+@@ -244,21 +245,46 @@ public class PKCS12Util {
+         CryptoStore store = token.getCryptoStore();
+ 
+         for (X509Certificate cert : store.getCertificates()) {
+-            loadCertChainFromNSS(pkcs12, cert);
++            loadCertFromNSS(pkcs12, cert, true, true);
+         }
+     }
+ 
+-    public void loadCertFromNSS(PKCS12 pkcs12, String nickname) throws Exception {
++    public void loadCertFromNSS(PKCS12 pkcs12, String nickname, boolean includeKey, boolean includeChain) throws Exception {
+ 
+         CryptoManager cm = CryptoManager.getInstance();
+ 
+         X509Certificate[] certs = cm.findCertsByNickname(nickname);
+         for (X509Certificate cert : certs) {
+-            loadCertChainFromNSS(pkcs12, cert);
++            loadCertFromNSS(pkcs12, cert, includeKey, includeChain);
+         }
+     }
+ 
+-    public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id, boolean replace) throws Exception {
++    public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, boolean includeKey, boolean includeChain) throws Exception {
++
++        CryptoManager cm = CryptoManager.getInstance();
++
++        BigInteger id = createLocalID(cert);
++
++        // load cert info
++        loadCertInfoFromNSS(pkcs12, cert, id, true);
++
++        if (includeKey) {
++            // load key info if exists
++            loadKeyInfoFromNSS(pkcs12, cert, id);
++        }
++
++        if (includeChain) {
++            // load cert chain
++            X509Certificate[] certChain = cm.buildCertificateChain(cert);
++            for (int i = 1; i < certChain.length; i++) {
++                X509Certificate c = certChain[i];
++                BigInteger cid = createLocalID(c);
++                loadCertInfoFromNSS(pkcs12, c, cid, false);
++            }
++        }
++    }
++
++    public void loadCertInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id, boolean replace) throws Exception {
+ 
+         String nickname = cert.getNickname();
+         logger.info("Loading certificate \"" + nickname + "\" from NSS database");
+@@ -272,7 +298,7 @@ public class PKCS12Util {
+         pkcs12.addCertInfo(certInfo, replace);
+     }
+ 
+-    public void loadCertKeyFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id) throws Exception {
++    public void loadKeyInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger id) throws Exception {
+ 
+         String nickname = cert.getNickname();
+         logger.info("Loading private key for certificate \"" + nickname + "\" from NSS database");
+@@ -298,30 +324,9 @@ public class PKCS12Util {
+         }
+     }
+ 
+-    public void loadCertChainFromNSS(PKCS12 pkcs12, X509Certificate cert) throws Exception {
+-
+-        CryptoManager cm = CryptoManager.getInstance();
++    public PFX generatePFX(PKCS12 pkcs12, Password password) throws Exception {
+ 
+-        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");
++        logger.info("Generating PKCS #12 data");
+ 
+         SEQUENCE safeContents = new SEQUENCE();
+ 
+@@ -342,6 +347,14 @@ public class PKCS12Util {
+         PFX pfx = new PFX(authSafes);
+         pfx.computeMacData(password, null, 5);
+ 
++        return pfx;
++    }
++
++    public void storeIntoFile(PKCS12 pkcs12, String filename, Password password) throws Exception {
++
++        PFX pfx = generatePFX(pkcs12, password);
++
++        logger.info("Storing data into PKCS #12 file");
+         ByteArrayOutputStream bos = new ByteArrayOutputStream();
+         pfx.encode(bos);
+         byte[] data = bos.toByteArray();
+@@ -362,7 +375,7 @@ public class PKCS12Util {
+         // get key attributes
+         SET bagAttrs = bag.getBagAttributes();
+ 
+-        for (int i = 0; i < bagAttrs.size(); i++) {
++        for (int i = 0; bagAttrs != null && i < bagAttrs.size(); i++) {
+ 
+             Attribute attr = (Attribute) bagAttrs.elementAt(i);
+             OBJECT_IDENTIFIER oid = attr.getType();
+@@ -376,7 +389,7 @@ public class PKCS12Util {
+                 BMPString subjectDN = (BMPString) new BMPString.Template().decode(bis);
+ 
+                 keyInfo.subjectDN = subjectDN.toString();
+-                logger.fine("Subject DN: " + keyInfo.subjectDN);
++                logger.fine("   Subject DN: " + keyInfo.subjectDN);
+ 
+             } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {
+ 
+@@ -387,12 +400,10 @@ public class PKCS12Util {
+                 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("   ID: " + keyInfo.id.toString(16));
+             }
+         }
+ 
+-        logger.fine("Found private key " + keyInfo.subjectDN);
+-
+         return keyInfo;
+     }
+ 
+@@ -406,12 +417,11 @@ public class PKCS12Util {
+         byte[] x509cert = certStr.toByteArray();
+ 
+         certInfo.cert = new X509CertImpl(x509cert);
+-        logger.fine("Found certificate " + certInfo.cert.getSubjectDN());
++        logger.fine("   Subject DN: " + certInfo.cert.getSubjectDN());
+ 
+         SET bagAttrs = bag.getBagAttributes();
+-        if (bagAttrs == null) return certInfo;
+ 
+-        for (int i = 0; i < bagAttrs.size(); i++) {
++        for (int i = 0; bagAttrs != null && i < bagAttrs.size(); i++) {
+ 
+             Attribute attr = (Attribute) bagAttrs.elementAt(i);
+             OBJECT_IDENTIFIER oid = attr.getType();
+@@ -425,7 +435,7 @@ public class PKCS12Util {
+                 BMPString nickname = (BMPString) (new BMPString.Template()).decode(bis);
+ 
+                 certInfo.nickname = nickname.toString();
+-                logger.fine("Nickname: " + certInfo.nickname);
++                logger.fine("   Nickname: " + certInfo.nickname);
+ 
+ 
+             } else if (oid.equals(SafeBag.LOCAL_KEY_ID)) {
+@@ -437,7 +447,7 @@ public class PKCS12Util {
+                 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));
++                logger.fine("   ID: " + certInfo.id.toString(16));
+ 
+             } else if (oid.equals(PKCS12.CERT_TRUST_FLAGS_OID) && trustFlagsEnabled) {
+ 
+@@ -448,16 +458,22 @@ public class PKCS12Util {
+                 BMPString trustFlags = (BMPString) (new BMPString.Template()).decode(is);
+ 
+                 certInfo.trustFlags = trustFlags.toString();
+-                logger.fine("Trust flags: " + certInfo.trustFlags);
++                logger.fine("   Trust flags: " + certInfo.trustFlags);
+             }
+         }
+ 
++        if (certInfo.id == null) {
++            logger.fine("   ID not specified, generating new ID");
++            certInfo.id = createLocalID(x509cert);
++            logger.fine("   ID: " + certInfo.id.toString(16));
++        }
++
+         return certInfo;
+     }
+ 
+     public void getKeyInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {
+ 
+-        logger.fine("Getting private keys");
++        logger.fine("Load private keys:");
+ 
+         AuthenticatedSafes safes = pfx.getAuthSafes();
+ 
+@@ -472,6 +488,7 @@ public class PKCS12Util {
+ 
+                 if (!oid.equals(SafeBag.PKCS8_SHROUDED_KEY_BAG)) continue;
+ 
++                logger.fine(" - Private key:");
+                 PKCS12KeyInfo keyInfo = getKeyInfo(bag, password);
+                 pkcs12.addKeyInfo(keyInfo);
+             }
+@@ -480,7 +497,7 @@ public class PKCS12Util {
+ 
+     public void getCertInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception {
+ 
+-        logger.fine("Getting certificates");
++        logger.fine("Loading certificates:");
+ 
+         AuthenticatedSafes safes = pfx.getAuthSafes();
+ 
+@@ -495,6 +512,7 @@ public class PKCS12Util {
+ 
+                 if (!oid.equals(SafeBag.CERT_BAG)) continue;
+ 
++                logger.fine(" - Certificate:");
+                 PKCS12CertInfo certInfo = getCertInfo(bag);
+                 pkcs12.addCertInfo(certInfo, true);
+             }
+-- 
+1.8.3.1
+
diff --git a/SOURCES/pki-core-Install-tools-clean-up.patch b/SOURCES/pki-core-Install-tools-clean-up.patch
new file mode 100644
index 0000000..3b055eb
--- /dev/null
+++ b/SOURCES/pki-core-Install-tools-clean-up.patch
@@ -0,0 +1,117 @@
+From 0762566785b11c7a9879809ae82b8925fb829c57 Mon Sep 17 00:00:00 2001
+From: "Endi S. Dewata" <edewata@redhat.com>
+Date: Wed, 30 Mar 2016 04:29:11 +0200
+Subject: [PATCH] Install tools clean-up.
+
+Some variables in pkispawn and pkidestroy have been renamed for
+clarity.
+
+The unused PKI_CERT_DB_PASSWORD_SLOT variable has been removed.
+
+The constant pki_self_signed_token property has been moved into
+default.cfg.
+
+https://fedorahosted.org/pki/ticket/2247
+---
+ base/server/config/pkislots.cfg                       |  1 -
+ base/server/etc/default.cfg                           |  1 +
+ base/server/python/pki/server/deployment/pkiparser.py |  3 ---
+ base/server/sbin/pkidestroy                           | 12 ++++++------
+ base/server/sbin/pkispawn                             | 12 ++++++------
+ 5 files changed, 13 insertions(+), 16 deletions(-)
+
+diff --git a/base/server/config/pkislots.cfg b/base/server/config/pkislots.cfg
+index 23c1f824a4ec27d50161549c21a55d36d662fec7..926527599cee5f0de80746bb56e075e0a3e90def 100644
+--- a/base/server/config/pkislots.cfg
++++ b/base/server/config/pkislots.cfg
+@@ -11,7 +11,6 @@ PKI_AJP_PORT_SLOT=[PKI_AJP_PORT]
+ PKI_AJP_REDIRECT_PORT_SLOT=[PKI_AJP_REDIRECT_PORT]
+ PKI_CA_HOSTNAME_SLOT=[PKI_CA_HOSTNAME]
+ PKI_CA_PORT_SLOT=[PKI_CA_PORT]
+-PKI_CERT_DB_PASSWORD_SLOT=[PKI_CERT_DB_PASSWORD]
+ PKI_CFG_PATH_NAME_SLOT=[PKI_CFG_PATH_NAME]
+ PKI_CLOSE_AJP_PORT_COMMENT_SLOT=[PKI_CLOSE_AJP_PORT_COMMENT]
+ PKI_CLOSE_ENABLE_PROXY_COMMENT_SLOT=[PKI_CLOSE_ENABLE_PROXY_COMMENT]
+diff --git a/base/server/etc/default.cfg b/base/server/etc/default.cfg
+index ae0021bb19c9aeda03a7eb5a42490d4919315a82..21c792472c7757ce5eda5e96ccfbd0552cec8b98 100644
+--- a/base/server/etc/default.cfg
++++ b/base/server/etc/default.cfg
+@@ -109,6 +109,7 @@ pki_security_domain_https_port=8443
+ pki_security_domain_name=%(pki_dns_domainname)s Security Domain
+ pki_security_domain_password=
+ pki_security_domain_user=caadmin
++pki_self_signed_token=internal
+ #for supporting server cert SAN injection
+ pki_san_inject=False
+ pki_san_for_server_cert=
+diff --git a/base/server/python/pki/server/deployment/pkiparser.py b/base/server/python/pki/server/deployment/pkiparser.py
+index ca9ef998f69952dc1495c9b17dd1cf45d71f16f4..273b5ac3071640dd836adf1b541cc56b494a24c6 100644
+--- a/base/server/python/pki/server/deployment/pkiparser.py
++++ b/base/server/python/pki/server/deployment/pkiparser.py
+@@ -794,8 +794,6 @@ class PKIConfigParser:
+                 self.mdict['pki_ca_hostname']
+             self.mdict['PKI_CA_PORT_SLOT'] = \
+                 self.mdict['pki_ca_port']
+-            self.mdict['PKI_CERT_DB_PASSWORD_SLOT'] = \
+-                self.mdict['pki_pin']
+             self.mdict['PKI_CFG_PATH_NAME_SLOT'] = \
+                 self.mdict['pki_target_cs_cfg']
+             self.mdict['PKI_CLOSE_SEPARATE_PORTS_SERVER_COMMENT_SLOT'] = \
+@@ -1106,7 +1104,6 @@ class PKIConfigParser:
+             self.mdict['pki_secmod_database'] = \
+                 os.path.join(self.mdict['pki_database_path'],
+                              "secmod.db")
+-            self.mdict['pki_self_signed_token'] = "internal"
+             self.mdict['pki_self_signed_nickname'] = \
+                 self.mdict['pki_ssl_server_nickname']
+             self.mdict['pki_self_signed_subject'] = \
+diff --git a/base/server/sbin/pkidestroy b/base/server/sbin/pkidestroy
+index 12d37f2f39b56958e95eea98d92128d8f206301c..d7285c7e50e1f3f4e50fd30077eb385fd831f415 100755
+--- a/base/server/sbin/pkidestroy
++++ b/base/server/sbin/pkidestroy
+@@ -257,12 +257,12 @@ def main(argv):
+     pki_subsystem_scriptlets = parser.mdict['destroy_scriplets'].split()
+     deployer = util.PKIDeployer(parser.mdict)
+     rv = 0
+-    for pki_scriptlet in pki_subsystem_scriptlets:
+-        scriptlet = __import__("pki.server.deployment.scriptlets." +
+-                               pki_scriptlet,
+-                               fromlist=[pki_scriptlet])
+-        instance = scriptlet.PkiScriptlet()
+-        rv = instance.destroy(deployer)
++    for scriptlet_name in pki_subsystem_scriptlets:
++        scriptlet_module = __import__(
++            "pki.server.deployment.scriptlets." + scriptlet_name,
++            fromlist=[scriptlet_name])
++        scriptlet = scriptlet_module.PkiScriptlet()
++        rv = scriptlet.destroy(deployer)
+         if rv != 0:
+             sys.exit(1)
+     config.pki_log.debug(log.PKI_DICTIONARY_MASTER,
+diff --git a/base/server/sbin/pkispawn b/base/server/sbin/pkispawn
+index e7b22ef1e66598c2a1a64b544ffdc171b88bbd4a..caa5e9bee474cbb8aa76701f83cc53dab308fc44 100755
+--- a/base/server/sbin/pkispawn
++++ b/base/server/sbin/pkispawn
+@@ -515,13 +515,13 @@ def main(argv):
+     pki_subsystem_scriptlets = parser.mdict['spawn_scriplets'].split()
+     deployer = util.PKIDeployer(parser.mdict, parser.slots_dict)
+     rv = 0
+-    for pki_scriptlet in pki_subsystem_scriptlets:
+-        scriptlet = __import__("pki.server.deployment.scriptlets." +
+-                               pki_scriptlet,
+-                               fromlist=[pki_scriptlet])
+-        instance = scriptlet.PkiScriptlet()
++    for scriptlet_name in pki_subsystem_scriptlets:
++        scriptlet_module = __import__(
++            "pki.server.deployment.scriptlets." + scriptlet_name,
++            fromlist=[scriptlet_name])
++        scriptlet = scriptlet_module.PkiScriptlet()
+         try:
+-            rv = instance.spawn(deployer)
++            rv = scriptlet.spawn(deployer)
+         # pylint: disable=W0703
+         except Exception:
+             log_error_details()
+-- 
+2.7.3
+
diff --git a/SOURCES/pki-core-Make-PKIInstance-and-PKISubsystem-hashable.patch b/SOURCES/pki-core-Make-PKIInstance-and-PKISubsystem-hashable.patch
new file mode 100644
index 0000000..682a6ee
--- /dev/null
+++ b/SOURCES/pki-core-Make-PKIInstance-and-PKISubsystem-hashable.patch
@@ -0,0 +1,42 @@
+From 361c708d5854786d8c80dd9864818137d733661c Mon Sep 17 00:00:00 2001
+From: Christian Heimes <cheimes@redhat.com>
+Date: Fri, 4 Mar 2016 23:54:04 +0100
+Subject: [PATCH] Make PKIInstance and PKISubsystem hashable
+
+The upgrade uses instance and subsystem as keys for dicts.
+
+(cherry picked from commit 9e78f981e923c879033c26eebad0cb803d66b8d9)
+
+Conflicts:
+	tests/python/server/test_server.py
+---
+ base/server/python/pki/server/__init__.py | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py
+index 971a3f6..0f61f2a 100644
+--- a/base/server/python/pki/server/__init__.py
++++ b/base/server/python/pki/server/__init__.py
+@@ -109,7 +109,8 @@ class PKISubsystem(object):
+                 self.instance < other.instance or
+                 self_type < other_type)
+ 
+-    __hash__ = None
++    def __hash__(self):
++        return hash((self.name, self.instance, self.type))
+ 
+     def load(self):
+         self.config.clear()
+@@ -391,7 +392,8 @@ class PKIInstance(object):
+         return (self.name < other.name or
+                 self.type < other.type)
+ 
+-    __hash__ = None
++    def __hash__(self):
++        return hash((self.name, self.type))
+ 
+     def is_valid(self):
+         return os.path.exists(self.conf_dir)
+-- 
+1.8.3.1
+
diff --git a/SOURCES/pki-core-Use-correct-textual-encoding-for-PKCS-7-objects.patch b/SOURCES/pki-core-Use-correct-textual-encoding-for-PKCS-7-objects.patch
new file mode 100644
index 0000000..c6d7255
--- /dev/null
+++ b/SOURCES/pki-core-Use-correct-textual-encoding-for-PKCS-7-objects.patch
@@ -0,0 +1,165 @@
+From 670244adbca3ca5aa5e199b720061b9110c28abf Mon Sep 17 00:00:00 2001
+From: Fraser Tweedale <ftweedal@redhat.com>
+Date: Wed, 13 Jan 2016 17:41:05 +1100
+Subject: [PATCH] Use correct textual encoding for PKCS #7 objects
+
+PKCS #7 objects are being output with the "CERTIFICATE CHAIN" label
+which is invalid (RFC 7468) and unrecognised by many programs
+(including OpenSSL).  Use the correct "PKCS7" label instead.
+
+Also do a drive-by refactor of the normalizeCertAndReq to remove
+some redundant code.
+
+Fixes: https://fedorahosted.org/pki/ticket/1699
+---
+ .../webapps/ca/agent/ca/displayBySerial.template   |  4 +--
+ .../webapps/ca/agent/ca/displayBySerial2.template  |  4 +--
+ .../ca/agent/ca/displayCertFromRequest.template    |  4 +--
+ .../webapps/ca/ee/ca/displayBySerial.template      |  4 +--
+ .../shared/webapps/ca/ee/ca/displayCaCert.template |  6 ++--
+ .../com/netscape/cmsutil/crypto/CryptoUtil.java    | 35 ++--------------------
+ 6 files changed, 13 insertions(+), 44 deletions(-)
+
+diff --git a/base/ca/shared/webapps/ca/agent/ca/displayBySerial.template b/base/ca/shared/webapps/ca/agent/ca/displayBySerial.template
+index 2bb2bfab7c2e208555b69188f6f33dd536732e3f..f95434a1b61fcc2eba00537ef050d849160e65aa 100644
+--- a/base/ca/shared/webapps/ca/agent/ca/displayBySerial.template
++++ b/base/ca/shared/webapps/ca/agent/ca/displayBySerial.template
+@@ -191,11 +191,11 @@ document.write(result.header.certChainBase64);
+ Base 64 encoded certificate with CA certificate chain in pkcs7 format
+ </font>
+ <p><pre>
+------BEGIN CERTIFICATE CHAIN-----
+ <SCRIPT type="text/javascript">
++document.writeln('-----BEGIN PKCS7-----');
+ document.write(result.header.pkcs7ChainBase64);
++document.writeln('-----END PKCS7-----');
+ </SCRIPT>
+------END CERTIFICATE CHAIN-----
+ </pre>
+ 
+ <br><p>
+diff --git a/base/ca/shared/webapps/ca/agent/ca/displayBySerial2.template b/base/ca/shared/webapps/ca/agent/ca/displayBySerial2.template
+index 4a193e3243e79074feabd21e0094f4b5cea635b9..f0604ef7fc3a7a9ec4c1dd016f0652c507e204dd 100644
+--- a/base/ca/shared/webapps/ca/agent/ca/displayBySerial2.template
++++ b/base/ca/shared/webapps/ca/agent/ca/displayBySerial2.template
+@@ -97,11 +97,11 @@ The following format can be used to install this certificate into a server.
+ Base 64 encoded certificate
+ </font>
+ <p><pre>
+------BEGIN CERTIFICATE CHAIN-----
+ <SCRIPT type="text/javascript">
++document.writeln('-----BEGIN PKCS7-----');
+ document.write(result.header.certChainBase64);
++document.writeln('-----END PKCS7-----');
+ </SCRIPT>
+------END CERTIFICATE CHAIN-----
+ </pre>
+ 
+ <br><p>
+diff --git a/base/ca/shared/webapps/ca/agent/ca/displayCertFromRequest.template b/base/ca/shared/webapps/ca/agent/ca/displayCertFromRequest.template
+index f1148570c5e1cd3c251ee64008228da2e710b421..402154037790343061dc4a711de0d0fba738dbf2 100644
+--- a/base/ca/shared/webapps/ca/agent/ca/displayCertFromRequest.template
++++ b/base/ca/shared/webapps/ca/agent/ca/displayCertFromRequest.template
+@@ -102,9 +102,9 @@ function displayCert(cert)
+ 		'Base 64 encoded certificate with CA certificate chain in pkcs7 format'+
+ 		'</font>'+
+ 		'<p><pre>'+
+-		'-----BEGIN CERTIFICATE CHAIN-----');
++		'-----BEGIN PKCS7-----');
+ 		document.writeln(cert.pkcs7ChainBase64);
+-		document.writeln('-----END CERTIFICATE CHAIN-----'+
++		document.writeln('-----END PKCS7-----'+
+ 		'</pre>');
+ 
+ }
+diff --git a/base/ca/shared/webapps/ca/ee/ca/displayBySerial.template b/base/ca/shared/webapps/ca/ee/ca/displayBySerial.template
+index e9b4d72bfb2b23a67c15282ae521b513d7a5dbfd..d482644b768750b704461785fe39eb744db7cbe9 100644
+--- a/base/ca/shared/webapps/ca/ee/ca/displayBySerial.template
++++ b/base/ca/shared/webapps/ca/ee/ca/displayBySerial.template
+@@ -116,11 +116,11 @@ document.write(result.header.certChainBase64);
+ Base 64 encoded certificate with CA certificate chain in pkcs7 format
+ </font>
+ <p><pre>
+------BEGIN CERTIFICATE-----
+ <SCRIPT LANGUAUGE="JavaScript">
++document.writeln('-----BEGIN PKCS7-----');
+ document.write(result.header.pkcs7ChainBase64);
++document.writeln('-----END PKCS7-----');
+ </SCRIPT>
+------END CERTIFICATE-----
+ </pre>
+ 
+ <br><p>
+diff --git a/base/ca/shared/webapps/ca/ee/ca/displayCaCert.template b/base/ca/shared/webapps/ca/ee/ca/displayCaCert.template
+index 4e93919f53d553872ff9ee98356d81edda9a7640..4da0d74c8302329addf1ec1dd042f7ffe7ea18ae 100644
+--- a/base/ca/shared/webapps/ca/ee/ca/displayCaCert.template
++++ b/base/ca/shared/webapps/ca/ee/ca/displayCaCert.template
+@@ -43,9 +43,9 @@ if (result.header.displayFormat == "chain") {
+     document.writeln('<center><b>' + result.header.subjectdn);
+     document.writeln('</b></center><p></font><br>');
+     document.writeln('<pre>');
+-    document.writeln('-----BEGIN CERTIFICATE-----');
+-    document.writeln(result.header.chainBase64);
+-    document.writeln('-----END CERTIFICATE-----');
++    document.writeln('-----BEGIN PKCS7-----');
++    document.write(result.header.chainBase64);
++    document.writeln('-----END PKCS7-----');
+     document.writeln('</pre>');
+ } else if (result.header.displayFormat == "individual") {
+     if (result.recordSet.length == 0) {
+diff --git a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
+index 3b1041a74bb4b663dd9c5f4c9fa983da133f04a3..59883831afa5c9016594c54bbb25bf5f503f00b7 100644
+--- a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
++++ b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java
+@@ -822,46 +822,15 @@ public class CryptoUtil {
+         if (s == null) {
+             return s;
+         }
+-        s = s.replaceAll("-----BEGIN CERTIFICATE REQUEST-----", "");
+-        s = s.replaceAll("-----BEGIN NEW CERTIFICATE REQUEST-----", "");
+-        s = s.replaceAll("-----END CERTIFICATE REQUEST-----", "");
+-        s = s.replaceAll("-----END NEW CERTIFICATE REQUEST-----", "");
+-        s = s.replaceAll("-----BEGIN CERTIFICATE-----", "");
+-        s = s.replaceAll("-----END CERTIFICATE-----", "");
+-        s = s.replaceAll("-----BEGIN CERTIFICATE CHAIN-----", "");
+-        s = s.replaceAll("-----END CERTIFICATE CHAIN-----", "");
++        // grammar defined at https://tools.ietf.org/html/rfc7468#section-3
++        s = s.replaceAll("-----(BEGIN|END) [\\p{Print}&&[^- ]]([- ]?[\\p{Print}&&[^- ]])*-----", "");
+ 
+         StringBuffer sb = new StringBuffer();
+         StringTokenizer st = new StringTokenizer(s, "\r\n ");
+ 
+         while (st.hasMoreTokens()) {
+             String nextLine = st.nextToken();
+-
+             nextLine = nextLine.trim();
+-            if (nextLine.equals("-----BEGIN CERTIFICATE REQUEST-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----BEGIN NEW CERTIFICATE REQUEST-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----END CERTIFICATE REQUEST-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----END NEW CERTIFICATE REQUEST-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----BEGIN CERTIFICATE-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----END CERTIFICATE-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----BEGIN CERTIFICATE CHAIN-----")) {
+-                continue;
+-            }
+-            if (nextLine.equals("-----END CERTIFICATE CHAIN-----")) {
+-                continue;
+-            }
+             sb.append(nextLine);
+         }
+         return sb.toString();
+-- 
+2.4.3
+
diff --git a/SPECS/pki-core.spec b/SPECS/pki-core.spec
index b2e4cf7..9618e62 100644
--- a/SPECS/pki-core.spec
+++ b/SPECS/pki-core.spec
@@ -40,7 +40,7 @@ distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
 
 Name:             pki-core
 Version:          10.2.5
-Release:          6%{?dist}
+Release:          10%{?dist}
 Summary:          Certificate System - PKI Core Components
 URL:              http://pki.fedoraproject.org/
 License:          GPLv2
@@ -144,12 +144,32 @@ Source0:          http://pki.fedoraproject.org/pki/sources/%{name}/%{version}/%{
 Source0:          http://pki.fedoraproject.org/pki/sources/%{name}/%{version}/%{release}/%{name}-%{version}%{?prerel}.tar.gz
 %endif
 
+## pki-core-10.2.5-2
+## pki-core-10.2.5-3
 Patch1:           pki-core-rhel-7-2.patch
+## pki-core-10.2.5-4
+## pki-core-10.2.5-5
 Patch2:           pki-core-handle-JSON-decode-error.patch
 Patch3:           pki-core-fix-exception-when-talking-to-Dogtag-9-systems.patch
+## pki-core-10.2.5-6
 Patch4:           pki-core-added-CLI-to-update-cert-data-and-request-in-CS-cfg.patch
 Patch5:           pki-core-fixed-pkidbuser-group-memberships.patch
 Patch6:           pki-core-added-support-for-secure-database-connection-in-CLI.patch
+## pki-core-10.2.5-7
+Patch7:           pki-core-Added-support-for-existing-CA-case.patch
+Patch8:           pki-core-Fixed-mismatching-certificate-validity-calculation.patch
+Patch9:           pki-core-Fix-to-determine-supported-javadoc-options.patch
+## pki-core-10.2.5-8
+Patch10:          pki-core-Use-correct-textual-encoding-for-PKCS-7-objects.patch
+## pki-core-10.2.5-9
+Patch11:          pki-core-Added-support-for-cloning-3rd-party-CA-certificates.patch
+Patch12:          pki-core-Fixed-certificate-chain-import-problem.patch
+Patch13:          pki-core-Fix-escaping-of-password-fields-to-prevent-interpolation.patch
+Patch14:          pki-core-Install-tools-clean-up.patch
+Patch15:          pki-core-Fixed-KRA-install-problem.patch
+Patch16:          pki-core-Fixed-missing-trust-flags-in-certificate-backup.patch
+## pki-core-10.2.5-10
+Patch17:          pki-core-Make-PKIInstance-and-PKISubsystem-hashable.patch
 
 %global saveFileContext() \
 if [ -s /etc/selinux/config ]; then \
@@ -647,6 +667,17 @@ This package is a part of the PKI Core used by the Certificate System.
 %patch4 -p1
 %patch5 -p1
 %patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
+%patch10 -p1
+%patch11 -p1
+%patch12 -p1
+%patch13 -p1
+%patch14 -p1
+%patch15 -p1
+%patch16 -p1
+%patch17 -p1
 
 %clean
 %{__rm} -rf %{buildroot}
@@ -834,6 +865,7 @@ systemctl daemon-reload
 %{python_sitelib}/pki/*.py
 %{python_sitelib}/pki/*.pyc
 %{python_sitelib}/pki/*.pyo
+%{python_sitelib}/pki/cli/
 %dir %{_localstatedir}/log/pki
 %{_sbindir}/pki-upgrade
 %{_mandir}/man8/pki-upgrade.8.gz
@@ -990,6 +1022,27 @@ systemctl daemon-reload
 %endif # %{with server}
 
 %changelog
+* Thu Apr 21 2016 Dogtag Team <pki-devel@redhat.com> 10.2.5-10
+- Bugzilla Bug #1318302 - pkispawn ignores 3rd-party CA certs in
+  pki_clone_pkcs12_path (python hash fix)
+
+* Fri Mar 18 2016 Dogtag Team <pki-devel@redhat.com> 10.2.5-9
+- Bugzilla Bug #1318302 - pkispawn ignores 3rd-party CA certs in
+  pki_clone_pkcs12_path
+- Bugzilla Bug #1320580 - IPA replica installation fails if password
+  has %% character in it.
+
+* Fri Mar 18 2016 Dogtag Team <pki-devel@redhat.com> 10.2.5-8
+- PKI TRAC Ticket #1699 - CA PKCS7 display does not seem to be properly
+  formatted
+
+* Thu Feb 18 2016 Dogtag Team <pki-devel@redhat.com> 10.2.5-7
+- Bugzilla Bug #1289323 -  [RFE] Provide the user the ability to import their
+  own CA certificate with private key [edewata]
+- Bugzilla Bug #1277691 - ipa-cacert-manage renew failed with validity
+  out of range [edewata]
+- PKI TRAC Ticket #2040 - Determine supported javadoc options [mharmsen]
+
 * Mon Sep 21 2015 Dogtag Team <pki-devel@redhat.com> 10.2.5-6
 - Bugzilla Bug #1258630 - Upgraded CA lacks ca.sslserver.certreq
   in CS.cfg [edewata]