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" +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 ++# ++# 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 PKCS #12 file containing certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 PKI client type (default: java)') ++ print(' -d Client security database location ' + ++ '(default: ~/.dogtag/nssdb)') ++ print(' -c Client security database password ' + ++ '(mutually exclusive to the -C option)') ++ print(' -C Client-side password file ' + ++ '(mutually exclusive to the -c option)') ++ print(' --token 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 -p -w -o "); +- 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 -p -w -o "); + } + +- 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() + " [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 certInfos = new ArrayList(); ++ ++ 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 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() + " [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 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() + " [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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 (default: pki-tomcat).') ++ print(' --pkcs12-file Output file to store the exported certificate and key in PKCS #12 format.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 ++# ++# 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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 ++# ++# 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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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] ') + print() + print(' -i, --instance 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] ') ++ def print_help(self): # flake8: noqa ++ print('Usage: pki-server subsystem-cert-export [OPTIONS] [cert ID]') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' --cert-file 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 ++# ++# 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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 ++# ++# 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 (default: pki-tomcat).') ++ print(' --pkcs12-file PKCS #12 file to store certificates and keys.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 keyInfosByID = new LinkedHashMap(); ++ ++ Map certInfosByID = new LinkedHashMap(); ++ ++ public PKCS12() { ++ } ++ ++ public Collection 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 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 getCertInfosByNickname(String nickname) { ++ ++ Collection result = new ArrayList(); ++ ++ for (PKCS12CertInfo certInfo : certInfosByID.values()) { ++ if (!nickname.equals(certInfo.getNickname())) continue; ++ result.add(certInfo); ++ } ++ ++ return result; ++ } ++ ++ public void removeCertInfoByNickname(String nickname) { ++ ++ Collection 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 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" +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 ++# ++# 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 ca-cert-release-hold + + .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 \-\-output \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 --request-type --csr-file --subject + +-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 \-\-output ++ ++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 + +-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 ca-cert-request-review --file + +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 subjectAttributes = new HashMap(); ++ 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] ') ++ print() ++ print(' -i, --instance Instance ID (default: pki-tomcat).') ++ print(' --cert-file Output file to store the exported certificate in PEM format.') ++ print(' --csr-file Output file to store the exported CSR in PEM format.') ++ print(' --pkcs12-file Output file to store the exported certificate and key in PKCS #12 format.') ++ print(' --pkcs12-password Password for the PKCS #12 file.') ++ print(' --pkcs12-password-file 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 +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 + +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 +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 +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" +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" +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 +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" +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 +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 +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 + +

+------BEGIN CERTIFICATE CHAIN-----
+ 
+------END CERTIFICATE CHAIN-----
+ 
+ +

+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 + +

+------BEGIN CERTIFICATE CHAIN-----
+ 
+------END CERTIFICATE CHAIN-----
+ 
+ +

+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'+ + ''+ + '

'+
+-		'-----BEGIN CERTIFICATE CHAIN-----');
++		'-----BEGIN PKCS7-----');
+ 		document.writeln(cert.pkcs7ChainBase64);
+-		document.writeln('-----END CERTIFICATE CHAIN-----'+
++		document.writeln('-----END PKCS7-----'+
+ 		'
'); + + } +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 + +

+------BEGIN CERTIFICATE-----
+ 
+------END CERTIFICATE-----
+ 
+ +

+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('

' + result.header.subjectdn); + document.writeln('


'); + document.writeln('

');
+-    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('
'); + } 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 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 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 10.2.5-8 +- PKI TRAC Ticket #1699 - CA PKCS7 display does not seem to be properly + formatted + +* Thu Feb 18 2016 Dogtag Team 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 10.2.5-6 - Bugzilla Bug #1258630 - Upgraded CA lacks ca.sslserver.certreq in CS.cfg [edewata]