From edewata at redhat.com Wed Sep 2 03:20:05 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Tue, 1 Sep 2015 22:20:05 -0500 Subject: [Pki-devel] [PATCH] 641 Added CLI to update cert data and request in CS.cfg. Message-ID: <55E66AE5.3050304@redhat.com> A set of new pki-server commands have been added to simplify updating the cert data and cert request stored in the CS.cfg with the cert data and cert request stored in the NSS and LDAP database, respectively. https://fedorahosted.org/pki/ticket/1551 -- Endi S. Dewata -------------- next part -------------- From e8d08f30ae1bb90ee4514debf97ff321e1554809 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Wed, 2 Sep 2015 04:50:24 +0200 Subject: [PATCH] Added CLI to update cert data and request in CS.cfg. A set of new pki-server commands have been added to simplify updating the cert data and cert request stored in the CS.cfg with the cert data and cert request stored in the NSS and LDAP database, respectively. https://fedorahosted.org/pki/ticket/1551 --- base/server/python/pki/server/__init__.py | 115 ++++++++- base/server/python/pki/server/ca.py | 92 ++++++++ base/server/python/pki/server/cli/ca.py | 206 ++++++++++++++++ base/server/python/pki/server/cli/subsystem.py | 312 +++++++++++++++++++++---- base/server/python/pki/server/upgrade.py | 3 + base/server/sbin/pki-server | 2 + 6 files changed, 684 insertions(+), 46 deletions(-) create mode 100644 base/server/python/pki/server/ca.py create mode 100644 base/server/python/pki/server/cli/ca.py diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index 97b2e97992ad1daf0313127ce89f826cc3d0fb10..83a5f09d1faa0a506b6c8ff60d3c1c7d35f1d352 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -21,7 +21,11 @@ from __future__ import absolute_import from lxml import etree +import getpass import grp +import io +import ldap +import operator import os import pwd import re @@ -32,7 +36,7 @@ import pki INSTANCE_BASE_DIR = '/var/lib/pki' REGISTRY_DIR = '/etc/sysconfig/pki' SUBSYSTEM_TYPES = ['ca', 'kra', 'ocsp', 'tks', 'tps'] - +SUBSYSTEM_CLASSES = {} class PKIServer(object): @@ -66,6 +70,7 @@ class PKISubsystem(object): self.base_dir = instance.base_dir self.conf_dir = os.path.join(self.base_dir, 'conf') + self.cs_conf = os.path.join(self.conf_dir, 'CS.cfg') self.context_xml_template = os.path.join( pki.SHARE_DIR, self.name, 'conf', 'Catalina', 'localhost', self.name + '.xml') @@ -73,9 +78,62 @@ class PKISubsystem(object): self.context_xml = os.path.join( instance.conf_dir, 'Catalina', 'localhost', self.name + '.xml') + self.config = {} + self.type = None + self.prefix = None + # custom subsystem location self.doc_base = os.path.join(self.base_dir, 'webapps', self.name) + def load(self): + self.config.clear() + + lines = open(self.cs_conf).read().splitlines() + + for line in lines: + parts = line.split('=', 1) + name = parts[0] + value = parts[1] + self.config[name] = value + + self.type = self.config['cs.type'] + self.prefix = self.type.lower() + + def find_subsystem_certs(self): + certs = [] + + ids = self.config['%s.cert.list' % self.name].split(',') + for id in ids: + cert = self.create_subsystem_cert_object(id) + certs.append(cert) + + return certs + + def get_subsystem_cert(self, id): + return self.create_subsystem_cert_object(id) + + def create_subsystem_cert_object(self, id): + cert = {} + cert['id'] = id + cert['nickname'] = self.config.get('%s.%s.nickname' % (self.name, id), None) + cert['token'] = self.config.get('%s.%s.tokenname' % (self.name, id), None) + cert['data'] = self.config.get('%s.%s.cert' % (self.name, id), None) + cert['request'] = self.config.get('%s.%s.certreq' % (self.name, id), None) + return cert + + def update_subsystem_cert(self, cert): + id = cert['id'] + self.config['%s.%s.nickname' % (self.name, id)] = cert.get('nickname', None) + self.config['%s.%s.tokenname' % (self.name, id)] = cert.get('token', None) + self.config['%s.%s.cert' % (self.name, id)] = cert.get('data', None) + self.config['%s.%s.certreq' % (self.name, id)] = cert.get('request', None) + + def save(self): + sorted_config = sorted(self.config.items(), key=operator.itemgetter(0)) + with io.open(self.cs_conf, 'wb') as f: + for (key, value) in sorted_config: + f.write('%s=%s\n' % (key, value)) + def is_valid(self): return os.path.exists(self.conf_dir) @@ -103,6 +161,21 @@ class PKISubsystem(object): def disable(self): self.instance.undeploy(self.name) + def open_database(self, name='internaldb'): + + hostname = self.config['%s.ldapconn.host' % name] + port = self.config['%s.ldapconn.port' % name] + bind_dn = self.config['%s.ldapauth.bindDN' % name] + + # TODO: add support for other authentication + # mechanisms (e.g. client cert authentication, LDAPI) + bind_password = self.instance.get_password(name) + + con = ldap.initialize('ldap://%s:%s' % (hostname, port)) + con.simple_bind_s(bind_dn, bind_password) + + return con + def __repr__(self): return str(self.instance) + '/' + self.name @@ -120,6 +193,9 @@ class PKIInstance(object): self.base_dir = os.path.join(pki.BASE_DIR, name) self.conf_dir = os.path.join(self.base_dir, 'conf') + self.password_conf = os.path.join(self.conf_dir, 'password.conf') + + self.nssdb_dir = os.path.join(self.base_dir, 'alias') self.lib_dir = os.path.join(self.base_dir, 'lib') self.registry_dir = os.path.join( @@ -136,6 +212,8 @@ class PKIInstance(object): self.uid = None self.gid = None + self.passwords = {} + self.subsystems = [] def is_valid(self): @@ -158,6 +236,7 @@ class PKIInstance(object): return rc == 0 def load(self): + # load UID and GID with open(self.registry_file, 'r') as registry: lines = registry.readlines() @@ -173,11 +252,41 @@ class PKIInstance(object): 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() + + 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 pki.server.SUBSYSTEM_TYPES: - subsystem = PKISubsystem(self, subsystem_name) + 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: + return self.passwords[name] + + password = getpass.getpass(prompt='Enter password for %s: ' % name) + self.passwords[name] = password + + return password + + def get_subsystem(self, name): + for subsystem in self.subsystems: + if name == subsystem.name: + return subsystem + return None + def is_deployed(self, webapp_name): context_xml = os.path.join( self.conf_dir, 'Catalina', 'localhost', webapp_name + '.xml') diff --git a/base/server/python/pki/server/ca.py b/base/server/python/pki/server/ca.py new file mode 100644 index 0000000000000000000000000000000000000000..70ebf4dd1044ecad32a0cf072ba45c6ace5eacea --- /dev/null +++ b/base/server/python/pki/server/ca.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# 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. +# + +from __future__ import absolute_import +import ldap +import ldap.filter + +import pki +import pki.server + + +class CASubsystem(pki.server.PKISubsystem): + + def __init__(self, instance): + super(CASubsystem, self).__init__(instance, 'ca') + + def find_cert_requests(self, cert=None): + + base_dn = self.config['internaldb.basedn'] + + if cert: + escaped_value = ldap.filter.escape_filter_chars(cert) + search_filter = '(extdata-req--005fissued--005fcert=%s)' % escaped_value + + else: + search_filter = '(objectClass=*)' + + con = self.open_database() + + entries = con.search_s( + 'ou=ca,ou=requests,%s' % base_dn, + ldap.SCOPE_ONELEVEL, + search_filter, + None) + + con.unbind_s() + + requests = [] + for entry in entries: + requests.append(self.create_request_object(entry)) + + return requests + + def get_cert_requests(self, request_id): + + base_dn = self.config['internaldb.basedn'] + + con = self.open_database() + + entries = con.search_s( + 'cn=%s,ou=ca,ou=requests,%s' % (request_id, base_dn), + ldap.SCOPE_BASE, + '(objectClass=*)', + None) + + con.unbind_s() + + entry = entries[0] + return self.create_request_object(entry) + + def create_request_object(self, entry): + + attrs = entry[1] + + request = {} + request['id'] = attrs['cn'][0] + request['type'] = attrs['requestType'][0] + request['status'] = attrs['requestState'][0] + request['request'] = attrs['extdata-cert--005frequest'][0] + + return request + + +pki.server.SUBSYSTEM_CLASSES['ca'] = CASubsystem diff --git a/base/server/python/pki/server/cli/ca.py b/base/server/python/pki/server/cli/ca.py new file mode 100644 index 0000000000000000000000000000000000000000..2ad8652f4fddd032779941cb7e2ae4e643c25e0a --- /dev/null +++ b/base/server/python/pki/server/cli/ca.py @@ -0,0 +1,206 @@ +#!/usr/bin/python +# 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. +# + +from __future__ import absolute_import +from __future__ import print_function +import getopt +import io +import sys + +import pki.cli +import pki.server.ca + + +class CACLI(pki.cli.CLI): + + def __init__(self): + super(CACLI, self).__init__( + 'ca', 'CA management commands') + + self.add_module(CACertCLI()) + + +class CACertCLI(pki.cli.CLI): + + def __init__(self): + super(CACertCLI, self).__init__( + 'cert', 'CA certificates management commands') + + self.add_module(CACertRequestCLI()) + + +class CACertRequestCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestCLI, self).__init__( + 'request', 'CA certificate requests management commands') + + self.add_module(CACertRequestFindCLI()) + self.add_module(CACertRequestShowCLI()) + + @staticmethod + def print_request(request, details=False): + print(' Request ID: %s' % request['id']) + print(' Type: %s' % request['type']) + print(' Status: %s' % request['status']) + + if details: + print(' Request: %s' % request['request']) + + +class CACertRequestFindCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestFindCLI, self).__init__( + 'find', 'Find CA certificate requests') + + def usage(self): + print('Usage: pki-server ca-cert-request-find [OPTIONS]') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' --cert Issued certificate.') + print(' --cert-file File containing issued certificate.') + 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=', 'cert=', 'cert-file=', + 'verbose', 'help']) + + except getopt.GetoptError as e: + print('ERROR: ' + str(e)) + self.usage() + sys.exit(1) + + instance_name = 'pki-tomcat' + cert = None + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = a + + elif o == '--cert': + cert = a + + elif o == '--cert-file': + with io.open(a, 'rb') as f: + cert = 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.usage() + sys.exit(1) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem('ca') + results = subsystem.find_cert_requests(cert=cert) + + self.print_message('%s entries matched' % len(results)) + + first = True + for request in results: + if first: + first = False + else: + print() + + CACertRequestCLI.print_request(request) + + +class CACertRequestShowCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestShowCLI, self).__init__( + 'show', 'Show CA certificate request') + + def usage(self): + print('Usage: pki-server ca-cert-request-show [OPTIONS]') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, args): + + try: + opts, args = getopt.gnu_getopt(args, 'i:v', [ + 'instance=', 'output-file=', + 'verbose', 'help']) + + except getopt.GetoptError as e: + print('ERROR: ' + str(e)) + self.usage() + sys.exit(1) + + if len(args) != 1: + print('ERROR: missing request ID') + self.usage() + sys.exit(1) + + request_id = args[0] + instance_name = 'pki-tomcat' + output_file = None + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = a + + elif o == '--output-file': + output_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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem('ca') + request = subsystem.get_cert_requests(request_id) + + if output_file: + with io.open(output_file, 'wb') as f: + f.write(request['request']) + + else: + CACertRequestCLI.print_request(request, details=True) diff --git a/base/server/python/pki/server/cli/subsystem.py b/base/server/python/pki/server/cli/subsystem.py index b1c20af59de460cc9d8e9ae4a6288787082cc02a..ef06c25bbcae11bc822badb0e1215e7f6194881a 100644 --- a/base/server/python/pki/server/cli/subsystem.py +++ b/base/server/python/pki/server/cli/subsystem.py @@ -21,8 +21,11 @@ from __future__ import absolute_import from __future__ import print_function +import base64 import getopt +import nss.nss as nss import os +import string import sys import pki.cli @@ -40,6 +43,8 @@ class SubsystemCLI(pki.cli.CLI): self.add_module(SubsystemFindCLI()) self.add_module(SubsystemShowCLI()) + self.add_module(SubsystemCertCLI()) + @staticmethod def print_subsystem(subsystem): print(' Subsystem ID: %s' % subsystem.name) @@ -55,7 +60,7 @@ class SubsystemFindCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-find [OPTIONS]') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -72,7 +77,7 @@ class SubsystemFindCLI(pki.cli.CLI): self.usage() sys.exit(1) - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -90,28 +95,13 @@ class SubsystemFindCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - results = [] - - for name in os.listdir(instance.base_dir): - - subsystem = pki.server.PKISubsystem(instance, name) - if not subsystem.is_valid(): - continue - - results.append(subsystem) - - self.print_message('%s entries matched' % len(results)) + self.print_message('%s entries matched' % len(instance.subsystems)) first = True - for subsystem in results: + for subsystem in instance.subsystems: if first: first = False else: @@ -128,7 +118,7 @@ class SubsystemShowCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-show [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -151,7 +141,7 @@ class SubsystemShowCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -169,15 +159,10 @@ class SubsystemShowCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) SubsystemCLI.print_subsystem(subsystem) @@ -190,7 +175,7 @@ class SubsystemEnableCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-enable [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -213,7 +198,7 @@ class SubsystemEnableCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -231,15 +216,10 @@ class SubsystemEnableCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) subsystem.enable() self.print_message('Enabled "%s" subsystem' % subsystem_name) @@ -257,7 +237,7 @@ class SubsystemDisableCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-disable [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -280,7 +260,7 @@ class SubsystemDisableCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -298,17 +278,263 @@ class SubsystemDisableCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) subsystem.disable() self.print_message('Disabled "%s" subsystem' % subsystem_name) SubsystemCLI.print_subsystem(subsystem) + + +class SubsystemCertCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertCLI, self).__init__( + 'cert', 'Subsystem certificate management commands') + + self.add_module(SubsystemCertFindCLI()) + self.add_module(SubsystemCertShowCLI()) + self.add_module(SubsystemCertUpdateCLI()) + + @staticmethod + def print_subsystem_cert(cert): + 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']) + + +class SubsystemCertFindCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertFindCLI, self).__init__( + 'find', 'Find subsystem certificates') + + def usage(self): + print('Usage: pki-server subsystem-cert-find [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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) + + subsystem_name = args[0] + instance_name = 'pki-tomcat' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + results = subsystem.find_subsystem_certs() + + self.print_message('%s entries matched' % len(results)) + + first = True + for cert in results: + if first: + first = False + else: + print() + + SubsystemCertCLI.print_subsystem_cert(cert) + + +class SubsystemCertShowCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertShowCLI, self).__init__( + 'show', 'Show subsystem certificate') + + def usage(self): + print('Usage: pki-server subsystem-cert-show [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + subsystem_cert = subsystem.get_subsystem_cert(cert_id) + + SubsystemCertCLI.print_subsystem_cert(subsystem_cert) + + +class SubsystemCertUpdateCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertUpdateCLI, self).__init__( + 'update', 'Update subsystem certificate') + + def usage(self): + print('Usage: pki-server subsystem-cert-update [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + subsystem_cert = subsystem.get_subsystem_cert(cert_id) + + # get cert data from NSS database + nss.nss_init(instance.nssdb_dir) + nss_cert = nss.find_cert_from_nickname(subsystem_cert['nickname']) + data = base64.b64encode(nss_cert.der_data) + del nss_cert + nss.nss_shutdown() + subsystem_cert['data'] = data + + # format cert data for LDAP database + lines = [data[i:i+64] for i in range(0, len(data), 64)] + data = string.join(lines, '\r\n') + '\r\n' + + # get cert request from local CA + # TODO: add support for remote CA + ca = instance.get_subsystem('ca') + results = ca.find_cert_requests(cert=data) + cert_request = results[-1] + request = cert_request['request'] + + # format cert request for CS.cfg + lines = request.splitlines() + if lines[0] == '-----BEGIN CERTIFICATE REQUEST-----': + lines = lines[1:] + if lines[-1] == '-----END CERTIFICATE REQUEST-----': + lines = lines[:-1] + request = string.join(lines, '') + subsystem_cert['request'] = request + + # store cert data and request in CS.cfg + subsystem.update_subsystem_cert(subsystem_cert) + subsystem.save() + + self.print_message('Updated "%s" subsystem certificate' % cert_id) + + SubsystemCertCLI.print_subsystem_cert(subsystem_cert) diff --git a/base/server/python/pki/server/upgrade.py b/base/server/python/pki/server/upgrade.py index a0e52b22b87035f6b39c388002f63eb8af2eb4ff..ffe7c891d92c4fd16a5e42bdc4d268c2251a0604 100644 --- a/base/server/python/pki/server/upgrade.py +++ b/base/server/python/pki/server/upgrade.py @@ -215,6 +215,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): if self.subsystemName: subsystem = pki.server.PKISubsystem(instance, self.subsystemName) subsystem.validate() + subsystem.load() return [subsystem] subsystem_list = [] @@ -229,6 +230,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): instance, subsystemName) subsystem.validate() + subsystem.load() subsystem_list.append(subsystem) else: for subsystemName in pki.server.SUBSYSTEM_TYPES: @@ -241,6 +243,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): instance, subsystemName) subsystem.validate() + subsystem.load() subsystem_list.append(subsystem) subsystem_list.sort() diff --git a/base/server/sbin/pki-server b/base/server/sbin/pki-server index d479788debbaed8c58f977023463b3033636e1c3..f3e784a1539a4cff82cdc765da53f2cb01b27a3c 100644 --- a/base/server/sbin/pki-server +++ b/base/server/sbin/pki-server @@ -25,6 +25,7 @@ import getopt import sys import pki.cli +import pki.server.cli.ca import pki.server.cli.instance import pki.server.cli.subsystem import pki.server.cli.migrate @@ -38,6 +39,7 @@ class PKIServerCLI(pki.cli.CLI): 'pki-server', 'PKI server command-line interface') + self.add_module(pki.server.cli.ca.CACLI()) self.add_module(pki.server.cli.instance.InstanceCLI()) self.add_module(pki.server.cli.subsystem.SubsystemCLI()) self.add_module(pki.server.cli.migrate.MigrateCLI()) -- 2.4.3 From lachen at redhat.com Wed Sep 2 17:27:05 2015 From: lachen at redhat.com (Lan Chen) Date: Wed, 2 Sep 2015 13:27:05 -0400 (EDT) Subject: [Pki-devel] Dogtag 10.1 API In-Reply-To: <625987450.12482137.1441214487694.JavaMail.zimbra@redhat.com> References: <625987450.12482137.1441214487694.JavaMail.zimbra@redhat.com> Message-ID: <2012342168.12483885.1441214825755.JavaMail.zimbra@redhat.com> Hi All, Is there good documentation on Dogtag 10.1 API somewhere? Lan ----- Original Message ----- From: "Ryan Murray" To: "Lan Chen" Sent: Wednesday, September 2, 2015 9:53:09 AM Subject: Re: Dogtag 10.1 There is a massive difference between the API's, I checked before raising the concern. By massive I mean they are all different. Example: 10.1 Noun=certs 10.2 Noun=certificates None of the other nouns are working as documented by 10.2. On Sep 2, 2015 9:50 AM, "Lan Chen" < lachen at redhat.com > wrote: Ryan, I just checked with Paul, we need it installed on RHEL. Could you see if the APIs documented for 10.2 also works for 10.1, there shouldn't be too big of a difference between the versions. From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 9:38:25 AM Subject: Re: Dogtag 10.1 The packages are not avaliable to the RHEL 7 channels or EPEL channels. From a technical standpoint I would need to spend time getting the exact limitations, but as it stands I would have to install tons of fedora packages or compile from source to get 10.2 running on RHEL. On Wednesday, September 2, 2015, Lan Chen < lachen at redhat.com > wrote:
I meant why can't 10.2 be on RHEL, and need to be on Fedora? From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 9:33:08 AM Subject: Re: Dogtag 10.1 There is no issue with the install, that was done Monday night. The issue is with the API not being documented. The client had to use fedora in all of their tests due to the newer 10.2 version and better API. On Wednesday, September 2, 2015, Lan Chen < lachen at redhat.com > wrote:
What's the issue on installing Dogtag 10.2 on RHEL? From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 8:02:27 AM Subject: Dogtag 10.1 Hi Lan, Could you please see if there is anyone at Red Hat technical that would have any documentation on the dog tag 10.1 API? It is not documented online that I can find. Hunting through source code to find undocumented API calls is a stretch on the SOW, and installing Fedora is a change to needs Red Hat's OK. Thanks
-------------- next part -------------- An HTML attachment was scrubbed... URL: From edewata at redhat.com Fri Sep 4 21:47:00 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Fri, 4 Sep 2015 16:47:00 -0500 Subject: [Pki-devel] [PATCH] 641 Added CLI to update cert data and request in CS.cfg. In-Reply-To: <55E66AE5.3050304@redhat.com> References: <55E66AE5.3050304@redhat.com> Message-ID: <55EA1154.40905@redhat.com> On 9/1/2015 10:20 PM, Endi Sukma Dewata wrote: > A set of new pki-server commands have been added to simplify > updating the cert data and cert request stored in the CS.cfg with > the cert data and cert request stored in the NSS and LDAP database, > respectively. > > https://fedorahosted.org/pki/ticket/1551 ACKed by mharmsen. Fixed some pylint warnings. Pushed to master. -- Endi S. Dewata -------------- next part -------------- >From 9f7d83f0cfda059e7ceca41d7887c46bebb08f14 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Wed, 2 Sep 2015 04:50:24 +0200 Subject: [PATCH] Added CLI to update cert data and request in CS.cfg. A set of new pki-server commands have been added to simplify updating the cert data and cert request stored in the CS.cfg with the cert data and cert request stored in the NSS and LDAP database, respectively. https://fedorahosted.org/pki/ticket/1551 --- base/server/python/pki/server/__init__.py | 115 ++++++++- base/server/python/pki/server/ca.py | 92 ++++++++ base/server/python/pki/server/cli/ca.py | 206 ++++++++++++++++ base/server/python/pki/server/cli/subsystem.py | 313 +++++++++++++++++++++---- base/server/python/pki/server/upgrade.py | 3 + base/server/sbin/pki-server | 2 + 6 files changed, 684 insertions(+), 47 deletions(-) create mode 100644 base/server/python/pki/server/ca.py create mode 100644 base/server/python/pki/server/cli/ca.py diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index 97b2e97992ad1daf0313127ce89f826cc3d0fb10..70e35b8f2c7bc66339dc315dcab946177d87d1a3 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -21,7 +21,11 @@ from __future__ import absolute_import from lxml import etree +import getpass import grp +import io +import ldap +import operator import os import pwd import re @@ -32,7 +36,7 @@ import pki INSTANCE_BASE_DIR = '/var/lib/pki' REGISTRY_DIR = '/etc/sysconfig/pki' SUBSYSTEM_TYPES = ['ca', 'kra', 'ocsp', 'tks', 'tps'] - +SUBSYSTEM_CLASSES = {} class PKIServer(object): @@ -66,6 +70,7 @@ class PKISubsystem(object): self.base_dir = instance.base_dir self.conf_dir = os.path.join(self.base_dir, 'conf') + self.cs_conf = os.path.join(self.conf_dir, 'CS.cfg') self.context_xml_template = os.path.join( pki.SHARE_DIR, self.name, 'conf', 'Catalina', 'localhost', self.name + '.xml') @@ -73,9 +78,62 @@ class PKISubsystem(object): self.context_xml = os.path.join( instance.conf_dir, 'Catalina', 'localhost', self.name + '.xml') + self.config = {} + self.type = None + self.prefix = None + # custom subsystem location self.doc_base = os.path.join(self.base_dir, 'webapps', self.name) + def load(self): + self.config.clear() + + lines = open(self.cs_conf).read().splitlines() + + for line in lines: + parts = line.split('=', 1) + name = parts[0] + value = parts[1] + self.config[name] = value + + self.type = self.config['cs.type'] + self.prefix = self.type.lower() + + def find_subsystem_certs(self): + certs = [] + + cert_ids = self.config['%s.cert.list' % self.name].split(',') + for cert_id in cert_ids: + cert = self.create_subsystem_cert_object(cert_id) + certs.append(cert) + + return certs + + def get_subsystem_cert(self, cert_id): + return self.create_subsystem_cert_object(cert_id) + + def create_subsystem_cert_object(self, cert_id): + cert = {} + cert['id'] = cert_id + cert['nickname'] = self.config.get('%s.%s.nickname' % (self.name, cert_id), None) + cert['token'] = self.config.get('%s.%s.tokenname' % (self.name, cert_id), None) + cert['data'] = self.config.get('%s.%s.cert' % (self.name, cert_id), None) + cert['request'] = self.config.get('%s.%s.certreq' % (self.name, cert_id), None) + return cert + + def update_subsystem_cert(self, cert): + cert_id = cert['id'] + self.config['%s.%s.nickname' % (self.name, cert_id)] = cert.get('nickname', None) + self.config['%s.%s.tokenname' % (self.name, cert_id)] = cert.get('token', None) + 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 save(self): + sorted_config = sorted(self.config.items(), key=operator.itemgetter(0)) + with io.open(self.cs_conf, 'wb') as f: + for (key, value) in sorted_config: + f.write('%s=%s\n' % (key, value)) + def is_valid(self): return os.path.exists(self.conf_dir) @@ -103,6 +161,21 @@ class PKISubsystem(object): def disable(self): self.instance.undeploy(self.name) + def open_database(self, name='internaldb'): + + hostname = self.config['%s.ldapconn.host' % name] + port = self.config['%s.ldapconn.port' % name] + bind_dn = self.config['%s.ldapauth.bindDN' % name] + + # TODO: add support for other authentication + # mechanisms (e.g. client cert authentication, LDAPI) + bind_password = self.instance.get_password(name) + + con = ldap.initialize('ldap://%s:%s' % (hostname, port)) + con.simple_bind_s(bind_dn, bind_password) + + return con + def __repr__(self): return str(self.instance) + '/' + self.name @@ -120,6 +193,9 @@ class PKIInstance(object): self.base_dir = os.path.join(pki.BASE_DIR, name) self.conf_dir = os.path.join(self.base_dir, 'conf') + self.password_conf = os.path.join(self.conf_dir, 'password.conf') + + self.nssdb_dir = os.path.join(self.base_dir, 'alias') self.lib_dir = os.path.join(self.base_dir, 'lib') self.registry_dir = os.path.join( @@ -136,6 +212,8 @@ class PKIInstance(object): self.uid = None self.gid = None + self.passwords = {} + self.subsystems = [] def is_valid(self): @@ -158,6 +236,7 @@ class PKIInstance(object): return rc == 0 def load(self): + # load UID and GID with open(self.registry_file, 'r') as registry: lines = registry.readlines() @@ -173,11 +252,41 @@ class PKIInstance(object): 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() + + 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 pki.server.SUBSYSTEM_TYPES: - subsystem = PKISubsystem(self, subsystem_name) + 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: + return self.passwords[name] + + password = getpass.getpass(prompt='Enter password for %s: ' % name) + self.passwords[name] = password + + return password + + def get_subsystem(self, name): + for subsystem in self.subsystems: + if name == subsystem.name: + return subsystem + return None + def is_deployed(self, webapp_name): context_xml = os.path.join( self.conf_dir, 'Catalina', 'localhost', webapp_name + '.xml') diff --git a/base/server/python/pki/server/ca.py b/base/server/python/pki/server/ca.py new file mode 100644 index 0000000000000000000000000000000000000000..70ebf4dd1044ecad32a0cf072ba45c6ace5eacea --- /dev/null +++ b/base/server/python/pki/server/ca.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# 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. +# + +from __future__ import absolute_import +import ldap +import ldap.filter + +import pki +import pki.server + + +class CASubsystem(pki.server.PKISubsystem): + + def __init__(self, instance): + super(CASubsystem, self).__init__(instance, 'ca') + + def find_cert_requests(self, cert=None): + + base_dn = self.config['internaldb.basedn'] + + if cert: + escaped_value = ldap.filter.escape_filter_chars(cert) + search_filter = '(extdata-req--005fissued--005fcert=%s)' % escaped_value + + else: + search_filter = '(objectClass=*)' + + con = self.open_database() + + entries = con.search_s( + 'ou=ca,ou=requests,%s' % base_dn, + ldap.SCOPE_ONELEVEL, + search_filter, + None) + + con.unbind_s() + + requests = [] + for entry in entries: + requests.append(self.create_request_object(entry)) + + return requests + + def get_cert_requests(self, request_id): + + base_dn = self.config['internaldb.basedn'] + + con = self.open_database() + + entries = con.search_s( + 'cn=%s,ou=ca,ou=requests,%s' % (request_id, base_dn), + ldap.SCOPE_BASE, + '(objectClass=*)', + None) + + con.unbind_s() + + entry = entries[0] + return self.create_request_object(entry) + + def create_request_object(self, entry): + + attrs = entry[1] + + request = {} + request['id'] = attrs['cn'][0] + request['type'] = attrs['requestType'][0] + request['status'] = attrs['requestState'][0] + request['request'] = attrs['extdata-cert--005frequest'][0] + + return request + + +pki.server.SUBSYSTEM_CLASSES['ca'] = CASubsystem diff --git a/base/server/python/pki/server/cli/ca.py b/base/server/python/pki/server/cli/ca.py new file mode 100644 index 0000000000000000000000000000000000000000..2ad8652f4fddd032779941cb7e2ae4e643c25e0a --- /dev/null +++ b/base/server/python/pki/server/cli/ca.py @@ -0,0 +1,206 @@ +#!/usr/bin/python +# 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. +# + +from __future__ import absolute_import +from __future__ import print_function +import getopt +import io +import sys + +import pki.cli +import pki.server.ca + + +class CACLI(pki.cli.CLI): + + def __init__(self): + super(CACLI, self).__init__( + 'ca', 'CA management commands') + + self.add_module(CACertCLI()) + + +class CACertCLI(pki.cli.CLI): + + def __init__(self): + super(CACertCLI, self).__init__( + 'cert', 'CA certificates management commands') + + self.add_module(CACertRequestCLI()) + + +class CACertRequestCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestCLI, self).__init__( + 'request', 'CA certificate requests management commands') + + self.add_module(CACertRequestFindCLI()) + self.add_module(CACertRequestShowCLI()) + + @staticmethod + def print_request(request, details=False): + print(' Request ID: %s' % request['id']) + print(' Type: %s' % request['type']) + print(' Status: %s' % request['status']) + + if details: + print(' Request: %s' % request['request']) + + +class CACertRequestFindCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestFindCLI, self).__init__( + 'find', 'Find CA certificate requests') + + def usage(self): + print('Usage: pki-server ca-cert-request-find [OPTIONS]') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' --cert Issued certificate.') + print(' --cert-file File containing issued certificate.') + 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=', 'cert=', 'cert-file=', + 'verbose', 'help']) + + except getopt.GetoptError as e: + print('ERROR: ' + str(e)) + self.usage() + sys.exit(1) + + instance_name = 'pki-tomcat' + cert = None + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = a + + elif o == '--cert': + cert = a + + elif o == '--cert-file': + with io.open(a, 'rb') as f: + cert = 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.usage() + sys.exit(1) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem('ca') + results = subsystem.find_cert_requests(cert=cert) + + self.print_message('%s entries matched' % len(results)) + + first = True + for request in results: + if first: + first = False + else: + print() + + CACertRequestCLI.print_request(request) + + +class CACertRequestShowCLI(pki.cli.CLI): + + def __init__(self): + super(CACertRequestShowCLI, self).__init__( + 'show', 'Show CA certificate request') + + def usage(self): + print('Usage: pki-server ca-cert-request-show [OPTIONS]') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, args): + + try: + opts, args = getopt.gnu_getopt(args, 'i:v', [ + 'instance=', 'output-file=', + 'verbose', 'help']) + + except getopt.GetoptError as e: + print('ERROR: ' + str(e)) + self.usage() + sys.exit(1) + + if len(args) != 1: + print('ERROR: missing request ID') + self.usage() + sys.exit(1) + + request_id = args[0] + instance_name = 'pki-tomcat' + output_file = None + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = a + + elif o == '--output-file': + output_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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem('ca') + request = subsystem.get_cert_requests(request_id) + + if output_file: + with io.open(output_file, 'wb') as f: + f.write(request['request']) + + else: + CACertRequestCLI.print_request(request, details=True) diff --git a/base/server/python/pki/server/cli/subsystem.py b/base/server/python/pki/server/cli/subsystem.py index b1c20af59de460cc9d8e9ae4a6288787082cc02a..688a5c6ed0bd7d93b87163f31e926ba4d8deca48 100644 --- a/base/server/python/pki/server/cli/subsystem.py +++ b/base/server/python/pki/server/cli/subsystem.py @@ -21,8 +21,10 @@ from __future__ import absolute_import from __future__ import print_function +import base64 import getopt -import os +import nss.nss as nss +import string import sys import pki.cli @@ -40,6 +42,8 @@ class SubsystemCLI(pki.cli.CLI): self.add_module(SubsystemFindCLI()) self.add_module(SubsystemShowCLI()) + self.add_module(SubsystemCertCLI()) + @staticmethod def print_subsystem(subsystem): print(' Subsystem ID: %s' % subsystem.name) @@ -55,7 +59,7 @@ class SubsystemFindCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-find [OPTIONS]') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -72,7 +76,7 @@ class SubsystemFindCLI(pki.cli.CLI): self.usage() sys.exit(1) - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -90,28 +94,13 @@ class SubsystemFindCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - results = [] - - for name in os.listdir(instance.base_dir): - - subsystem = pki.server.PKISubsystem(instance, name) - if not subsystem.is_valid(): - continue - - results.append(subsystem) - - self.print_message('%s entries matched' % len(results)) + self.print_message('%s entries matched' % len(instance.subsystems)) first = True - for subsystem in results: + for subsystem in instance.subsystems: if first: first = False else: @@ -128,7 +117,7 @@ class SubsystemShowCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-show [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -151,7 +140,7 @@ class SubsystemShowCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -169,15 +158,10 @@ class SubsystemShowCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) SubsystemCLI.print_subsystem(subsystem) @@ -190,7 +174,7 @@ class SubsystemEnableCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-enable [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -213,7 +197,7 @@ class SubsystemEnableCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -231,15 +215,10 @@ class SubsystemEnableCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) subsystem.enable() self.print_message('Enabled "%s" subsystem' % subsystem_name) @@ -257,7 +236,7 @@ class SubsystemDisableCLI(pki.cli.CLI): def usage(self): print('Usage: pki-server subsystem-disable [OPTIONS] ') print() - print(' -i, --instance Instance ID.') + print(' -i, --instance Instance ID (default: pki-tomcat).') print(' -v, --verbose Run in verbose mode.') print(' --help Show help message.') print() @@ -280,7 +259,7 @@ class SubsystemDisableCLI(pki.cli.CLI): sys.exit(1) subsystem_name = args[0] - instance_name = None + instance_name = 'pki-tomcat' for o, a in opts: if o in ('-i', '--instance'): @@ -298,17 +277,263 @@ class SubsystemDisableCLI(pki.cli.CLI): self.usage() sys.exit(1) - if not instance_name: - print('ERROR: missing instance ID') - self.usage() - sys.exit(1) - instance = pki.server.PKIInstance(instance_name) instance.load() - subsystem = pki.server.PKISubsystem(instance, subsystem_name) + subsystem = instance.get_subsystem(subsystem_name) subsystem.disable() self.print_message('Disabled "%s" subsystem' % subsystem_name) SubsystemCLI.print_subsystem(subsystem) + + +class SubsystemCertCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertCLI, self).__init__( + 'cert', 'Subsystem certificate management commands') + + self.add_module(SubsystemCertFindCLI()) + self.add_module(SubsystemCertShowCLI()) + self.add_module(SubsystemCertUpdateCLI()) + + @staticmethod + def print_subsystem_cert(cert): + 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']) + + +class SubsystemCertFindCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertFindCLI, self).__init__( + 'find', 'Find subsystem certificates') + + def usage(self): + print('Usage: pki-server subsystem-cert-find [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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) + + subsystem_name = args[0] + instance_name = 'pki-tomcat' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + results = subsystem.find_subsystem_certs() + + self.print_message('%s entries matched' % len(results)) + + first = True + for cert in results: + if first: + first = False + else: + print() + + SubsystemCertCLI.print_subsystem_cert(cert) + + +class SubsystemCertShowCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertShowCLI, self).__init__( + 'show', 'Show subsystem certificate') + + def usage(self): + print('Usage: pki-server subsystem-cert-show [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + subsystem_cert = subsystem.get_subsystem_cert(cert_id) + + SubsystemCertCLI.print_subsystem_cert(subsystem_cert) + + +class SubsystemCertUpdateCLI(pki.cli.CLI): + + def __init__(self): + super(SubsystemCertUpdateCLI, self).__init__( + 'update', 'Update subsystem certificate') + + def usage(self): + print('Usage: pki-server subsystem-cert-update [OPTIONS] ') + print() + print(' -i, --instance Instance ID (default: pki-tomcat).') + print(' -v, --verbose Run in verbose mode.') + print(' --help Show help message.') + print() + + def execute(self, argv): + + try: + opts, args = getopt.getopt(argv, 'i:v', [ + 'instance=', + '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' + + for o, a in opts: + if o in ('-i', '--instance'): + instance_name = 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) + + instance = pki.server.PKIInstance(instance_name) + instance.load() + + subsystem = instance.get_subsystem(subsystem_name) + subsystem_cert = subsystem.get_subsystem_cert(cert_id) + + # get cert data from NSS database + nss.nss_init(instance.nssdb_dir) + nss_cert = nss.find_cert_from_nickname(subsystem_cert['nickname']) + data = base64.b64encode(nss_cert.der_data) + del nss_cert + nss.nss_shutdown() + subsystem_cert['data'] = data + + # format cert data for LDAP database + lines = [data[i:i+64] for i in range(0, len(data), 64)] + data = string.join(lines, '\r\n') + '\r\n' + + # get cert request from local CA + # TODO: add support for remote CA + ca = instance.get_subsystem('ca') + results = ca.find_cert_requests(cert=data) + cert_request = results[-1] + request = cert_request['request'] + + # format cert request for CS.cfg + lines = request.splitlines() + if lines[0] == '-----BEGIN CERTIFICATE REQUEST-----': + lines = lines[1:] + if lines[-1] == '-----END CERTIFICATE REQUEST-----': + lines = lines[:-1] + request = string.join(lines, '') + subsystem_cert['request'] = request + + # store cert data and request in CS.cfg + subsystem.update_subsystem_cert(subsystem_cert) + subsystem.save() + + self.print_message('Updated "%s" subsystem certificate' % cert_id) + + SubsystemCertCLI.print_subsystem_cert(subsystem_cert) diff --git a/base/server/python/pki/server/upgrade.py b/base/server/python/pki/server/upgrade.py index a0e52b22b87035f6b39c388002f63eb8af2eb4ff..ffe7c891d92c4fd16a5e42bdc4d268c2251a0604 100644 --- a/base/server/python/pki/server/upgrade.py +++ b/base/server/python/pki/server/upgrade.py @@ -215,6 +215,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): if self.subsystemName: subsystem = pki.server.PKISubsystem(instance, self.subsystemName) subsystem.validate() + subsystem.load() return [subsystem] subsystem_list = [] @@ -229,6 +230,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): instance, subsystemName) subsystem.validate() + subsystem.load() subsystem_list.append(subsystem) else: for subsystemName in pki.server.SUBSYSTEM_TYPES: @@ -241,6 +243,7 @@ class PKIServerUpgrader(pki.upgrade.PKIUpgrader): instance, subsystemName) subsystem.validate() + subsystem.load() subsystem_list.append(subsystem) subsystem_list.sort() diff --git a/base/server/sbin/pki-server b/base/server/sbin/pki-server index d479788debbaed8c58f977023463b3033636e1c3..f3e784a1539a4cff82cdc765da53f2cb01b27a3c 100644 --- a/base/server/sbin/pki-server +++ b/base/server/sbin/pki-server @@ -25,6 +25,7 @@ import getopt import sys import pki.cli +import pki.server.cli.ca import pki.server.cli.instance import pki.server.cli.subsystem import pki.server.cli.migrate @@ -38,6 +39,7 @@ class PKIServerCLI(pki.cli.CLI): 'pki-server', 'PKI server command-line interface') + self.add_module(pki.server.cli.ca.CACLI()) self.add_module(pki.server.cli.instance.InstanceCLI()) self.add_module(pki.server.cli.subsystem.SubsystemCLI()) self.add_module(pki.server.cli.migrate.MigrateCLI()) -- 2.4.3 From edewata at redhat.com Fri Sep 11 21:46:57 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Fri, 11 Sep 2015 16:46:57 -0500 Subject: [Pki-devel] [PATCH] 642 Added support for secure database connection in CLI. Message-ID: <55F34BD1.7080109@redhat.com> The pki-server subsystem-cert-update has been modified to support secure database connection with client certificate authentication. The certificate and the private key will be exported temporarily into PEM files so python-ldap can use them. The pki client-cert-show has been modified to provide an option to export client certificate's private key. https://fedorahosted.org/pki/ticket/1551 -- Endi S. Dewata -------------- next part -------------- From cd0619e2074a51fa1525f2b4b83458dfd6a1a2d6 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Fri, 4 Sep 2015 06:30:27 +0200 Subject: [PATCH] Added support for secure database connection in CLI. The pki-server subsystem-cert-update has been modified to support secure database connection with client certificate authentication. The certificate and the private key will be exported temporarily into PEM files so python-ldap can use them. The pki client-cert-show has been modified to provide an option to export client certificate's private key. https://fedorahosted.org/pki/ticket/1551 --- .../cmstools/client/ClientCertShowCLI.java | 176 +++++++++++++-------- base/server/python/pki/server/__init__.py | 120 +++++++++++++- base/server/python/pki/server/ca.py | 8 +- 3 files changed, 225 insertions(+), 79 deletions(-) diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java index f79501cfc62bace815ade9a24196f877857c1aed..e44fae7457ac226711fdf9f19cf20722d3aab296 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java @@ -29,10 +29,8 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.mozilla.jss.crypto.X509Certificate; -import com.netscape.certsrv.cert.CertData; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; -import com.netscape.cmsutil.util.Utils; /** * @author Endi S. Dewata @@ -57,6 +55,10 @@ public class ClientCertShowCLI extends CLI { option.setArgName("path"); options.addOption(option); + option = new Option(null, "private-key", true, "PEM file to store the private key."); + option.setArgName("path"); + options.addOption(option); + option = new Option(null, "client-cert", true, "PEM file to store the certificate and the private key."); option.setArgName("path"); options.addOption(option); @@ -107,90 +109,82 @@ public class ClientCertShowCLI extends CLI { String nickname = cmdArgs[0]; String certPath = cmd.getOptionValue("cert"); + String privateKeyPath = cmd.getOptionValue("private-key"); + String clientCertPath = cmd.getOptionValue("client-cert"); String pkcs12Path = cmd.getOptionValue("pkcs12"); String pkcs12Password = cmd.getOptionValue("pkcs12-password"); - String clientCertPath = cmd.getOptionValue("client-cert"); - if (certPath != null) { + File pkcs12File; - if (verbose) System.out.println("Exporting certificate to " + clientCertPath + "."); + if (pkcs12Path != null) { + // exporting certificate to PKCS #12 file - // late initialization - mainCLI.init(); - - client = mainCLI.getClient(); - X509Certificate cert = client.getCert(nickname); - - try (PrintWriter out = new PrintWriter(new FileWriter(certPath))) { - out.println(CertData.HEADER); - out.println(Utils.base64encode(cert.getEncoded())); - out.println(CertData.FOOTER); - } - - } else if (pkcs12Path != null) { - - if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12Path + "."); + pkcs12File = new File(pkcs12Path); if (pkcs12Password == null) { throw new Exception("Missing PKCS #12 password"); } - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); + } else if (certPath != null || clientCertPath != null || privateKeyPath != null) { + // exporting certificate and/or private key to PEM files using temporary PKCS #12 file - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } - - // export certificate chain and private key into PKCS #12 file - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12Path, - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - } else if (clientCertPath != null) { - - if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); - - // generate random PKCS #12 password - pkcs12Password = RandomStringUtils.randomAlphanumeric(16); - - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); - - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } - - // export certificate chain and private key into a temporary PKCS #12 file - File pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); + // prepare temporary PKCS #12 file + pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); pkcs12File.deleteOnExit(); - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - // export client certificate and private key into a PEM file - exportClientCertificate( - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - clientCertPath); + // generate random password + pkcs12Password = RandomStringUtils.randomAlphanumeric(16); } else { - // late initialization + // displaying certificate info + mainCLI.init(); client = mainCLI.getClient(); X509Certificate cert = client.getCert(nickname); ClientCLI.printCertInfo(cert); + return; + } + + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12File + "."); + exportPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + nickname); + + if (certPath != null) { + if (verbose) System.out.println("Exporting certificate to " + certPath + "."); + exportCertificate( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + certPath); + } + + if (privateKeyPath != null) { + if (verbose) System.out.println("Exporting private key to " + privateKeyPath + "."); + exportPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + privateKeyPath); + } + + if (clientCertPath != null) { + if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); + exportClientCertificateAndPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + clientCertPath); } } @@ -218,7 +212,53 @@ public class ClientCertShowCLI extends CLI { } } - public void exportClientCertificate( + public void exportCertificate( + String pkcs12Path, + String pkcs12PasswordPath, + String certPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-clcerts", // certificate only + "-nokeys", + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", certPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export certificate", e); + } + } + + public void exportPrivateKey( + String pkcs12Path, + String pkcs12PasswordPath, + String privateKeyPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-nocerts", // private key only + "-nodes", // no encryption + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", privateKeyPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export private key", e); + } + } + + public void exportClientCertificateAndPrivateKey( String pkcs12Path, String pkcs12PasswordPath, String clientCertPath) throws Exception { @@ -226,7 +266,7 @@ public class ClientCertShowCLI extends CLI { String[] command = { "/bin/openssl", "pkcs12", - "-clcerts", // client certificate only + "-clcerts", // client certificate and private key "-nodes", // no encryption "-in", pkcs12Path, "-passin", "file:" + pkcs12PasswordPath, @@ -237,7 +277,7 @@ public class ClientCertShowCLI extends CLI { run(command); } catch (Exception e) { - throw new Exception("Unable to export client certificate", e); + throw new Exception("Unable to export client certificate and private key", e); } } diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index 70e35b8f2c7bc66339dc315dcab946177d87d1a3..1c8943d171fbae98897105f468567ab961a9c566 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -29,7 +29,9 @@ import operator import os import pwd import re +import shutil import subprocess +import tempfile import pki @@ -163,18 +165,46 @@ class PKISubsystem(object): def open_database(self, name='internaldb'): + # TODO: add LDAPI support hostname = self.config['%s.ldapconn.host' % name] port = self.config['%s.ldapconn.port' % name] - bind_dn = self.config['%s.ldapauth.bindDN' % name] + secure = self.config['%s.ldapconn.secureConn' % name] - # TODO: add support for other authentication - # mechanisms (e.g. client cert authentication, LDAPI) - bind_password = self.instance.get_password(name) + if secure == 'true': + url = 'ldaps://%s:%s' % (hostname, port) - con = ldap.initialize('ldap://%s:%s' % (hostname, port)) - con.simple_bind_s(bind_dn, bind_password) + elif secure == 'false': + url = 'ldap://%s:%s' % (hostname, port) - return con + else: + raise Exception('Invalid parameter value in %s.ldapconn.secureConn: %s' % (name, secure)) + + connection = PKIDatabaseConnection(url) + + connection.set_security_database( + self.instance.nssdb_dir, + self.instance.password_conf, + self.config['%s.cert.signing.nickname' % self.name] + ) + + auth_type = self.config['%s.ldapauth.authtype' % name] + if auth_type == 'BasicAuth': + connection.set_credentials( + bind_dn=self.config['%s.ldapauth.bindDN' % name], + bind_password=self.instance.get_password(name) + ) + + elif auth_type == 'SslClientAuth': + connection.set_credentials( + client_cert_nickname=self.config['%s.ldapauth.clientCertNickname' % name] + ) + + else: + raise Exception('Invalid parameter value in %s.ldapauth.authtype: %s' % (name, auth_type)) + + connection.open() + + return connection def __repr__(self): return str(self.instance) + '/' + self.name @@ -343,6 +373,82 @@ class PKIInstance(object): return self.name +class PKIDatabaseConnection(object): + + def __init__(self, url='ldap://localhost:389'): + + self.url = url + + self.nssdb_dir = None + self.nssdb_password_file = None + self.ca_cert_nickname = None + + self.bind_dn = None + self.bind_password = None + self.client_cert_nickname = None + + self.temp_dir = None + self.ldap = None + + def set_security_database(self, nssdb_dir=None, nssdb_password_file=None, ca_cert_nickname=None): + self.nssdb_dir = nssdb_dir + self.nssdb_password_file = nssdb_password_file + self.ca_cert_nickname = ca_cert_nickname + + def set_credentials(self, bind_dn=None, bind_password=None, client_cert_nickname=None): + self.bind_dn = bind_dn + self.bind_password = bind_password + self.client_cert_nickname = client_cert_nickname + + def open(self): + + self.temp_dir = tempfile.mkdtemp() + + if self.ca_cert_nickname: + + ca_cert_file = os.path.join(self.temp_dir, 'ca.pem') + + subprocess.check_call([ + '/usr/bin/pki', + '-d', self.nssdb_dir, + '-C', self.nssdb_password_file, + 'client-cert-show', self.ca_cert_nickname, + '--cert', ca_cert_file + ]) + + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_file) + + if self.client_cert_nickname: + + client_cert_file = os.path.join(self.temp_dir, 'cert.pem') + private_key_file = os.path.join(self.temp_dir, 'key.pem') + + subprocess.check_call([ + '/usr/bin/pki', + '-d', self.nssdb_dir, + '-C', self.nssdb_password_file, + 'client-cert-show', self.client_cert_nickname, + '--cert', client_cert_file, + '--private-key', private_key_file + ]) + + ldap.set_option(ldap.OPT_X_TLS_CERTFILE, client_cert_file) + ldap.set_option(ldap.OPT_X_TLS_KEYFILE, private_key_file) + + self.ldap = ldap.initialize(self.url) + + if self.bind_dn and self.bind_password: + self.ldap.simple_bind_s(self.bind_dn, self.bind_password) + + def close(self): + + if self.ldap: + self.ldap.unbind_s() + + if self.temp_dir: + shutil.rmtree(self.temp_dir) + + class PKIServerException(pki.PKIException): def __init__(self, message, exception=None, diff --git a/base/server/python/pki/server/ca.py b/base/server/python/pki/server/ca.py index 70ebf4dd1044ecad32a0cf072ba45c6ace5eacea..31e373ad85955fd1e64b08c8f5aedba8386384fa 100644 --- a/base/server/python/pki/server/ca.py +++ b/base/server/python/pki/server/ca.py @@ -45,13 +45,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'ou=ca,ou=requests,%s' % base_dn, ldap.SCOPE_ONELEVEL, search_filter, None) - con.unbind_s() + con.close() requests = [] for entry in entries: @@ -65,13 +65,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'cn=%s,ou=ca,ou=requests,%s' % (request_id, base_dn), ldap.SCOPE_BASE, '(objectClass=*)', None) - con.unbind_s() + con.close() entry = entries[0] return self.create_request_object(entry) -- 2.4.3 From edewata at redhat.com Sat Sep 12 00:16:32 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Fri, 11 Sep 2015 19:16:32 -0500 Subject: [Pki-devel] [PATCH] 643 Fixed pkidbuser group memberships. Message-ID: <55F36EE0.8070808@redhat.com> Due to a certificate mapping issue the subsystem certificate can be mapped into either the subsystem user or pkidbuser, which may cause problems since the users don't belong to the same groups. As a temporary solution the pkidbuser is now added into the same groups. This way the client subsystem can always access the services regardless of which user the certificate is actually mapped to. https://fedorahosted.org/pki/ticket/1595 -- Endi S. Dewata -------------- next part -------------- From 417a189254c3f726109fc8f66c1f9f2e5a86a096 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Fri, 11 Sep 2015 22:59:55 +0200 Subject: [PATCH] Fixed pkidbuser group memberships. Due to a certificate mapping issue the subsystem certificate can be mapped into either the subsystem user or pkidbuser, which may cause problems since the users don't belong to the same groups. As a temporary solution the pkidbuser is now added into the same groups. This way the client subsystem can always access the services regardless of which user the certificate is actually mapped to. https://fedorahosted.org/pki/ticket/1595 --- .../cms/servlet/csadmin/ConfigurationUtils.java | 87 +++++++++++++++------- 1 file changed, 59 insertions(+), 28 deletions(-) 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 a417be4a390b9177ea91c4db09347d0aa6feca4a..7b5bef567de773c0cd5b86ba6e36a1c16f444012 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 @@ -50,6 +50,7 @@ import java.security.cert.CertificateNotYetValidException; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.StringTokenizer; @@ -62,32 +63,6 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.xml.parsers.ParserConfigurationException; -import netscape.ldap.LDAPAttribute; -import netscape.ldap.LDAPAttributeSet; -import netscape.ldap.LDAPConnection; -import netscape.ldap.LDAPDN; -import netscape.ldap.LDAPEntry; -import netscape.ldap.LDAPException; -import netscape.ldap.LDAPModification; -import netscape.ldap.LDAPSearchConstraints; -import netscape.ldap.LDAPSearchResults; -import netscape.ldap.LDAPv3; -import netscape.security.pkcs.ContentInfo; -import netscape.security.pkcs.PKCS10; -import netscape.security.pkcs.PKCS7; -import netscape.security.pkcs.SignerInfo; -import netscape.security.util.DerOutputStream; -import netscape.security.util.ObjectIdentifier; -import netscape.security.x509.AlgorithmId; -import netscape.security.x509.BasicConstraintsExtension; -import netscape.security.x509.CertificateChain; -import netscape.security.x509.Extension; -import netscape.security.x509.Extensions; -import netscape.security.x509.KeyUsageExtension; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509Key; - import org.apache.commons.lang.StringUtils; import org.apache.velocity.context.Context; import org.mozilla.jss.CryptoManager; @@ -181,6 +156,32 @@ import com.netscape.cmsutil.http.JssSSLSocketFactory; import com.netscape.cmsutil.ldap.LDAPUtil; import com.netscape.cmsutil.xml.XMLObject; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPDN; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPSearchConstraints; +import netscape.ldap.LDAPSearchResults; +import netscape.ldap.LDAPv3; +import netscape.security.pkcs.ContentInfo; +import netscape.security.pkcs.PKCS10; +import netscape.security.pkcs.PKCS7; +import netscape.security.pkcs.SignerInfo; +import netscape.security.util.DerOutputStream; +import netscape.security.util.ObjectIdentifier; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.BasicConstraintsExtension; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.Extension; +import netscape.security.x509.Extensions; +import netscape.security.x509.KeyUsageExtension; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509Key; + /** * Utility class for functions to be used both by the RESTful installer * and the UI Panels. @@ -3930,7 +3931,7 @@ public class ConfigurationUtils { String groupName = "Trusted Managers"; IGroup group = system.getGroupFromName(groupName); if (!group.isMember(id)) { - CMS.debug("setupClientAuthUser: adding user to the trusted managers group."); + CMS.debug("setupClientAuthUser: adding user to the " + groupName + " group."); group.addMemberName(id); system.modifyGroup(group); } @@ -4156,7 +4157,7 @@ public class ConfigurationUtils { user.setX509Certificates(certs); system.addUser(user); - CMS.debug("setupDBUser(): successfully added the user"); + CMS.debug("setupDBUser(): successfully added " + DBUSER); system.addUserCert(user); CMS.debug("setupDBUser(): successfully add the user certificate"); @@ -4167,6 +4168,36 @@ public class ConfigurationUtils { // remove old db users CMS.debug("setupDBUser(): removing seeAlso from old dbusers"); removeOldDBUsers(certs[0].getSubjectDN().toString()); + + // workaround for ticket #1595 + IConfigStore cs = CMS.getConfigStore(); + String csType = cs.getString("cs.type").toUpperCase(); + + Collection groupNames = new ArrayList(); + + if ("CA".equals(csType)) { + groupNames.add("Subsystem Group"); + groupNames.add("Certificate Manager Agents"); + + } else if ("KRA".equals(csType)) { + groupNames.add("Data Recovery Manager Agents"); + groupNames.add("Trusted Managers"); + + } else if ("OCSP".equals(csType)) { + groupNames.add("Trusted Managers"); + + } else if ("TKS".equals(csType)) { + groupNames.add("Token Key Service Manager Agents"); + } + + for (String groupName : groupNames) { + IGroup group = system.getGroupFromName(groupName); + if (!group.isMember(DBUSER)) { + CMS.debug("setupDBUser(): adding " + DBUSER + " to the " + groupName + " group."); + group.addMemberName(DBUSER); + system.modifyGroup(group); + } + } } public static void addProfilesToTPSUser(String adminID) throws EUsrGrpException, LDAPException { -- 2.4.3 From edewata at redhat.com Sat Sep 12 15:05:11 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Sat, 12 Sep 2015 10:05:11 -0500 Subject: [Pki-devel] [PATCH] 0046 API: add support for generic entities In-Reply-To: <20150821060552.GS16439@dhcp-40-8.bne.redhat.com> References: <20150821060552.GS16439@dhcp-40-8.bne.redhat.com> Message-ID: <55F43F27.2020607@redhat.com> On 8/21/2015 1:05 AM, Fraser Tweedale wrote: > This patch adds support to retrieving generic entities (e.g. > List) from Response objects. > > Thanks, > Fraser ACK. -- Endi S. Dewata From ftweedal at redhat.com Mon Sep 14 03:38:20 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Mon, 14 Sep 2015 13:38:20 +1000 Subject: [Pki-devel] [PATCH] 0046 API: add support for generic entities In-Reply-To: <55F43F27.2020607@redhat.com> References: <20150821060552.GS16439@dhcp-40-8.bne.redhat.com> <55F43F27.2020607@redhat.com> Message-ID: <20150914033819.GJ17582@dhcp-40-8.bne.redhat.com> On Sat, Sep 12, 2015 at 10:05:11AM -0500, Endi Sukma Dewata wrote: > On 8/21/2015 1:05 AM, Fraser Tweedale wrote: > >This patch adds support to retrieving generic entities (e.g. > >List) from Response objects. > > > >Thanks, > >Fraser > > ACK. > Thanks, pushed to master (e78f3d3c1e1145112c7dbf9ea8884917ddaacb17). > -- > Endi S. Dewata From ftweedal at redhat.com Mon Sep 14 07:25:00 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Mon, 14 Sep 2015 17:25:00 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs Message-ID: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> The latest lightweight CAs (f.k.a. Sub-CAs) patches are attached. This cut has significant changes and additions including: - CAs are now stored in a flat structure with references to parents - CAs are now identified by "AuthorityID" (which for now is a UUID underneath). Many variables, method names and user-visible strings were updated accordingly. Out with "caRef" terminology. - "Sub-CA" terminology is (mostly) out; "Authority" is in. This is to support lightweight CAs that are not descendents of top-level CA (which can be implemented later). - ca-cert-request-submit command and related client / service classes were updated to add "authority" parameter - Some more specific use of exception (including some new exception classes) to indicate / catch particular errors. - More appropriate HTTP status codes return when client has send invalid data (400), referenced unknown authority (404) or attempts to create an authority with Subject DN already uesd by another authority (409 Conflict) - LDAP entry now gets added before key generation and signing. If something goes wrong, the DB entry is removed in the catch block. There are still notable gaps in functionality that are in progress or will be implemented soon: - Audit log events - Resources to enable/disable/delete authority - Resources to access cert and pkcs7 chain of authority - Keygen params - Param to specify token on which to generate authority key Thanks, Fraser -------------- next part -------------- From b4d1dd0ac041eab66028661d6c6ab5117ca3980c Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Add lightweight sub-CA support --- base/ca/shared/conf/db.ldif | 5 + base/ca/src/com/netscape/ca/CAService.java | 53 ++- .../src/com/netscape/ca/CertificateAuthority.java | 443 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 183 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../netscape/certsrv/authority/AuthorityData.java | 109 +++++ .../certsrv/authority/AuthorityResource.java | 36 ++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CANotFoundException.java | 12 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 44 ++ .../certsrv/ca/IssuerUnavailableException.java | 13 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 19 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../cms/servlet/cert/EnrollmentProcessor.java | 22 + .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-subCA.ldif | 5 + base/server/share/conf/schema.ldif | 10 + 23 files changed, 988 insertions(+), 107 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-subCA.ldif diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..8918c5a22566dd5f6bd52a7dd33da46169060e54 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=subCAs,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: subCAs diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..3b57f5b209160d7f35d1ebfbeeb325eccf29a072 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -25,15 +25,20 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; @@ -53,6 +58,13 @@ import netscape.security.x509.X509CertInfo; import netscape.security.x509.X509ExtensionException; import netscape.security.x509.X509Key; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSearchResults; + import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +72,9 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +88,20 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.IDBSSession; import com.netscape.certsrv.dbs.certdb.ICertRecord; import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.dbs.crldb.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +116,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +128,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +146,9 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import org.apache.commons.lang.StringUtils; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +162,12 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap(); + protected CertificateAuthority topCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,33 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + topCA = this; + } + + /** + * Construct and initialise a sub-CA + */ + public CertificateAuthority( + String subsystemId, + ISubsystem owner, + IConfigStore config, + CertificateAuthority topCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription + ) throws EBaseException { + setId(subsystemId); + this.topCA = topCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + mNickname = signingKeyNickname; + init(owner, config); + } + + private boolean isTopCA() { + return authorityID == null; } /** @@ -334,8 +393,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isTopCA()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = topCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +431,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The top-level CA owns these resources so skip these + * steps for sub-CAs. + */ + if (isTopCA()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +471,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isTopCA()) + loadSubCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +482,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isTopCA()) { + mByName = topCA.mByName; + mCRLPublisher = topCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +633,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isTopCA()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +645,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + if (!isTopCA()) return; // sub-CAs don't own these resources + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -1228,13 +1320,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isTopCA() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1396,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1535,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isTopCA()) { + mCertRepot = topCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1568,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isTopCA()) { + mCRLRepot = topCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1588,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1611,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isTopCA()) { + mPublisherProcessor = topCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1779,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isTopCA()) { + mPolicy = topCA.mPolicy; + mService = topCA.mService; + mNotify = topCA.mNotify; + mPNotify = topCA.mPNotify; + mRequestQueue = topCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1846,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isTopCA()) { + mCRLIssuePoints = topCA.mCRLIssuePoints; + mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1916,64 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register sub-CAs. + * + * This method must only be called by the top-level CA. + */ + private synchronized void loadSubCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadSubCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + if (aidAttr == null || nickAttr == null) + throw new ECAException("Malformed sub-CA object: " + entry.getDN()); + String keyNick = (String) nickAttr.getStringValues().nextElement(); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + CertificateAuthority subCA = new CertificateAuthority( + getId(), mOwner, mConfig, this, + aid, parentAID, keyNick, desc); + caMap.put(aid, subCA); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -2083,4 +2258,160 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all sub-CAs. + */ + public synchronized List getCAs() { + List cas = new ArrayList(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get the sub-CA by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the top-level CA. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? topCA : caMap.get(aid); + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new sub-CA. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA, or null to create + * sub-CA immediately below top-level CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority subCA = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(subCA.getAuthorityID(), subCA); + } + return subCA; + } + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check uniqueness of DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + for (ICertificateAuthority ca : caMap.values()) { + if (ca.getX500Name().equals(subjectX500Name)) + throw new IssuerUnavailableException( + "CA with Subject DN '" + subjectDN + "' already exists"); + } + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = topCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate sub-CA signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating sub-CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + getId(), mOwner, mConfig, topCA, aid, this.authorityID, + nickname, description); + } } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..5ca21892d441e4b0e611d117e4dd490df05cf6e6 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,183 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.jboss.resteasy.plugins.providers.atom.Link; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.certsrv.common.NameValuePairs; +import com.netscape.certsrv.common.OpDef; +import com.netscape.certsrv.common.ScopeDef; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.cms.realm.PKIPrincipal; +import com.netscape.cms.servlet.base.PKIService; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority topCA; + + public AuthorityService() { + topCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList(); + for (ICertificateAuthority ca : topCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity entity = new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response createCA(AuthorityData data) { + AuthorityID parentAID = null; + String parentAIDString = data.getParentID(); + if (parentAIDString != null) { + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + parentAIDString); + } + } + + try { + ICertificateAuthority subCA = topCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating sub-CA: " + e.toString()); + } + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityDescription() + ); + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..64f2af83720311e3eac0ee7a9197a05ff0e7198a --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,109 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.jboss.resteasy.plugins.providers.atom.Link; + + at XmlRootElement(name = "ca") + at XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @XmlAttribute + protected String aid; + + public String getID() { + return aid; + } + + + @XmlAttribute + protected String parentAID; + + public String getParentID() { + return parentAID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + String dn, String aid, String parentAID, + String description) { + this.dn = dn; + this.aid = aid; + this.parentAID = parentAID; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..817885687b04d1b10ea35dca9a1942fe5ce201f4 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + + at Path("authorities") +public interface AuthorityResource { + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + //@ACLMapping("certs") + //@AuthMethodMapping("certs") + public Response createCA(AuthorityData data); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..86f3870ab8cec700ab27cd6a8555e8e8d6e84c01 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(Object o) { + if (o instanceof AuthorityID) + return uuid.compareTo(((AuthorityID) o).uuid); + throw new ClassCastException("Invalid comparsion operand for AuthorityID"); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b7574d4243d9941e30aba506b4ad5fd7b8394386 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,12 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..d707bf848ba0973c2a817c45abeba8219bff574f 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,47 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all sub-CA handles. + */ + public List getCAs(); + + /** + * Get the CA ID of this CA. Returns null for the top-level CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the CA ID of this CA's parent CA. Returns null for + * authorities signed by the top-level CA. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..541df308d413676da51e5a1bce27232464118920 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (unused for top-level CA) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the public key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..54cfafed22a0654dd993c9c67f247eefac09c1a3 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -26,8 +26,12 @@ import netscape.security.x509.PKIXExtensions; import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; @@ -161,18 +165,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..7e358b87f35b8aef2d3ef9de3f8dfd4c7a2b7053 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,10 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -146,6 +150,24 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + // Insert AuthorityID into request context + // + String aidString = request.getParameter("authority"); + if (aidString != null) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestDataException("EnrollmentProcessor: invalid AuthorityID: " + aidString); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (ca.getCA(aid) == null) + throw new CANotFoundException("CA not found: " + aidString); + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString); + } + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif new file mode 100644 index 0000000000000000000000000000000000000000..d03ca70ab466a6229f0efbbe2df6c587d8d5ea5c --- /dev/null +++ b/base/server/share/conf/schema-subCA.ldif @@ -0,0 +1,5 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..3a692cac9370e9bb115a35fd0fe56be1d49b9ce9 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,13 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From 602672eca140cde9fb4f1bcc8eab9d297c52d5f0 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 10 Jun 2015 03:02:35 -0400 Subject: [PATCH] Add subca CLI --- .../certsrv/authority/AuthorityClient.java | 57 ++++++++++++++ .../src/com/netscape/certsrv/ca/CAClient.java | 3 +- .../netscape/cmstools/authority/AuthorityCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityCreateCLI.java | 86 ++++++++++++++++++++++ .../cmstools/authority/AuthorityFindCLI.java | 62 ++++++++++++++++ .../cmstools/authority/AuthorityShowCLI.java | 57 ++++++++++++++ .../src/com/netscape/cmstools/cli/CACLI.java | 2 + 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..7ad549c7d4bf85513aa64911fdf1c71c5ac4fc21 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -0,0 +1,57 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- +package com.netscape.certsrv.authority; + +import java.net.URISyntaxException; +import java.util.List; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; + +import com.netscape.certsrv.client.Client; +import com.netscape.certsrv.client.PKIClient; + +/** + * @author Fraser Tweedale + */ +public class AuthorityClient extends Client { + + public AuthorityResource proxy; + + public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException { + super(client, subsystem, "authority"); + proxy = createProxy(AuthorityResource.class); + } + + public List listCAs() { + Response response = proxy.listCAs(); + GenericType> type = new GenericType>() {}; + return client.getEntity(response, type); + } + + public AuthorityData getCA(String caIDString) { + Response response = proxy.getCA(caIDString); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData createCA(AuthorityData data) { + Response response = proxy.createCA(data); + return client.getEntity(response, AuthorityData.class); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 100644 --- a/base/common/src/com/netscape/certsrv/ca/CAClient.java +++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java @@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient; import com.netscape.certsrv.profile.ProfileClient; import com.netscape.certsrv.selftests.SelfTestClient; import com.netscape.certsrv.user.UserClient; +import com.netscape.certsrv.authority.AuthorityClient; public class CAClient extends SubsystemClient { @@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient { } public void init() throws URISyntaxException { - + addClient(new AuthorityClient(client, name)); addClient(new CertClient(client, name)); addClient(new GroupClient(client, name)); addClient(new ProfileClient(client, name)); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..f2b630d32f9b8fc12792d14c84e90e12f7c23f4d --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.net.URI; +import java.util.Locale; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityClient; +import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCLI extends CLI { + + public AuthorityClient authorityClient; + + public AuthorityCLI(CLI parent) { + super("authority", "CA management commands", parent); + + addModule(new AuthorityFindCLI(this)); + addModule(new AuthorityShowCLI(this)); + addModule(new AuthorityCreateCLI(this)); + } + + public String getFullName() { + if (parent instanceof MainCLI) { + // do not include MainCLI's name + return name; + } else { + return parent.getFullName() + "-" + name; + } + } + + public void execute(String[] args) throws Exception { + client = parent.getClient(); + authorityClient = new AuthorityClient(client, "ca"); + super.execute(args); + } + + protected static void printAuthorityData(AuthorityData data) { + System.out.println(" Issuer DN: " + data.getDN()); + System.out.println(" ID: " + data.getID()); + System.out.println(" Parent ID: " + data.getParentID()); + String desc = data.getDescription(); + if (desc != null) + System.out.println(" Description: " + desc); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..19329cbba0fac3f8c9722cc6854cbeaf6a31c75c --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -0,0 +1,86 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCreateCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityCreateCLI(AuthorityCLI authorityCLI) { + super("create", "Create CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option(null, "parent", true, "ID of parent CA"); + optParent.setArgName("id"); + options.addOption(optParent); + + Option optDesc = new Option(null, "desc", true, "Optional description"); + optDesc.setArgName("string"); + options.addOption(optDesc); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No DN specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String parentAIDString = null; + if (cmd.hasOption("parent")) { + parentAIDString = cmd.getOptionValue("parent"); + try { + new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + System.err.println("Bad CA ID: " + parentAIDString); + printHelp(); + System.exit(-1); + } + } + + String desc = null; + if (cmd.hasOption("desc")) + desc = cmd.getOptionValue("desc"); + + String dn = cmdArgs[0]; + AuthorityData data = new AuthorityData( + dn, null, parentAIDString, desc); + AuthorityData newData = authorityCLI.authorityClient.createCA(data); + AuthorityCLI.printAuthorityData(newData); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..4a5684671d6a778146de183a0d122aaa58c45d8d --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java @@ -0,0 +1,62 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityFindCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityFindCLI(AuthorityCLI authorityCLI) { + super("find", "Find CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName(), options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + List datas = authorityCLI.authorityClient.listCAs(); + + MainCLI.printMessage(datas.size() + " entries matched"); + if (datas.size() == 0) return; + + boolean first = true; + for (AuthorityData data : datas) { + if (first) + first = false; + else + System.out.println(); + AuthorityCLI.printAuthorityData(data); + } + + MainCLI.printMessage("Number of entries returned " + datas.size()); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..a252f3001ae2581f770e58e68a077eb909a5490b --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java @@ -0,0 +1,57 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityShowCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityShowCLI(AuthorityCLI authorityCLI) { + super("show", "Show CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + String caIDString = cmdArgs[0]; + AuthorityData data = authorityCLI.authorityClient.getCA(caIDString); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java @@ -20,6 +20,7 @@ package com.netscape.cmstools.cli; import com.netscape.certsrv.ca.CAClient; import com.netscape.certsrv.client.Client; +import com.netscape.cmstools.authority.AuthorityCLI; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.group.GroupCLI; import com.netscape.cmstools.profile.ProfileCLI; @@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI { public CACLI(CLI parent) { super("ca", "CA management commands", parent); + addModule(new AuthorityCLI(this)); addModule(new CertCLI(this)); addModule(new GroupCLI(this)); addModule(new KRAConnectorCLI(this)); -- 2.4.3 -------------- next part -------------- From bb5d5b827bf326c2d931e2477977a90fdecf2405 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 1 Sep 2015 09:57:42 -0400 Subject: [PATCH] Add ability to request cert from subca via REST Add the optional "ca" query parameter for REST cert request submission. Also update the ca-cert-request-submit CLI command with an option to provide an AuthorityID. --- .../src/com/netscape/cms/servlet/test/CATest.java | 4 ++-- .../dogtagpki/server/ca/rest/CertRequestService.java | 11 ++++++++++- .../src/com/netscape/certsrv/cert/CertClient.java | 7 +++++-- .../netscape/certsrv/cert/CertRequestResource.java | 4 +++- .../netscape/cmstools/cert/CertRequestSubmitCLI.java | 20 +++++++++++++++++++- .../cmstools/client/ClientCertRequestCLI.java | 2 +- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java index 15023cad939abb11927abc64fe5916e04cb65661..5876c57f985caa38ad5895f4368113620370910d 100644 --- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java +++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java @@ -288,7 +288,7 @@ public class CATest { private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); @@ -308,7 +308,7 @@ public class CATest { private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..654d814d8a963892a6b39a1f77745e1071a5408d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -40,7 +40,9 @@ import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -113,7 +115,11 @@ public class CertRequestService extends PKIService implements CertRequestResourc } @Override - public Response enrollCert(CertEnrollmentRequest data) { + public Response enrollCert(CertEnrollmentRequest data, String aidString) { + // Ignore the aidString param; it is pulled out of the + // servletRequest that is passed to CertRequestDAO, + // but is included in the signature so that clients + // can easily provide it via @QueryParam if (data == null) { CMS.debug("enrollCert: data is null"); @@ -137,6 +143,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("enrollCert: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + CMS.debug("enrollCert: unknown CA: " + e); + throw new ResourceNotFoundException(e.toString()); } catch (EBaseException e) { throw new PKIException(e); } catch (Exception e) { diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java index 42b04b7021f0063894c340c177915d799b621ddd..211711b3da64e5125beee000759c2f2926d85e86 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertClient.java +++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java @@ -21,6 +21,7 @@ import java.net.URISyntaxException; import javax.ws.rs.core.Response; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.client.Client; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.SubsystemClient; @@ -85,8 +86,10 @@ public class CertClient extends Client { return client.getEntity(response, CertRequestInfo.class); } - public CertRequestInfos enrollRequest(CertEnrollmentRequest data) { - Response response = certRequestClient.enrollCert(data); + public CertRequestInfos enrollRequest( + CertEnrollmentRequest data, AuthorityID aid) { + String aidString = aid != null ? aid.toString() : null; + Response response = certRequestClient.enrollCert(data, aidString); return client.getEntity(response, CertRequestInfos.class); } diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java index 7f08b4af392e3e56419abdad7cb66bd191688222..b877b681ccf905b4da2949fe04ec21e8a6407bba 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java +++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java @@ -37,7 +37,9 @@ public interface CertRequestResource { @POST @Path("certrequests") @ClientResponseType(entityType=CertRequestInfos.class) - public Response enrollCert(CertEnrollmentRequest data); + public Response enrollCert( + CertEnrollmentRequest data, + @QueryParam("authority") String caIDString); /** * Used to retrieve cert request info for a specific request diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..e46079406bfbd1dbd47e32567b52dde85f181233 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -8,8 +8,10 @@ import java.util.Scanner; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; @@ -22,6 +24,10 @@ public class CertRequestSubmitCLI extends CLI { public CertRequestSubmitCLI(CertCLI certCLI) { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; + + Option optCA = new Option(null, "authority", true, "Authority ID (omit for top-level CA)"); + optCA.setArgName("id"); + options.addOption(optCA); } public void printHelp() { @@ -55,9 +61,21 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } + AuthorityID aid = null; + if (cmd.hasOption("authority")) { + String aidString = cmd.getOptionValue("authority"); + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + System.err.println("Bad AuthorityID: " + aidString); + printHelp(); + System.exit(-1); + } + } + try { CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd); + CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(cri); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..13b8c632f9b6d3fce96fb07547852bdef552873d 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI { System.out.println("Sending certificate request."); } - CertRequestInfos infos = certClient.enrollRequest(request); + CertRequestInfos infos = certClient.enrollRequest(request, null); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); -- 2.4.3 From edewata at redhat.com Tue Sep 15 15:22:32 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Tue, 15 Sep 2015 10:22:32 -0500 Subject: [Pki-devel] [PATCH] 643 Fixed pkidbuser group memberships. In-Reply-To: <55F36EE0.8070808@redhat.com> References: <55F36EE0.8070808@redhat.com> Message-ID: <55F837B8.6050007@redhat.com> On 9/11/2015 7:16 PM, Endi Sukma Dewata wrote: > Due to a certificate mapping issue the subsystem certificate can > be mapped into either the subsystem user or pkidbuser, which may > cause problems since the users don't belong to the same groups. > As a temporary solution the pkidbuser is now added into the same > groups. This way the client subsystem can always access the > services regardless of which user the certificate is actually > mapped to. > > https://fedorahosted.org/pki/ticket/1595 Rebased for RHEL 7.2. -- Endi S. Dewata -------------- next part -------------- >From c267b17b97a3ac03f51f0074e8c0b1cf4388c68b Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Fri, 11 Sep 2015 22:59:55 +0200 Subject: [PATCH] Fixed pkidbuser group memberships. Due to a certificate mapping issue the subsystem certificate can be mapped into either the subsystem user or pkidbuser, which may cause problems since the users don't belong to the same groups. As a temporary solution the pkidbuser is now added into the same groups. This way the client subsystem can always access the services regardless of which user the certificate is actually mapped to. https://fedorahosted.org/pki/ticket/1595 --- .../cms/servlet/csadmin/ConfigurationUtils.java | 88 +++++++++++++++------- 1 file changed, 60 insertions(+), 28 deletions(-) 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 708240b53baf6bd04e4f4651edaee3c695b9a896..d99929f20996f1aa7ddbf21d29dfe7b54905354d 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 @@ -50,6 +50,7 @@ import java.security.cert.CertificateNotYetValidException; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.StringTokenizer; @@ -62,32 +63,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.xml.parsers.ParserConfigurationException; -import netscape.ldap.LDAPAttribute; -import netscape.ldap.LDAPAttributeSet; -import netscape.ldap.LDAPConnection; -import netscape.ldap.LDAPDN; -import netscape.ldap.LDAPEntry; -import netscape.ldap.LDAPException; -import netscape.ldap.LDAPModification; -import netscape.ldap.LDAPSearchConstraints; -import netscape.ldap.LDAPSearchResults; -import netscape.ldap.LDAPv3; -import netscape.security.pkcs.ContentInfo; -import netscape.security.pkcs.PKCS10; -import netscape.security.pkcs.PKCS7; -import netscape.security.pkcs.SignerInfo; -import netscape.security.util.DerOutputStream; -import netscape.security.util.ObjectIdentifier; -import netscape.security.x509.AlgorithmId; -import netscape.security.x509.BasicConstraintsExtension; -import netscape.security.x509.CertificateChain; -import netscape.security.x509.Extension; -import netscape.security.x509.Extensions; -import netscape.security.x509.KeyUsageExtension; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509Key; - +import org.apache.commons.lang.StringUtils; import org.apache.velocity.context.Context; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.CryptoManager.NicknameConflictException; @@ -180,6 +156,32 @@ import com.netscape.cmsutil.http.JssSSLSocketFactory; import com.netscape.cmsutil.ldap.LDAPUtil; import com.netscape.cmsutil.xml.XMLObject; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPDN; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPSearchConstraints; +import netscape.ldap.LDAPSearchResults; +import netscape.ldap.LDAPv3; +import netscape.security.pkcs.ContentInfo; +import netscape.security.pkcs.PKCS10; +import netscape.security.pkcs.PKCS7; +import netscape.security.pkcs.SignerInfo; +import netscape.security.util.DerOutputStream; +import netscape.security.util.ObjectIdentifier; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.BasicConstraintsExtension; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.Extension; +import netscape.security.x509.Extensions; +import netscape.security.x509.KeyUsageExtension; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509Key; + /** * Utility class for functions to be used both by the RESTful installer * and the UI Panels. @@ -3892,7 +3894,7 @@ public class ConfigurationUtils { String groupName = "Trusted Managers"; IGroup group = system.getGroupFromName(groupName); if (!group.isMember(id)) { - CMS.debug("setupClientAuthUser: adding user to the trusted managers group."); + CMS.debug("setupClientAuthUser: adding user to the " + groupName + " group."); group.addMemberName(id); system.modifyGroup(group); } @@ -4119,7 +4121,7 @@ public class ConfigurationUtils { user.setX509Certificates(certs); system.addUser(user); - CMS.debug("setupDBUser(): successfully added the user"); + CMS.debug("setupDBUser(): successfully added " + DBUSER); system.addUserCert(user); CMS.debug("setupDBUser(): successfully add the user certificate"); @@ -4130,6 +4132,36 @@ public class ConfigurationUtils { // remove old db users CMS.debug("setupDBUser(): removing seeAlso from old dbusers"); removeOldDBUsers(certs[0].getSubjectDN().toString()); + + // workaround for ticket #1595 + IConfigStore cs = CMS.getConfigStore(); + String csType = cs.getString("cs.type").toUpperCase(); + + Collection groupNames = new ArrayList(); + + if ("CA".equals(csType)) { + groupNames.add("Subsystem Group"); + groupNames.add("Certificate Manager Agents"); + + } else if ("KRA".equals(csType)) { + groupNames.add("Data Recovery Manager Agents"); + groupNames.add("Trusted Managers"); + + } else if ("OCSP".equals(csType)) { + groupNames.add("Trusted Managers"); + + } else if ("TKS".equals(csType)) { + groupNames.add("Token Key Service Manager Agents"); + } + + for (String groupName : groupNames) { + IGroup group = system.getGroupFromName(groupName); + if (!group.isMember(DBUSER)) { + CMS.debug("setupDBUser(): adding " + DBUSER + " to the " + groupName + " group."); + group.addMemberName(DBUSER); + system.modifyGroup(group); + } + } } public static void addProfilesToTPSUser(String adminID) throws EUsrGrpException, LDAPException { -- 2.4.3 From edewata at redhat.com Tue Sep 15 18:08:47 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Tue, 15 Sep 2015 13:08:47 -0500 Subject: [Pki-devel] [PATCH] 643 Fixed pkidbuser group memberships. In-Reply-To: <55F837B8.6050007@redhat.com> References: <55F36EE0.8070808@redhat.com> <55F837B8.6050007@redhat.com> Message-ID: <55F85EAF.7000509@redhat.com> On 9/15/2015 10:22 AM, Endi Sukma Dewata wrote: > On 9/11/2015 7:16 PM, Endi Sukma Dewata wrote: >> Due to a certificate mapping issue the subsystem certificate can >> be mapped into either the subsystem user or pkidbuser, which may >> cause problems since the users don't belong to the same groups. >> As a temporary solution the pkidbuser is now added into the same >> groups. This way the client subsystem can always access the >> services regardless of which user the certificate is actually >> mapped to. >> >> https://fedorahosted.org/pki/ticket/1595 > > Rebased for RHEL 7.2. ACKed by mharmsen. Pushed to master. -- Endi S. Dewata From alee at redhat.com Wed Sep 16 16:23:00 2015 From: alee at redhat.com (Ade Lee) Date: Wed, 16 Sep 2015 12:23:00 -0400 Subject: [Pki-devel] [PATCH] 269 - python code for subcas Message-ID: <1442420580.6855.4.camel@redhat.com> This patch provides python client code (and tests) for the subcas feature, as based on Fraser's patches. Those need to be applied to the server code in order for this functionality to be tested. Right now, as per Fraser's patches, there is only support for aid, parent_aid, dn, description. No support yet for cacert and intermediates, or for status. The patch does include unit tests -- which should be required from now on for new python code -- as well as some functional test code - which can later be moved into a functional test framework. Please review. Ade -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-vakwetu-0269-Python-client-for-subcas.patch Type: text/x-patch Size: 23133 bytes Desc: not available URL: From ftweedal at redhat.com Fri Sep 18 04:58:48 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Fri, 18 Sep 2015 14:58:48 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> Message-ID: <20150918045848.GM16937@dhcp-40-8.bne.redhat.com> On Mon, Sep 14, 2015 at 05:25:00PM +1000, Fraser Tweedale wrote: > The latest lightweight CAs (f.k.a. Sub-CAs) patches are attached. > This cut has significant changes and additions including: > > - CAs are now stored in a flat structure with references to parents > > - CAs are now identified by "AuthorityID" (which for now is a UUID > underneath). Many variables, method names and user-visible > strings were updated accordingly. Out with "caRef" terminology. > > - "Sub-CA" terminology is (mostly) out; "Authority" is in. This is > to support lightweight CAs that are not descendents of top-level > CA (which can be implemented later). > > - ca-cert-request-submit command and related client / service > classes were updated to add "authority" parameter > > - Some more specific use of exception (including some new exception > classes) to indicate / catch particular errors. > > - More appropriate HTTP status codes return when client has send > invalid data (400), referenced unknown authority (404) or attempts > to create an authority with Subject DN already uesd by another > authority (409 Conflict) > > - LDAP entry now gets added before key generation and signing. If > something goes wrong, the DB entry is removed in the catch block. > > There are still notable gaps in functionality that are in progress > or will be implemented soon: > > - Audit log events > - Resources to enable/disable/delete authority > - Resources to access cert and pkcs7 chain of authority > - Keygen params > - Param to specify token on which to generate authority key > Latest patches attached. Along with some minor improvements to the original patches, three new patches 0048-0050 add ability to enable and disable lightweight CAs. Commit messages for the earlier patches have also been updated for consistency and to include ticket references. Thanks, Fraser -------------- next part -------------- From a8ce408791f67e03191cd0ebb4c623297f0ec11e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Lightweight CAs: initial support This commit adds initial support for "lightweight CAs" - CAs that inhabit an existing CA instance and share the request queue and certificate database of the "top-level CA". We initially support only sub-CAs under the top-level CA - either direct sub-CAs or nested. The general design will support hosting unrelated CAs but creation or import of unrelated CAs is not yet implemented. Part of: https://fedorahosted.org/pki/ticket/1213 --- base/ca/shared/conf/db.ldif | 5 + base/ca/src/com/netscape/ca/CAService.java | 53 ++- .../src/com/netscape/ca/CertificateAuthority.java | 438 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 183 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../netscape/certsrv/authority/AuthorityData.java | 109 +++++ .../certsrv/authority/AuthorityResource.java | 36 ++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CANotFoundException.java | 12 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 44 +++ .../certsrv/ca/IssuerUnavailableException.java | 13 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 19 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../cms/servlet/cert/EnrollmentProcessor.java | 22 ++ .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-subCA.ldif | 5 + base/server/share/conf/schema.ldif | 10 + 23 files changed, 983 insertions(+), 107 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-subCA.ldif diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=authorities,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: authorities diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..5df7c77612132d2dc52ca43060d7ba782d9388cc 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -25,15 +25,20 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; @@ -53,6 +58,13 @@ import netscape.security.x509.X509CertInfo; import netscape.security.x509.X509ExtensionException; import netscape.security.x509.X509Key; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSearchResults; + import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +72,9 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +88,20 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.IDBSSession; import com.netscape.certsrv.dbs.certdb.ICertRecord; import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.dbs.crldb.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +116,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +128,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +146,9 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import org.apache.commons.lang.StringUtils; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +162,12 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap(); + protected CertificateAuthority topCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,30 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + topCA = this; + } + + /** + * Construct and initialise a sub-CA + */ + private CertificateAuthority( + CertificateAuthority topCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription + ) throws EBaseException { + setId(topCA.getId()); + this.topCA = topCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + mNickname = signingKeyNickname; + init(topCA.mOwner, topCA.mConfig); + } + + private boolean isTopCA() { + return authorityID == null; } /** @@ -334,8 +390,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isTopCA()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = topCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +428,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The top-level CA owns these resources so skip these + * steps for sub-CAs. + */ + if (isTopCA()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +468,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isTopCA()) + loadSubCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +479,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isTopCA()) { + mByName = topCA.mByName; + mCRLPublisher = topCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +630,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isTopCA()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +642,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + if (!isTopCA()) return; // sub-CAs don't own these resources + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -1228,13 +1317,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isTopCA() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1393,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1532,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isTopCA()) { + mCertRepot = topCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1565,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isTopCA()) { + mCRLRepot = topCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1585,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1608,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isTopCA()) { + mPublisherProcessor = topCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1776,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isTopCA()) { + mPolicy = topCA.mPolicy; + mService = topCA.mService; + mNotify = topCA.mNotify; + mPNotify = topCA.mPNotify; + mRequestQueue = topCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1843,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isTopCA()) { + mCRLIssuePoints = topCA.mCRLIssuePoints; + mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1913,63 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register sub-CAs. + * + * This method must only be called by the top-level CA. + */ + private synchronized void loadSubCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadSubCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + if (aidAttr == null || nickAttr == null) + throw new ECAException("Malformed sub-CA object: " + entry.getDN()); + String keyNick = (String) nickAttr.getStringValues().nextElement(); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + CertificateAuthority subCA = new CertificateAuthority( + this, aid, parentAID, keyNick, desc); + caMap.put(aid, subCA); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -2083,4 +2254,159 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all sub-CAs. + */ + public synchronized List getCAs() { + List cas = new ArrayList(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get the sub-CA by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the top-level CA. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? topCA : caMap.get(aid); + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new sub-CA. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA, or null to create + * sub-CA immediately below top-level CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority subCA = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(subCA.getAuthorityID(), subCA); + } + return subCA; + } + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check uniqueness of DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + for (ICertificateAuthority ca : caMap.values()) { + if (ca.getX500Name().equals(subjectX500Name)) + throw new IssuerUnavailableException( + "CA with Subject DN '" + subjectDN + "' already exists"); + } + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = topCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate sub-CA signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating sub-CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + topCA, aid, this.authorityID, nickname, description); + } } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..5ca21892d441e4b0e611d117e4dd490df05cf6e6 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,183 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.jboss.resteasy.plugins.providers.atom.Link; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.certsrv.common.NameValuePairs; +import com.netscape.certsrv.common.OpDef; +import com.netscape.certsrv.common.ScopeDef; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.cms.realm.PKIPrincipal; +import com.netscape.cms.servlet.base.PKIService; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority topCA; + + public AuthorityService() { + topCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList(); + for (ICertificateAuthority ca : topCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity entity = new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response createCA(AuthorityData data) { + AuthorityID parentAID = null; + String parentAIDString = data.getParentID(); + if (parentAIDString != null) { + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + parentAIDString); + } + } + + try { + ICertificateAuthority subCA = topCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating sub-CA: " + e.toString()); + } + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityDescription() + ); + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..64f2af83720311e3eac0ee7a9197a05ff0e7198a --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,109 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.jboss.resteasy.plugins.providers.atom.Link; + + at XmlRootElement(name = "ca") + at XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @XmlAttribute + protected String aid; + + public String getID() { + return aid; + } + + + @XmlAttribute + protected String parentAID; + + public String getParentID() { + return parentAID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + String dn, String aid, String parentAID, + String description) { + this.dn = dn; + this.aid = aid; + this.parentAID = parentAID; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..817885687b04d1b10ea35dca9a1942fe5ce201f4 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + + at Path("authorities") +public interface AuthorityResource { + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + //@ACLMapping("certs") + //@AuthMethodMapping("certs") + public Response createCA(AuthorityData data); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..86f3870ab8cec700ab27cd6a8555e8e8d6e84c01 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(Object o) { + if (o instanceof AuthorityID) + return uuid.compareTo(((AuthorityID) o).uuid); + throw new ClassCastException("Invalid comparsion operand for AuthorityID"); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b7574d4243d9941e30aba506b4ad5fd7b8394386 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,12 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..d707bf848ba0973c2a817c45abeba8219bff574f 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,47 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all sub-CA handles. + */ + public List getCAs(); + + /** + * Get the CA ID of this CA. Returns null for the top-level CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the CA ID of this CA's parent CA. Returns null for + * authorities signed by the top-level CA. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..541df308d413676da51e5a1bce27232464118920 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (unused for top-level CA) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the public key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..54cfafed22a0654dd993c9c67f247eefac09c1a3 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -26,8 +26,12 @@ import netscape.security.x509.PKIXExtensions; import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; @@ -161,18 +165,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..7e358b87f35b8aef2d3ef9de3f8dfd4c7a2b7053 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,10 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -146,6 +150,24 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + // Insert AuthorityID into request context + // + String aidString = request.getParameter("authority"); + if (aidString != null) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestDataException("EnrollmentProcessor: invalid AuthorityID: " + aidString); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (ca.getCA(aid) == null) + throw new CANotFoundException("CA not found: " + aidString); + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString); + } + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif new file mode 100644 index 0000000000000000000000000000000000000000..d03ca70ab466a6229f0efbbe2df6c587d8d5ea5c --- /dev/null +++ b/base/server/share/conf/schema-subCA.ldif @@ -0,0 +1,5 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..3a692cac9370e9bb115a35fd0fe56be1d49b9ce9 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,13 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From 5253ab958469de9e99e921ff7d04d7a5d20a4bd2 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 10 Jun 2015 03:02:35 -0400 Subject: [PATCH] Lightweight CAs: add ca-authority CLI Add CLI commands for creating, listing and showing lightweight CAs. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../certsrv/authority/AuthorityClient.java | 57 ++++++++++++++ .../src/com/netscape/certsrv/ca/CAClient.java | 3 +- .../netscape/cmstools/authority/AuthorityCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityCreateCLI.java | 86 ++++++++++++++++++++++ .../cmstools/authority/AuthorityFindCLI.java | 62 ++++++++++++++++ .../cmstools/authority/AuthorityShowCLI.java | 57 ++++++++++++++ .../src/com/netscape/cmstools/cli/CACLI.java | 2 + 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..7ad549c7d4bf85513aa64911fdf1c71c5ac4fc21 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -0,0 +1,57 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- +package com.netscape.certsrv.authority; + +import java.net.URISyntaxException; +import java.util.List; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; + +import com.netscape.certsrv.client.Client; +import com.netscape.certsrv.client.PKIClient; + +/** + * @author Fraser Tweedale + */ +public class AuthorityClient extends Client { + + public AuthorityResource proxy; + + public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException { + super(client, subsystem, "authority"); + proxy = createProxy(AuthorityResource.class); + } + + public List listCAs() { + Response response = proxy.listCAs(); + GenericType> type = new GenericType>() {}; + return client.getEntity(response, type); + } + + public AuthorityData getCA(String caIDString) { + Response response = proxy.getCA(caIDString); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData createCA(AuthorityData data) { + Response response = proxy.createCA(data); + return client.getEntity(response, AuthorityData.class); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 100644 --- a/base/common/src/com/netscape/certsrv/ca/CAClient.java +++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java @@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient; import com.netscape.certsrv.profile.ProfileClient; import com.netscape.certsrv.selftests.SelfTestClient; import com.netscape.certsrv.user.UserClient; +import com.netscape.certsrv.authority.AuthorityClient; public class CAClient extends SubsystemClient { @@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient { } public void init() throws URISyntaxException { - + addClient(new AuthorityClient(client, name)); addClient(new CertClient(client, name)); addClient(new GroupClient(client, name)); addClient(new ProfileClient(client, name)); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..f2b630d32f9b8fc12792d14c84e90e12f7c23f4d --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.net.URI; +import java.util.Locale; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityClient; +import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCLI extends CLI { + + public AuthorityClient authorityClient; + + public AuthorityCLI(CLI parent) { + super("authority", "CA management commands", parent); + + addModule(new AuthorityFindCLI(this)); + addModule(new AuthorityShowCLI(this)); + addModule(new AuthorityCreateCLI(this)); + } + + public String getFullName() { + if (parent instanceof MainCLI) { + // do not include MainCLI's name + return name; + } else { + return parent.getFullName() + "-" + name; + } + } + + public void execute(String[] args) throws Exception { + client = parent.getClient(); + authorityClient = new AuthorityClient(client, "ca"); + super.execute(args); + } + + protected static void printAuthorityData(AuthorityData data) { + System.out.println(" Issuer DN: " + data.getDN()); + System.out.println(" ID: " + data.getID()); + System.out.println(" Parent ID: " + data.getParentID()); + String desc = data.getDescription(); + if (desc != null) + System.out.println(" Description: " + desc); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..19329cbba0fac3f8c9722cc6854cbeaf6a31c75c --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -0,0 +1,86 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCreateCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityCreateCLI(AuthorityCLI authorityCLI) { + super("create", "Create CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option(null, "parent", true, "ID of parent CA"); + optParent.setArgName("id"); + options.addOption(optParent); + + Option optDesc = new Option(null, "desc", true, "Optional description"); + optDesc.setArgName("string"); + options.addOption(optDesc); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No DN specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String parentAIDString = null; + if (cmd.hasOption("parent")) { + parentAIDString = cmd.getOptionValue("parent"); + try { + new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + System.err.println("Bad CA ID: " + parentAIDString); + printHelp(); + System.exit(-1); + } + } + + String desc = null; + if (cmd.hasOption("desc")) + desc = cmd.getOptionValue("desc"); + + String dn = cmdArgs[0]; + AuthorityData data = new AuthorityData( + dn, null, parentAIDString, desc); + AuthorityData newData = authorityCLI.authorityClient.createCA(data); + AuthorityCLI.printAuthorityData(newData); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..4a5684671d6a778146de183a0d122aaa58c45d8d --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java @@ -0,0 +1,62 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityFindCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityFindCLI(AuthorityCLI authorityCLI) { + super("find", "Find CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName(), options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + List datas = authorityCLI.authorityClient.listCAs(); + + MainCLI.printMessage(datas.size() + " entries matched"); + if (datas.size() == 0) return; + + boolean first = true; + for (AuthorityData data : datas) { + if (first) + first = false; + else + System.out.println(); + AuthorityCLI.printAuthorityData(data); + } + + MainCLI.printMessage("Number of entries returned " + datas.size()); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..a252f3001ae2581f770e58e68a077eb909a5490b --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java @@ -0,0 +1,57 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityShowCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityShowCLI(AuthorityCLI authorityCLI) { + super("show", "Show CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + String caIDString = cmdArgs[0]; + AuthorityData data = authorityCLI.authorityClient.getCA(caIDString); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java @@ -20,6 +20,7 @@ package com.netscape.cmstools.cli; import com.netscape.certsrv.ca.CAClient; import com.netscape.certsrv.client.Client; +import com.netscape.cmstools.authority.AuthorityCLI; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.group.GroupCLI; import com.netscape.cmstools.profile.ProfileCLI; @@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI { public CACLI(CLI parent) { super("ca", "CA management commands", parent); + addModule(new AuthorityCLI(this)); addModule(new CertCLI(this)); addModule(new GroupCLI(this)); addModule(new KRAConnectorCLI(this)); -- 2.4.3 -------------- next part -------------- From 74f9abbe2cff23aa3a1cd1a3ac8689f8f6efb792 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 1 Sep 2015 09:57:42 -0400 Subject: [PATCH] Lightweight CAs: REST cert request param to specify authority Add the optional "ca" query parameter for REST cert request submission. Also update the ca-cert-request-submit CLI command with an option to provide an AuthorityID. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../src/com/netscape/cms/servlet/test/CATest.java | 4 ++-- .../dogtagpki/server/ca/rest/CertRequestService.java | 11 ++++++++++- .../src/com/netscape/certsrv/cert/CertClient.java | 7 +++++-- .../netscape/certsrv/cert/CertRequestResource.java | 4 +++- .../netscape/cmstools/cert/CertRequestSubmitCLI.java | 20 +++++++++++++++++++- .../cmstools/client/ClientCertRequestCLI.java | 2 +- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java index 15023cad939abb11927abc64fe5916e04cb65661..5876c57f985caa38ad5895f4368113620370910d 100644 --- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java +++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java @@ -288,7 +288,7 @@ public class CATest { private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); @@ -308,7 +308,7 @@ public class CATest { private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..654d814d8a963892a6b39a1f77745e1071a5408d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -40,7 +40,9 @@ import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -113,7 +115,11 @@ public class CertRequestService extends PKIService implements CertRequestResourc } @Override - public Response enrollCert(CertEnrollmentRequest data) { + public Response enrollCert(CertEnrollmentRequest data, String aidString) { + // Ignore the aidString param; it is pulled out of the + // servletRequest that is passed to CertRequestDAO, + // but is included in the signature so that clients + // can easily provide it via @QueryParam if (data == null) { CMS.debug("enrollCert: data is null"); @@ -137,6 +143,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("enrollCert: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + CMS.debug("enrollCert: unknown CA: " + e); + throw new ResourceNotFoundException(e.toString()); } catch (EBaseException e) { throw new PKIException(e); } catch (Exception e) { diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java index 42b04b7021f0063894c340c177915d799b621ddd..211711b3da64e5125beee000759c2f2926d85e86 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertClient.java +++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java @@ -21,6 +21,7 @@ import java.net.URISyntaxException; import javax.ws.rs.core.Response; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.client.Client; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.SubsystemClient; @@ -85,8 +86,10 @@ public class CertClient extends Client { return client.getEntity(response, CertRequestInfo.class); } - public CertRequestInfos enrollRequest(CertEnrollmentRequest data) { - Response response = certRequestClient.enrollCert(data); + public CertRequestInfos enrollRequest( + CertEnrollmentRequest data, AuthorityID aid) { + String aidString = aid != null ? aid.toString() : null; + Response response = certRequestClient.enrollCert(data, aidString); return client.getEntity(response, CertRequestInfos.class); } diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java index 7f08b4af392e3e56419abdad7cb66bd191688222..b877b681ccf905b4da2949fe04ec21e8a6407bba 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java +++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java @@ -37,7 +37,9 @@ public interface CertRequestResource { @POST @Path("certrequests") @ClientResponseType(entityType=CertRequestInfos.class) - public Response enrollCert(CertEnrollmentRequest data); + public Response enrollCert( + CertEnrollmentRequest data, + @QueryParam("authority") String caIDString); /** * Used to retrieve cert request info for a specific request diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..e46079406bfbd1dbd47e32567b52dde85f181233 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -8,8 +8,10 @@ import java.util.Scanner; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; @@ -22,6 +24,10 @@ public class CertRequestSubmitCLI extends CLI { public CertRequestSubmitCLI(CertCLI certCLI) { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; + + Option optCA = new Option(null, "authority", true, "Authority ID (omit for top-level CA)"); + optCA.setArgName("id"); + options.addOption(optCA); } public void printHelp() { @@ -55,9 +61,21 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } + AuthorityID aid = null; + if (cmd.hasOption("authority")) { + String aidString = cmd.getOptionValue("authority"); + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + System.err.println("Bad AuthorityID: " + aidString); + printHelp(); + System.exit(-1); + } + } + try { CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd); + CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(cri); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..13b8c632f9b6d3fce96fb07547852bdef552873d 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI { System.out.println("Sending certificate request."); } - CertRequestInfos infos = certClient.enrollRequest(request); + CertRequestInfos infos = certClient.enrollRequest(request, null); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); -- 2.4.3 -------------- next part -------------- From 1a0e1d4cb45c611b0a9cdab38ac55e1d9a1c01af Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 15 Sep 2015 23:36:58 -0400 Subject: [PATCH 48/50] Lightweight CAs: add 'authorityEnabled' attribute Add the 'authortyEnabled' attribute for lightweight CAs and throw new exception CADisabledException when request submission or signing operations are attempted on a disabled authority. Part of: https://fedorahosted.org/pki/ticket/1604 --- .../src/com/netscape/ca/CertificateAuthority.java | 30 +++++++++++++++++++--- .../server/ca/rest/CertRequestService.java | 8 ++++++ .../netscape/certsrv/ca/CADisabledException.java | 13 ++++++++++ .../cms/servlet/cert/EnrollmentProcessor.java | 6 ++++- .../cms/servlet/cert/RequestProcessor.java | 30 +++++++++++++++++++++- base/server/share/conf/schema-subCA.ldif | 3 ++- base/server/share/conf/schema.ldif | 3 ++- 7 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 base/common/src/com/netscape/certsrv/ca/CADisabledException.java diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 5df7c77612132d2dc52ca43060d7ba782d9388cc..05ab2a6024ab311b80cfd3ef026d77691169aac8 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -89,6 +89,7 @@ import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; @@ -167,6 +168,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori protected AuthorityID authorityID = null; protected AuthorityID authorityParentID = null; protected String authorityDescription = null; + protected boolean authorityEnabled = true; protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; @@ -277,13 +279,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori AuthorityID aid, AuthorityID parentAID, String signingKeyNickname, - String authorityDescription + String authorityDescription, + boolean authorityEnabled ) throws EBaseException { setId(topCA.getId()); this.topCA = topCA; this.authorityID = aid; this.authorityParentID = parentAID; this.authorityDescription = authorityDescription; + this.authorityEnabled = authorityEnabled; mNickname = signingKeyNickname; init(topCA.mOwner, topCA.mConfig); } @@ -292,6 +296,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return authorityID == null; } + private void ensureEnabled() throws CADisabledException { + if (!authorityEnabled) + throw new CADisabledException("Authority is disabled"); + } + /** * Retrieves subsystem identifier. */ @@ -1056,6 +1065,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CRLImpl sign(X509CRLImpl crl, String algname) throws EBaseException { + ensureEnabled(); X509CRLImpl signedcrl = null; IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); @@ -1128,6 +1138,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CertImpl sign(X509CertInfo certInfo, String algname) throws EBaseException { + ensureEnabled(); X509CertImpl signedcert = null; @@ -1212,6 +1223,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public byte[] sign(byte[] data, String algname) throws EBaseException { + ensureEnabled(); return mSigningUnit.sign(data, algname); } @@ -1952,8 +1964,16 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (descAttr != null) desc = (String) descAttr.getStringValues().nextElement(); + boolean enabled = true; + LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled"); + if (enabledAttr != null) { + String enabledString = (String) + enabledAttr.getStringValues().nextElement(); + enabled = enabledString.equalsIgnoreCase("TRUE"); + } + CertificateAuthority subCA = new CertificateAuthority( - this, aid, parentAID, keyNick, desc); + this, aid, parentAID, keyNick, desc, enabled); caMap.put(aid, subCA); } } catch (LDAPException e) { @@ -2131,6 +2151,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } private BasicOCSPResponse sign(ResponseData rd) throws EBaseException { + ensureEnabled(); try (DerOutputStream out = new DerOutputStream()) { DerOutputStream tmp = new DerOutputStream(); @@ -2352,7 +2373,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori new LDAPAttribute("objectclass", "authority"), new LDAPAttribute("cn", aidString), new LDAPAttribute("authorityID", aidString), - new LDAPAttribute("authorityKeyNickname", nickname) + new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityEnabled", "TRUE") }; LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); if (this.authorityID != null) @@ -2407,6 +2429,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } return new CertificateAuthority( - topCA, aid, this.authorityID, nickname, description); + topCA, aid, this.authorityID, nickname, description, true); } } diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 654d814d8a963892a6b39a1f77745e1071a5408d..f3ded9a207230ef455b1ae49866c8b5069acfb26 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -38,10 +38,12 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; @@ -143,6 +145,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("enrollCert: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("enrollCert: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (CANotFoundException e) { CMS.debug("enrollCert: unknown CA: " + e); throw new ResourceNotFoundException(e.toString()); @@ -219,6 +224,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("changeRequestState: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("changeRequestState: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (EPropertyException e) { CMS.debug("changeRequestState: execution error " + e); throw new PKIException(CMS.getUserMessage(getLocale(headers), diff --git a/base/common/src/com/netscape/certsrv/ca/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..df5fdf809f716202c38287e670a88e55eb93ce75 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot perform an operation + * because it is disabled. + */ +public class CADisabledException extends ECAException { + + public CADisabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 7e358b87f35b8aef2d3ef9de3f8dfd4c7a2b7053..5e22fc986dab621191c3157df5d0ef9a842eea34 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -31,6 +31,7 @@ import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.IEnrollProfile; @@ -163,8 +164,11 @@ public class EnrollmentProcessor extends CertProcessor { } ICertificateAuthority ca = (ICertificateAuthority) CMS.getSubsystem(CMS.SUBSYSTEM_CA); - if (ca.getCA(aid) == null) + ca = ca.getCA(aid); + if (ca == null) throw new CANotFoundException("CA not found: " + aidString); + if (!ca.getAuthorityEnabled()) + throw new CADisabledException("CA not enabled: " + aidString); ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString); } diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java index 2826f477e358a5e16657e985d7f13079cdb14a33..df318aa93c95c91c7885360654daf2d4a7da77c6 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java @@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EDeferException; @@ -346,11 +350,35 @@ public class RequestProcessor extends CertProcessor { * occurred */ private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale) - throws EProfileException { + throws EBaseException { String auditMessage = null; String auditSubjectID = auditSubjectID(); String auditRequesterID = auditRequesterID(req); + // ensure target CA is enabled + String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + // request already accepted; shouldn't happen + throw new BadRequestDataException("Invalid AuthorityID in request data"); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem("ca"); + if (ca == null) + // shouldn't happen + throw new CANotFoundException("Could not get top-level CA"); // shouldn't happen + ca = ca.getCA(aid); + if (ca == null) + // request already accepted; shouldn't happen + throw new CANotFoundException("Unknown CA: " + aidString); + if (!ca.getAuthorityEnabled()) + // authority disabled after request was submitted + throw new CADisabledException("CA '" + aidString + "' is disabled"); + } + try { profile.execute(req); req.setRequestStatus(RequestStatus.COMPLETE); diff --git a/base/server/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif index d03ca70ab466a6229f0efbbe2df6c587d8d5ea5c..762fc99abe87419da4a6bc79888a800c49219afa 100644 --- a/base/server/share/conf/schema-subCA.ldif +++ b/base/server/share/conf/schema-subCA.ldif @@ -2,4 +2,5 @@ dn: cn=schema attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) -objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 3a692cac9370e9bb115a35fd0fe56be1d49b9ce9..72d902713500192b75038ef2c980b4e4ee9bb3b8 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -674,6 +674,7 @@ add: attributeTypes attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) - add: objectClasses -objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From 13e7f4077da724c22cde97a8e0f1e96ba23fa636 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Thu, 17 Sep 2015 02:26:35 -0400 Subject: [PATCH 49/50] Lightweight CAs: add ability to modify attributes Add the ability to modify lightweight CA attributes, including enabling and disabling the CA (API and server aspects only). Part of: https://fedorahosted.org/pki/ticket/1604 --- .../src/com/netscape/ca/CertificateAuthority.java | 75 ++++++++++++++++++++++ .../dogtagpki/server/ca/rest/AuthorityService.java | 37 +++++++++++ .../netscape/certsrv/authority/AuthorityData.java | 11 +++- .../certsrv/authority/AuthorityResource.java | 31 +++++++++ .../netscape/certsrv/ca/ICertificateAuthority.java | 14 ++++ .../cmstools/authority/AuthorityCreateCLI.java | 2 +- 6 files changed, 168 insertions(+), 2 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 05ab2a6024ab311b80cfd3ef026d77691169aac8..efaed10c76ee0f6b415ec220ae8b54008515c38d 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -63,6 +63,8 @@ import netscape.ldap.LDAPAttributeSet; import netscape.ldap.LDAPConnection; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPModificationSet; import netscape.ldap.LDAPSearchResults; import org.mozilla.jss.CryptoManager; @@ -301,6 +303,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori throw new CADisabledException("Authority is disabled"); } + public boolean getAuthorityEnabled() { + return authorityEnabled; + } + /** * Retrieves subsystem identifier. */ @@ -2431,4 +2437,73 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new CertificateAuthority( topCA, aid, this.authorityID, nickname, description, true); } + + /** + * Update lightweight authority attributes. + * + * Pass null values to exclude an attribute from the update. + * + * If a passed value matches the current value, it is excluded + * from the update. + * + * To remove optional string values, pass the empty string. + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException { + if (isTopCA()) return; + + LDAPModificationSet mods = new LDAPModificationSet(); + + boolean nextEnabled = authorityEnabled; + if (enabled != null && enabled.booleanValue() != authorityEnabled) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE")); + nextEnabled = enabled; + } + + String nextDesc = authorityDescription; + if (desc != null) { + if (!desc.isEmpty() && authorityDescription != null + && !desc.equals(authorityDescription)) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } else if (desc.isEmpty() && authorityDescription != null) { + mods.add( + LDAPModification.DELETE, + new LDAPAttribute("description", authorityDescription)); + nextDesc = null; + } else if (!desc.isEmpty() && authorityDescription == null) { + mods.add( + LDAPModification.ADD, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } + } + + if (mods.size() > 0) { + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + // update was successful; update CA's state + authorityEnabled = nextEnabled; + authorityDescription = nextDesc; + } + + } } diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java index 5ca21892d441e4b0e611d117e4dd490df05cf6e6..9a542c8b8eec3a5122d1a977a6d721d540595dad 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -141,6 +141,42 @@ public class AuthorityService extends PKIService implements AuthorityResource { } } + @Override + public Response modifyCA(String aidString, AuthorityData data) { + // Ensure all (mutable) attributes are present + // because PUT expects complete representation + if ( + data.getEnabled() == null + || data.getDescription() == null + ) { + throw new BadRequestException("Incomplete data"); + } + + return patchCA(aidString, data); + } + + @Override + public Response patchCA(String aidString, AuthorityData data) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.modifyAuthority(data.getEnabled(), data.getDescription()); + return createOKResponse(readAuthorityData(ca)); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + private static AuthorityData readAuthorityData(ICertificateAuthority ca) throws PKIException { String dn; @@ -155,6 +191,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { dn, ca.getAuthorityID().toString(), parentAID != null ? parentAID.toString() : null, + ca.getAuthorityEnabled(), ca.getAuthorityDescription() ); } diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java index 64f2af83720311e3eac0ee7a9197a05ff0e7198a..13c0cecab03816eee8f77409902a0ee17ec50f80 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -77,6 +77,14 @@ public class AuthorityData { @XmlAttribute + protected Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + + @XmlAttribute protected String description; public String getDescription() { @@ -99,10 +107,11 @@ public class AuthorityData { public AuthorityData( String dn, String aid, String parentAID, - String description) { + Boolean enabled, String description) { this.dn = dn; this.aid = aid; this.parentAID = parentAID; + this.enabled = enabled; this.description = description; } diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java index 817885687b04d1b10ea35dca9a1942fe5ce201f4..24134d01178b0bee3f5c3aa951f20fbbc36e8c99 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -2,6 +2,7 @@ package com.netscape.certsrv.authority; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; @@ -11,6 +12,7 @@ import org.jboss.resteasy.annotations.ClientResponseType; import com.netscape.certsrv.acls.ACLMapping; import com.netscape.certsrv.authentication.AuthMethodMapping; +import com.netscape.certsrv.base.PATCH; @Path("authorities") public interface AuthorityResource { @@ -33,4 +35,33 @@ public interface AuthorityResource { //@AuthMethodMapping("certs") public Response createCA(AuthorityData data); + /** + * Modify a CA. + * + * AuthorityID, AuthorityParentID and DN are immutable; + * differences in these values are ignored. + */ + @PUT + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response modifyCA( + @PathParam("id") String caIDString, + AuthorityData data); + + /** + * Modify a CA, supporting partial modifications. + * + * AuthorityID, AuthorityParentID and DN are immutable; + * these values are ignored. + * + * Other values, if null, are ignored, otherwise they are + * set to the new value. To remove the description, use an + * empty string. + */ + @PATCH + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response patchCA( + @PathParam("id") String caIDString, + AuthorityData data); } diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index d707bf848ba0973c2a817c45abeba8219bff574f..c31d08ac75553c2a03d0cee3cf2d55a893d50761 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -536,6 +536,11 @@ public interface ICertificateAuthority extends ISubsystem { /** * Return CA description. May be null. */ + public boolean getAuthorityEnabled(); + + /** + * Return CA description. May be null. + */ public String getAuthorityDescription(); /** @@ -559,4 +564,13 @@ public interface ICertificateAuthority extends ISubsystem { public ICertificateAuthority createSubCA( String dn, String desc) throws EBaseException; + + /** + * Update authority configurables. + * + * @param enabled Whether CA is enabled or disabled + * @param desc Description; null or empty removes it + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException; } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java index 19329cbba0fac3f8c9722cc6854cbeaf6a31c75c..244dd51aabe5ec876849b2fb32c00f74cc140aff 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -78,7 +78,7 @@ public class AuthorityCreateCLI extends CLI { String dn = cmdArgs[0]; AuthorityData data = new AuthorityData( - dn, null, parentAIDString, desc); + dn, null, parentAIDString, true /* enabled */, desc); AuthorityData newData = authorityCLI.authorityClient.createCA(data); AuthorityCLI.printAuthorityData(newData); } -- 2.4.3 -------------- next part -------------- From 0d79007e35cfd858938e40085ef290a48325a28e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Thu, 17 Sep 2015 02:26:35 -0400 Subject: [PATCH 50/50] Lightweight CAs: add enable/disable CLI Add the ca-authority-{disable,enable} commands and Java client support for the modify and patch API. Part of: https://fedorahosted.org/pki/ticket/1604 --- .../certsrv/authority/AuthorityClient.java | 10 ++++ .../netscape/cmstools/authority/AuthorityCLI.java | 7 ++- .../cmstools/authority/AuthorityDisableCLI.java | 59 ++++++++++++++++++++++ .../cmstools/authority/AuthorityEnableCLI.java | 59 ++++++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java index 7ad549c7d4bf85513aa64911fdf1c71c5ac4fc21..73dce1914fa926881012d7dd2481bbe823c13e3f 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -54,4 +54,14 @@ public class AuthorityClient extends Client { return client.getEntity(response, AuthorityData.class); } + public AuthorityData modifyCA(AuthorityData data) { + Response response = proxy.modifyCA(data.getID(), data); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData patchCA(AuthorityData data) { + Response response = proxy.patchCA(data.getID(), data); + return client.getEntity(response, AuthorityData.class); + } + } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java index f2b630d32f9b8fc12792d14c84e90e12f7c23f4d..f07e9916e9cd9b0f1ee1f6c6d5eb4906f9a94c85 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -27,6 +27,8 @@ public class AuthorityCLI extends CLI { addModule(new AuthorityFindCLI(this)); addModule(new AuthorityShowCLI(this)); addModule(new AuthorityCreateCLI(this)); + addModule(new AuthorityDisableCLI(this)); + addModule(new AuthorityEnableCLI(this)); } public String getFullName() { @@ -47,7 +49,10 @@ public class AuthorityCLI extends CLI { protected static void printAuthorityData(AuthorityData data) { System.out.println(" Issuer DN: " + data.getDN()); System.out.println(" ID: " + data.getID()); - System.out.println(" Parent ID: " + data.getParentID()); + String parentAID = data.getParentID(); + if (parentAID != null) + System.out.println(" Parent ID: " + data.getParentID()); + System.out.println(" Enabled: " + data.getEnabled()); String desc = data.getDescription(); if (desc != null) System.out.println(" Description: " + desc); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..2d008676ebb41722865205a4b5078ba3393a28ce --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java @@ -0,0 +1,59 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityDisableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityDisableCLI(AuthorityCLI authorityCLI) { + super("disable", "Disable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, false, null); + data = authorityCLI.authorityClient.patchCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..ff1f35ac4cd9e8507d9550bc22b29188ca115d9c --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java @@ -0,0 +1,59 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityEnableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityEnableCLI(AuthorityCLI authorityCLI) { + super("enable", "Enable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, true, null); + data = authorityCLI.authorityClient.patchCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} -- 2.4.3 From edewata at redhat.com Fri Sep 18 10:59:53 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Fri, 18 Sep 2015 05:59:53 -0500 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> Message-ID: <55FBEEA9.1090200@redhat.com> On 9/14/2015 2:25 AM, Fraser Tweedale wrote: > The latest lightweight CAs (f.k.a. Sub-CAs) patches are attached. > This cut has significant changes and additions including: > > - CAs are now stored in a flat structure with references to parents > > - CAs are now identified by "AuthorityID" (which for now is a UUID > underneath). Many variables, method names and user-visible > strings were updated accordingly. Out with "caRef" terminology. > > - "Sub-CA" terminology is (mostly) out; "Authority" is in. This is > to support lightweight CAs that are not descendents of top-level > CA (which can be implemented later). > > - ca-cert-request-submit command and related client / service > classes were updated to add "authority" parameter > > - Some more specific use of exception (including some new exception > classes) to indicate / catch particular errors. > > - More appropriate HTTP status codes return when client has send > invalid data (400), referenced unknown authority (404) or attempts > to create an authority with Subject DN already uesd by another > authority (409 Conflict) > > - LDAP entry now gets added before key generation and signing. If > something goes wrong, the DB entry is removed in the catch block. > > There are still notable gaps in functionality that are in progress > or will be implemented soon: > > - Audit log events > - Resources to enable/disable/delete authority > - Resources to access cert and pkcs7 chain of authority > - Keygen params > - Param to specify token on which to generate authority key > > Thanks, > Fraser > Some comments: 1. The db.ldif creates ou=subCAs instead of ou=authorities. 2. Is authority DN globally unique? If that's the case it can be used as a user-friendly representation of the UUID. We'll continue to use UUID for the LDAP entry DN and REST URL, but users don't really need to see it in the CLI (except in detailed output). So the CLI output can be changed as follows: * Issuer DN/ID -> Authority DN * Parent ID -> Parent DN I think "Issuer DN" is more appropriate to be used in the context of a certificate to refer to the authority that issued the certificate. 3. Right now it's possible to create an authority with the same DN as the main CA's DN (it created the LDAP entry, but later failed due to nickname mismatch). If authority DN is unique, we shouldn't allow that. We can fix this by creating the main authority LDAP entry during installation. It's not just for fixing this issue, but all authorities (including the top-level ones) should have the corresponding LDAP entries. 4. In CertificateAuthority.createCA() if no parent is specified, the authority will be added as a subordinate of the main CA. I think adding an authority without parent should be reserved for top-level authorities which we may support in the future. For now the ca-authority-create CLI should require a parent. In #3 we're adding the main CA entry, so there's always a parent. 5. In the CLI output, if the authority doesn't have a Parent ID it shows a "null". It would be better to show "None" instead, or just don't show the Parent ID at all. 6. Assuming authority DN is unique, we can add --issuer option to these commands: * pki ca-cert-find --issuer * pki ca-cert-request-submit --issuer * pki client-cert-find --issuer * pki client-cert-request --issuer 7. I think we should store authority DN and parent DN in the LDAP entry itself. This way we don't need to rely on the nickname and NSS database to figure out the DNs. 8. Right now the authority certs & keys are stored in the NSS database. I'm not sure about the scalability & manageability (e.g. replication, orphaned keys). It might be OK for now, but if possible they should be stored (securely) in the LDAP entry itself or in KRA. We probably only need to store the certs & keys of the top-level authorities in the NSS database. 9. To support paging in AuthorityService.listCAs() later you might want to use DataCollection because it provides the fields to put the total results, the entries in the current page, and the paging links. -- Endi S. Dewata From alee at redhat.com Fri Sep 18 18:46:11 2015 From: alee at redhat.com (Ade Lee) Date: Fri, 18 Sep 2015 14:46:11 -0400 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <55FBEEA9.1090200@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> Message-ID: <1442601971.6855.17.camel@redhat.com> On Fri, 2015-09-18 at 05:59 -0500, Endi Sukma Dewata wrote: > On 9/14/2015 2:25 AM, Fraser Tweedale wrote: > > The latest lightweight CAs (f.k.a. Sub-CAs) patches are attached. > > This cut has significant changes and additions including: > > > > - CAs are now stored in a flat structure with references to parents > > > > - CAs are now identified by "AuthorityID" (which for now is a UUID > > underneath). Many variables, method names and user-visible > > strings were updated accordingly. Out with "caRef" terminology. > > > > - "Sub-CA" terminology is (mostly) out; "Authority" is in. This is > > to support lightweight CAs that are not descendents of top-level > > CA (which can be implemented later). > > > > - ca-cert-request-submit command and related client / service > > classes were updated to add "authority" parameter > > > > - Some more specific use of exception (including some new exception > > classes) to indicate / catch particular errors. > > > > - More appropriate HTTP status codes return when client has send > > invalid data (400), referenced unknown authority (404) or > > attempts > > to create an authority with Subject DN already uesd by another > > authority (409 Conflict) > > > > - LDAP entry now gets added before key generation and signing. If > > something goes wrong, the DB entry is removed in the catch > > block. > > > > There are still notable gaps in functionality that are in progress > > or will be implemented soon: > > > > - Audit log events > > - Resources to enable/disable/delete authority > > - Resources to access cert and pkcs7 chain of authority > > - Keygen params > > - Param to specify token on which to generate authority key > > > > Thanks, > > Fraser > > > > Some comments: > > 1. The db.ldif creates ou=subCAs instead of ou=authorities. > yup -Fraser already knows about this one. > 2. Is authority DN globally unique? If that's the case it can be used > as > a user-friendly representation of the UUID. We'll continue to use > UUID > for the LDAP entry DN and REST URL, but users don't really need to > see > it in the CLI (except in detailed output). So the CLI output can be > changed as follows: > * Issuer DN/ID -> Authority DN > * Parent ID -> Parent DN > > I think "Issuer DN" is more appropriate to be used in the context of > a > certificate to refer to the authority that issued the certificate. > I think I've been living in the openstack world too long, but I have no objection to seeing UUIDs. In fact, you need to see the UUID in order to figure out what to put in for the authority ID when making a request. So, showing the ID is required. > 3. Right now it's possible to create an authority with the same DN as > the main CA's DN (it created the LDAP entry, but later failed due to > nickname mismatch). If authority DN is unique, we shouldn't allow > that. > agreed - we need to check for this. > We can fix this by creating the main authority LDAP entry during > installation. It's not just for fixing this issue, but all > authorities > (including the top-level ones) should have the corresponding LDAP > entries. > > 4. In CertificateAuthority.createCA() if no parent is specified, the > authority will be added as a subordinate of the main CA. I think > adding > an authority without parent should be reserved for top-level > authorities > which we may support in the future. For now the ca-authority-create > CLI > should require a parent. In #3 we're adding the main CA entry, so > there's always a parent. > thats a great idea. And yeah, we will eventually be considering multiple top-level authorities. > 5. In the CLI output, if the authority doesn't have a Parent ID it > shows > a "null". It would be better to show "None" instead, or just don't > show > the Parent ID at all. I like parent ids, but null is not user friendly. > > 6. Assuming authority DN is unique, we can add --issuer option > to > these commands: > * pki ca-cert-find --issuer > * pki ca-cert-request-submit --issuer > * pki client-cert-find --issuer > * pki client-cert-request --issuer > If we do this, then we need to be sure that the DN is normalized - both on input -- ie. when the subca is created (we need to do this in any case) and also on processing in the CLI. I'm ok with offering this as an option (maybe --issuer_dn), but the primary (and initially required option) will be using UUID. We can defer this mechanism to another ticket/patch. Please open one. > 7. I think we should store authority DN and parent DN in the LDAP > entry > itself. This way we don't need to rely on the nickname and NSS > database > to figure out the DNs. > agreed. > 8. Right now the authority certs & keys are stored in the NSS > database. > I'm not sure about the scalability & manageability (e.g. replication, > orphaned keys). It might be OK for now, but if possible they should > be > stored (securely) in the LDAP entry itself or in KRA. We probably > only > need to store the certs & keys of the top-level authorities in the > NSS > database. > This was a big discussion a few months ago, and it was decided then that we were not comfortable with storing the keys in ldap. We can reconsider - but the mechanism of transferring keys is supposed to be custodia. 9. To support paging in AuthorityService.listCAs() later you might want > to use DataCollection because it provides the fields to put the total > results, the entries in the current page, and the paging links. > agreed. From edewata at redhat.com Fri Sep 18 19:11:27 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Fri, 18 Sep 2015 14:11:27 -0500 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <1442601971.6855.17.camel@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> Message-ID: <55FC61DF.70906@redhat.com> On 9/18/2015 1:46 PM, Ade Lee wrote: >> 6. Assuming authority DN is unique, we can add --issuer option >> tothese commands: >> * pki ca-cert-find --issuer >> * pki ca-cert-request-submit --issuer >> * pki client-cert-find --issuer >> * pki client-cert-request --issuer >> > > If we do this, then we need to be sure that the DN is normalized - both > on input -- ie. when the subca is created (we need to do this in any > case) and also on processing in the CLI. > > I'm ok with offering this as an option (maybe --issuer_dn), but the > primary (and initially required option) will be using UUID. We can > defer this mechanism to another ticket/patch. Please open one. Per IRC discussion we agreed with these options: * --issuer-id * --issuer-dn to be added to the ca-cert-* and client-cert-request commands. For the client-cert-find command we can only provide this option: * --issuer-dn since issuer ID is irrelevant on the client. Personally I think the issuer DN would be more useful since that's the value that you see in certificates, so it's more consistent everywhere, and no need to do a lookup to find the issuer ID. Also, although most likely we will copy & paste the ID or DN anyway, the DN is easier to read and confirm that you're submitting the request to the right authority. -- Endi S. Dewata From cheimes at redhat.com Mon Sep 21 13:14:30 2015 From: cheimes at redhat.com (Christian Heimes) Date: Mon, 21 Sep 2015 15:14:30 +0200 Subject: [Pki-devel] PATCH 005] Replace legacy Python base64 invocations with Py3-safe code In-Reply-To: <55DE01BF.7050201@redhat.com> References: <55912FF5.1000201@redhat.com> <55D48CA2.8070003@redhat.com> <55DE01BF.7050201@redhat.com> Message-ID: <560002B6.2020708@redhat.com> On 2015-08-26 20:13, Endi Sukma Dewata wrote: > As discussed on IRC, in b64encode() there's a code that converts Unicode > string data into ASCII: > > if isinstance(data, six.text_type): > data = data.encode('ascii') > > This conversion will not work if the string contains non-ASCII > characters, which limits the usage of this method. > > It's not that Python 3's base64.b64encode() doesn't support ASCII text > as noted in the method description, but it cannot encode Unicode string > because Unicode doesn't have a binary representation unless it's encoded > first. > > I think in this case the proper encoding for Unicode is UTF-8. So the > line should be changed to: > > if isinstance(data, six.text_type): > data = data.encode('utf-8') > > In b64decode(), the incoming data is a Unicode string containing the > base-64 encoding characters which are all ASCII, so data.encode('ascii') > will work, but to be more consistent it can also use data.encode('utf-8'). We discussed the ticket a couple of weeks ago on IRC. The function is deliberately limited to ASCII only text in order to avoid encoding hell. Python 3 tries to avoid encoding bugs by removing implicit encoding of text and decoding of bytes. The special treatment is only required for encoding/decoding X.509 data in JSON strings for Python 3. Since it's a special case I changed the patch. The additional two functions are now called decode_cert() and encode_cert(). The functions are only used for X.509 PEM <-> DER in JSON. Christian -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-cheimes-0005-3-Replace-legacy-Python-base64-invocations.patch Type: text/x-patch Size: 11704 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 455 bytes Desc: OpenPGP digital signature URL: From edewata at redhat.com Mon Sep 21 22:51:46 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Mon, 21 Sep 2015 17:51:46 -0500 Subject: [Pki-devel] [PATCH] 642 Added support for secure database connection in CLI. In-Reply-To: <55F34BD1.7080109@redhat.com> References: <55F34BD1.7080109@redhat.com> Message-ID: <56008A02.6040603@redhat.com> On 9/11/2015 4:46 PM, Endi Sukma Dewata wrote: > The pki-server subsystem-cert-update has been modified to support > secure database connection with client certificate authentication. > The certificate and the private key will be exported temporarily > into PEM files so python-ldap can use them. > > The pki client-cert-show has been modified to provide an option > to export client certificate's private key. > > https://fedorahosted.org/pki/ticket/1551 ACKed by mharmsen with some changes. -- Endi S. Dewata -------------- next part -------------- >From bb6b49e0fba2b946c28d1beebfb6d22dfe6d568e Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Fri, 4 Sep 2015 06:30:27 +0200 Subject: [PATCH] Added support for secure database connection in CLI. The pki-server subsystem-cert-update has been modified to support secure database connection with client certificate authentication. The pki client-cert-show has been modified to provide an option to export client certificate's private key. https://fedorahosted.org/pki/ticket/1551 --- .../cmstools/client/ClientCertShowCLI.java | 176 +++++++++++++-------- base/server/python/pki/server/__init__.py | 99 +++++++++++- base/server/python/pki/server/ca.py | 8 +- 3 files changed, 204 insertions(+), 79 deletions(-) diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java index f79501cfc62bace815ade9a24196f877857c1aed..e44fae7457ac226711fdf9f19cf20722d3aab296 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java @@ -29,10 +29,8 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.mozilla.jss.crypto.X509Certificate; -import com.netscape.certsrv.cert.CertData; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; -import com.netscape.cmsutil.util.Utils; /** * @author Endi S. Dewata @@ -57,6 +55,10 @@ public class ClientCertShowCLI extends CLI { option.setArgName("path"); options.addOption(option); + option = new Option(null, "private-key", true, "PEM file to store the private key."); + option.setArgName("path"); + options.addOption(option); + option = new Option(null, "client-cert", true, "PEM file to store the certificate and the private key."); option.setArgName("path"); options.addOption(option); @@ -107,90 +109,82 @@ public class ClientCertShowCLI extends CLI { String nickname = cmdArgs[0]; String certPath = cmd.getOptionValue("cert"); + String privateKeyPath = cmd.getOptionValue("private-key"); + String clientCertPath = cmd.getOptionValue("client-cert"); String pkcs12Path = cmd.getOptionValue("pkcs12"); String pkcs12Password = cmd.getOptionValue("pkcs12-password"); - String clientCertPath = cmd.getOptionValue("client-cert"); - if (certPath != null) { + File pkcs12File; - if (verbose) System.out.println("Exporting certificate to " + clientCertPath + "."); + if (pkcs12Path != null) { + // exporting certificate to PKCS #12 file - // late initialization - mainCLI.init(); - - client = mainCLI.getClient(); - X509Certificate cert = client.getCert(nickname); - - try (PrintWriter out = new PrintWriter(new FileWriter(certPath))) { - out.println(CertData.HEADER); - out.println(Utils.base64encode(cert.getEncoded())); - out.println(CertData.FOOTER); - } - - } else if (pkcs12Path != null) { - - if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12Path + "."); + pkcs12File = new File(pkcs12Path); if (pkcs12Password == null) { throw new Exception("Missing PKCS #12 password"); } - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); + } else if (certPath != null || clientCertPath != null || privateKeyPath != null) { + // exporting certificate and/or private key to PEM files using temporary PKCS #12 file - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } - - // export certificate chain and private key into PKCS #12 file - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12Path, - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - } else if (clientCertPath != null) { - - if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); - - // generate random PKCS #12 password - pkcs12Password = RandomStringUtils.randomAlphanumeric(16); - - // store password into a temporary file - File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); - pkcs12PasswordFile.deleteOnExit(); - - try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { - out.print(pkcs12Password); - } - - // export certificate chain and private key into a temporary PKCS #12 file - File pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); + // prepare temporary PKCS #12 file + pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); pkcs12File.deleteOnExit(); - exportPKCS12( - mainCLI.certDatabase.getAbsolutePath(), - mainCLI.config.getCertPassword(), - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - nickname); - - // export client certificate and private key into a PEM file - exportClientCertificate( - pkcs12File.getAbsolutePath(), - pkcs12PasswordFile.getAbsolutePath(), - clientCertPath); + // generate random password + pkcs12Password = RandomStringUtils.randomAlphanumeric(16); } else { - // late initialization + // displaying certificate info + mainCLI.init(); client = mainCLI.getClient(); X509Certificate cert = client.getCert(nickname); ClientCLI.printCertInfo(cert); + return; + } + + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12File + "."); + exportPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + nickname); + + if (certPath != null) { + if (verbose) System.out.println("Exporting certificate to " + certPath + "."); + exportCertificate( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + certPath); + } + + if (privateKeyPath != null) { + if (verbose) System.out.println("Exporting private key to " + privateKeyPath + "."); + exportPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + privateKeyPath); + } + + if (clientCertPath != null) { + if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); + exportClientCertificateAndPrivateKey( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + clientCertPath); } } @@ -218,7 +212,53 @@ public class ClientCertShowCLI extends CLI { } } - public void exportClientCertificate( + public void exportCertificate( + String pkcs12Path, + String pkcs12PasswordPath, + String certPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-clcerts", // certificate only + "-nokeys", + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", certPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export certificate", e); + } + } + + public void exportPrivateKey( + String pkcs12Path, + String pkcs12PasswordPath, + String privateKeyPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-nocerts", // private key only + "-nodes", // no encryption + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", privateKeyPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export private key", e); + } + } + + public void exportClientCertificateAndPrivateKey( String pkcs12Path, String pkcs12PasswordPath, String clientCertPath) throws Exception { @@ -226,7 +266,7 @@ public class ClientCertShowCLI extends CLI { String[] command = { "/bin/openssl", "pkcs12", - "-clcerts", // client certificate only + "-clcerts", // client certificate and private key "-nodes", // no encryption "-in", pkcs12Path, "-passin", "file:" + pkcs12PasswordPath, @@ -237,7 +277,7 @@ public class ClientCertShowCLI extends CLI { run(command); } catch (Exception e) { - throw new Exception("Unable to export client certificate", e); + throw new Exception("Unable to export client certificate and private key", e); } } diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py index 70e35b8f2c7bc66339dc315dcab946177d87d1a3..ec4dd7e9cd677d9213d21e5d89d92123134ecc30 100644 --- a/base/server/python/pki/server/__init__.py +++ b/base/server/python/pki/server/__init__.py @@ -29,7 +29,9 @@ import operator import os import pwd import re +import shutil import subprocess +import tempfile import pki @@ -163,18 +165,43 @@ class PKISubsystem(object): def open_database(self, name='internaldb'): + # TODO: add LDAPI support hostname = self.config['%s.ldapconn.host' % name] port = self.config['%s.ldapconn.port' % name] - bind_dn = self.config['%s.ldapauth.bindDN' % name] + secure = self.config['%s.ldapconn.secureConn' % name] - # TODO: add support for other authentication - # mechanisms (e.g. client cert authentication, LDAPI) - bind_password = self.instance.get_password(name) + if secure == 'true': + url = 'ldaps://%s:%s' % (hostname, port) - con = ldap.initialize('ldap://%s:%s' % (hostname, port)) - con.simple_bind_s(bind_dn, bind_password) + elif secure == 'false': + url = 'ldap://%s:%s' % (hostname, port) - return con + else: + raise Exception('Invalid parameter value in %s.ldapconn.secureConn: %s' % (name, secure)) + + connection = PKIDatabaseConnection(url) + + connection.set_security_database(self.instance.nssdb_dir) + + auth_type = self.config['%s.ldapauth.authtype' % name] + if auth_type == 'BasicAuth': + connection.set_credentials( + bind_dn=self.config['%s.ldapauth.bindDN' % name], + bind_password=self.instance.get_password(name) + ) + + elif auth_type == 'SslClientAuth': + connection.set_credentials( + client_cert_nickname=self.config['%s.ldapauth.clientCertNickname' % name], + nssdb_password=self.instance.get_password('internal') + ) + + else: + raise Exception('Invalid parameter value in %s.ldapauth.authtype: %s' % (name, auth_type)) + + connection.open() + + return connection def __repr__(self): return str(self.instance) + '/' + self.name @@ -343,6 +370,64 @@ class PKIInstance(object): return self.name +class PKIDatabaseConnection(object): + + def __init__(self, url='ldap://localhost:389'): + + self.url = url + + self.nssdb_dir = None + + self.bind_dn = None + self.bind_password = None + + self.client_cert_nickname = None + self.nssdb_password = None + + self.temp_dir = None + self.ldap = None + + def set_security_database(self, nssdb_dir=None): + self.nssdb_dir = nssdb_dir + + def set_credentials(self, bind_dn=None, bind_password=None, + client_cert_nickname=None, nssdb_password=None): + self.bind_dn = bind_dn + self.bind_password = bind_password + self.client_cert_nickname = client_cert_nickname + self.nssdb_password = nssdb_password + + def open(self): + + self.temp_dir = tempfile.mkdtemp() + + if self.nssdb_dir: + + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.nssdb_dir) + + if self.client_cert_nickname: + + password_file = os.path.join(self.temp_dir, 'password.txt') + with open(password_file, 'w') as f: + f.write(self.nssdb_password) + + ldap.set_option(ldap.OPT_X_TLS_CERTFILE, self.client_cert_nickname) + ldap.set_option(ldap.OPT_X_TLS_KEYFILE, password_file) + + self.ldap = ldap.initialize(self.url) + + if self.bind_dn and self.bind_password: + self.ldap.simple_bind_s(self.bind_dn, self.bind_password) + + def close(self): + + if self.ldap: + self.ldap.unbind_s() + + if self.temp_dir: + shutil.rmtree(self.temp_dir) + + class PKIServerException(pki.PKIException): def __init__(self, message, exception=None, diff --git a/base/server/python/pki/server/ca.py b/base/server/python/pki/server/ca.py index 70ebf4dd1044ecad32a0cf072ba45c6ace5eacea..31e373ad85955fd1e64b08c8f5aedba8386384fa 100644 --- a/base/server/python/pki/server/ca.py +++ b/base/server/python/pki/server/ca.py @@ -45,13 +45,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'ou=ca,ou=requests,%s' % base_dn, ldap.SCOPE_ONELEVEL, search_filter, None) - con.unbind_s() + con.close() requests = [] for entry in entries: @@ -65,13 +65,13 @@ class CASubsystem(pki.server.PKISubsystem): con = self.open_database() - entries = con.search_s( + entries = con.ldap.search_s( 'cn=%s,ou=ca,ou=requests,%s' % (request_id, base_dn), ldap.SCOPE_BASE, '(objectClass=*)', None) - con.unbind_s() + con.close() entry = entries[0] return self.create_request_object(entry) -- 2.4.3 From edewata at redhat.com Tue Sep 22 17:55:26 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Tue, 22 Sep 2015 12:55:26 -0500 Subject: [Pki-devel] [PATCH] 644 Added support for directory-authenticated profiles in CLI. Message-ID: <5601960E.80003@redhat.com> The pki client-cert-request CLI has been modified to support directory-authenticated profiles by sending the username and password as XML/JSON request attributes. The CertRequetService will then put the credentials into an AuthCredentials object. The ProfileSubmitServlet has also been modified to create an AuthCredentials object from the HTTP request object. The certificate processor classes have been modified to accept an AuthCredentials object instead of retrieving it from HTTP request object. https://fedorahosted.org/pki/ticket/1463 -- Endi S. Dewata -------------- next part -------------- From 899a87d956cdfc9be24fb9bf3111bd3e1e5d36bb Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Thu, 17 Sep 2015 00:33:32 +0200 Subject: [PATCH] Added support for directory-authenticated profiles in CLI. The pki client-cert-request CLI has been modified to support directory-authenticated profiles by sending the username and password as XML/JSON request attributes. The CertRequetService will then put the credentials into an AuthCredentials object. The ProfileSubmitServlet has also been modified to create an AuthCredentials object from the HTTP request object. The certificate processor classes have been modified to accept an AuthCredentials object instead of retrieving it from HTTP request object. https://fedorahosted.org/pki/ticket/1463 --- .../server/ca/rest/CertRequestService.java | 6 +- .../certsrv/cert/CertEnrollmentRequest.java | 12 +- .../cmstools/client/ClientCertRequestCLI.java | 83 ++++++++-- .../cms/authentication/DirBasedAuthentication.java | 49 +++--- .../netscape/cms/servlet/cert/CertProcessor.java | 38 ++--- .../netscape/cms/servlet/cert/CertRequestDAO.java | 179 ++++++++++++++++++++- .../cms/servlet/cert/EnrollmentProcessor.java | 61 ++----- .../cms/servlet/cert/RenewalProcessor.java | 128 +++++---------- .../cms/servlet/processors/CAProcessor.java | 56 ++++--- .../cms/servlet/profile/ProfileSubmitServlet.java | 178 +++++++++++++++++++- .../cmscore/authentication/AuthSubsystem.java | 4 + 11 files changed, 560 insertions(+), 234 deletions(-) diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index a11cb470b21240127b405a694c92fc665dd9ed69..1974b9279f685f0ef07995eca7a1010505ce729e 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -132,7 +132,11 @@ public class CertRequestService extends PKIService implements CertRequestResourc CertRequestInfos infos; try { - infos = dao.submitRequest(data, servletRequest, uriInfo, getLocale(headers)); + if (data.isRenewal()) { + infos = dao.submitRenewalRequest(data, servletRequest, uriInfo, getLocale(headers)); + } else { + infos = dao.submitEnrollmentRequest(data, servletRequest, uriInfo, getLocale(headers)); + } } catch (EAuthException e) { CMS.debug("enrollCert: authentication failed: " + e); throw new UnauthorizedException(e.toString()); diff --git a/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java b/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java index 72aad330fecc63290c9e6d82e576971df499028e..ba43e884df944af857fa69e6bf663a7dead6e601 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java +++ b/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java @@ -37,6 +37,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import com.netscape.certsrv.base.ResourceMessage; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.profile.ProfileOutput; @@ -48,7 +49,7 @@ import com.netscape.certsrv.profile.ProfileOutput; @XmlRootElement(name = "CertEnrollmentRequest") @XmlAccessorType(XmlAccessType.FIELD) -public class CertEnrollmentRequest { +public class CertEnrollmentRequest extends ResourceMessage { private static final String PROFILE_ID = "profileId"; private static final String RENEWAL = "renewal"; @@ -278,7 +279,7 @@ public class CertEnrollmentRequest { @Override public int hashCode() { final int prime = 31; - int result = 1; + int result = super.hashCode(); result = prime * result + ((inputs == null) ? 0 : inputs.hashCode()); result = prime * result + ((outputs == null) ? 0 : outputs.hashCode()); result = prime * result + ((profileId == null) ? 0 : profileId.hashCode()); @@ -293,7 +294,7 @@ public class CertEnrollmentRequest { public boolean equals(Object obj) { if (this == obj) return true; - if (obj == null) + if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; @@ -338,8 +339,6 @@ public class CertEnrollmentRequest { before.setProfileId("caUserCert"); before.setRenewal(false); - //Simulate a "caUserCert" Profile enrollment - ProfileInput certReq = before.createInput("KeyGenInput"); certReq.addAttribute(new ProfileAttribute("cert_request_type", "crmf", null)); certReq.addAttribute(new ProfileAttribute( @@ -363,6 +362,9 @@ public class CertEnrollmentRequest { submitter.addAttribute(new ProfileAttribute("requestor_email", "admin at redhat.com", null)); submitter.addAttribute(new ProfileAttribute("requestor_phone", "650-555-5555", null)); + before.setAttribute("uid", "testuser"); + before.setAttribute("pwd", "password"); + String xml = before.toXML(); System.out.println(xml); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..813140745b5f1f526e859011de4080f36e7a9994 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -19,13 +19,13 @@ package com.netscape.cmstools.client; import java.io.ByteArrayOutputStream; +import java.io.Console; import java.io.File; import java.security.KeyPair; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; -import netscape.ldap.util.DN; -import netscape.ldap.util.RDN; - import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.io.FileUtils; @@ -50,6 +50,9 @@ import com.netscape.cmstools.cli.MainCLI; import com.netscape.cmsutil.util.Cert; import com.netscape.cmsutil.util.Utils; +import netscape.ldap.util.DN; +import netscape.ldap.util.RDN; + /** * @author Endi S. Dewata */ @@ -73,6 +76,10 @@ public class ClientCertRequestCLI extends CLI { option.setArgName("request type"); options.addOption(option); + option = new Option(null, "password", true, "Request password"); + option.setArgName("password"); + options.addOption(option); + option = new Option(null, "attribute-encoding", false, "Enable Attribute encoding"); options.addOption(option); @@ -265,20 +272,72 @@ public class ClientCertRequestCLI extends CLI { } } + // parse subject DN and put the values in a map + 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) { - DN dn = new DN(subjectDN); - Vector rdns = dn.getRDNs(); - - 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]; - ProfileAttribute uidAttr = sn.getAttribute("sn_" + type); - uidAttr.setValue(value); + 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); } } + // get password from CLI option + String requestPassword = cmd.getOptionValue("password"); + + // get credentials for LDAP-authenticated profiles + // TODO: remove hard-coded profile names + if (profileID.equals("caDirUserCert") + || profileID.equals("caECDirUserCert") + || profileID.equals("caDirUserRenewal")) { + + // get UID from subject DN + String value = subjectAttributes.get("uid"); + request.setAttribute("uid", value); + + // if not specified, get from console + if (requestPassword == null) { + Console console = System.console(); + requestPassword = new String(console.readPassword("Password: ")); + } + } + + // store password if specified + if (requestPassword != null) { + request.setAttribute("pwd", requestPassword); + } + if (verbose) { System.out.println("Sending certificate request."); } diff --git a/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java b/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java index a8a95284df14bd287f7a9a0f5ccba43bf174c4a8..efbfb8053828c741d11b2a6ee574fda660a0f767 100644 --- a/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java +++ b/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java @@ -26,18 +26,6 @@ import java.util.Locale; import java.util.StringTokenizer; import java.util.Vector; -import netscape.ldap.LDAPAttribute; -import netscape.ldap.LDAPConnection; -import netscape.ldap.LDAPEntry; -import netscape.ldap.LDAPException; -import netscape.ldap.LDAPSearchResults; -import netscape.ldap.LDAPv2; -import netscape.security.x509.CertificateExtensions; -import netscape.security.x509.CertificateSubjectName; -import netscape.security.x509.CertificateValidity; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CertInfo; - import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.AuthToken; import com.netscape.certsrv.authentication.EAuthException; @@ -56,6 +44,18 @@ import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.cmsutil.util.Utils; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSearchResults; +import netscape.ldap.LDAPv2; +import netscape.security.x509.CertificateExtensions; +import netscape.security.x509.CertificateSubjectName; +import netscape.security.x509.CertificateValidity; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CertInfo; + /** * Abstract class for directory based authentication managers * Uses a pattern for formulating subject names. @@ -256,26 +256,35 @@ public abstract class DirBasedAuthentication mImplName = implName; mConfig = config; + CMS.debug(name + ": initialization"); + /* initialize ldap server configuration */ mLdapConfig = mConfig.getSubStore(PROP_LDAP); + + CMS.debug(name + ": needBaseDN: " + needBaseDN); if (needBaseDN) { + mBaseDN = mLdapConfig.getString(PROP_BASEDN); - if (mBaseDN == null || mBaseDN.trim().equals("")) + if (mBaseDN == null || mBaseDN.trim().equals("")) { + CMS.debug(name + ": missing basedn"); throw new EPropertyNotFound(CMS.getUserMessage("CMS_BASE_GET_PROPERTY_FAILED", "basedn")); + } + CMS.debug(name + ": basedn: " + mBaseDN); + mGroupsEnable = mLdapConfig.getBoolean(PROP_GROUPS_ENABLE, false); - CMS.debug("DirBasedAuthentication: mGroupsEnable=" + (mGroupsEnable ? "true" : "false")); + CMS.debug(name + ": groupsEnable: " + mGroupsEnable); mGroupsBaseDN = mLdapConfig.getString(PROP_GROUPS_BASEDN, mBaseDN); - CMS.debug("DirBasedAuthentication: mGroupsBaseDN="+ mGroupsBaseDN); + CMS.debug(name + ": groupsBaseDN: " + mGroupsBaseDN); mGroups= mLdapConfig.getString(PROP_GROUPS, "ou=groups"); - CMS.debug("DirBasedAuthentication: mGroups="+ mGroups); + CMS.debug(name + ": groups: " + mGroups); mGroupObjectClass = mLdapConfig.getString(PROP_GROUP_OBJECT_CLASS, "groupofuniquenames"); - CMS.debug("DirBasedAuthentication: mGroupObjectClass="+ mGroupObjectClass); + CMS.debug(name + ": groupObjectClass: " + mGroupObjectClass); mUserIDName = mLdapConfig.getString(PROP_USERID_NAME, "uid"); - CMS.debug("DirBasedAuthentication: mUserIDName="+ mUserIDName); + CMS.debug(name + ": userIDName: " + mUserIDName); mSearchGroupUserByUserdn = mLdapConfig.getBoolean(PROP_SEARCH_GROUP_USER_BY_USERDN, true); - CMS.debug("DirBasedAuthentication: mSearchGroupUserByUserdn="+ mSearchGroupUserByUserdn); + CMS.debug(name + ": searchGroupUserByUserdn: " + mSearchGroupUserByUserdn); mGroupUserIDName = mLdapConfig.getString(PROP_GROUP_USERID_NAME, "cn"); - CMS.debug("DirBasedAuthentication: mGroupUserIDName="+ mGroupUserIDName); + CMS.debug(name + ": groupUserIDName: " + mGroupUserIDName); } mConnFactory = CMS.getLdapAnonConnFactory("DirBasedAuthentication"); mConnFactory.init(mLdapConfig); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java index 4cd54a25719bcd82728ef803f225bac481211584..d235b247bf132a6b3038ac592b3a677b66158ab2 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java @@ -23,8 +23,6 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; -import javax.servlet.http.HttpServletRequest; - import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.IAuthToken; import com.netscape.certsrv.base.EBaseException; @@ -42,6 +40,7 @@ import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.request.INotify; import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.request.RequestStatus; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cmsutil.ldap.LDAPUtil; @@ -51,26 +50,27 @@ public class CertProcessor extends CAProcessor { super(id, locale); } - protected void setCredentialsIntoContext(HttpServletRequest request, IProfileAuthenticator authenticator, - IProfileContext ctx) { - Enumeration authIds = authenticator.getValueNames(); + /** + * Copy credentials required by profile authenticator into profile context. + */ + protected void setCredentialsIntoContext( + IProfileAuthenticator authenticator, + IProfileContext ctx, + AuthCredentials credentials) { - if (authIds != null) { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authNames not null"); - while (authIds.hasMoreElements()) { - String authName = authIds.nextElement(); + if (authenticator != null) { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName:" + - authName); - if (request.getParameter(authName) != null) { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName found in request"); - ctx.set(authName, request.getParameter(authName)); - } else { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName not found in request"); - } + CMS.debug("CertProcessor: getting credentials for " + authenticator.getName()); + Enumeration names = authenticator.getValueNames(); + if (names == null) return; + + while (names.hasMoreElements()) { + String name = names.nextElement(); + Object value = credentials.get(name); + if (value == null) continue; + + ctx.set(name, value.toString()); } - } else { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authIds` null"); } } diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index c94ee14961ef39681a53f506b24e4ca5ab06a27e..5fc89b3e936fb05adc6c72cdf72aa0a1aaaaee23 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -17,7 +17,9 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.cms.servlet.cert; +import java.math.BigInteger; import java.util.Collection; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -28,14 +30,20 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang.StringUtils; + import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.certsrv.cert.CertReviewResponse; +import com.netscape.certsrv.dbs.certdb.ICertRecord; +import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.profile.IProfile; +import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileSubsystem; import com.netscape.certsrv.request.CMSRequestInfo; import com.netscape.certsrv.request.CMSRequestInfos; @@ -43,6 +51,7 @@ import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.request.IRequestQueue; import com.netscape.certsrv.request.RequestId; import com.netscape.certsrv.request.RequestNotFoundException; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cms.servlet.request.CMSRequestDAO; @@ -164,20 +173,176 @@ public class CertRequestDAO extends CMSRequestDAO { * @throws EBaseException * @throws ServletException */ - public CertRequestInfos submitRequest(CertEnrollmentRequest data, HttpServletRequest request, UriInfo uriInfo, + public CertRequestInfos submitEnrollmentRequest( + CertEnrollmentRequest data, + HttpServletRequest request, + UriInfo uriInfo, Locale locale) throws EBaseException { CertRequestInfos ret = new CertRequestInfos(); - HashMap results = null; - if (data.isRenewal()) { - RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); - results = processor.processRenewal(data, request); + EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); + String profileId = processor.getProfileID() == null ? data.getProfileId() : processor.getProfileID(); + CMS.debug("CertRequestDAO: profile: " + profileId); + + IProfile profile = ps.getProfile(profileId); + if (profile == null) { + CMS.debug("CertRequestDAO: Profile " + profileId + " not found"); + throw new BadRequestDataException("Profile " + profileId + " not found"); + } + + if (!ps.isProfileEnable(profileId)) { + CMS.debug("CertRequestDAO: Profile " + profileId + " not enabled"); + throw new BadRequestDataException("Profile " + profileId + " not enabled"); + } + + AuthCredentials credentials = new AuthCredentials(); + + // get credentials from request attributes + IProfileAuthenticator authenticator = profile.getAuthenticator(); + if (authenticator != null) { + + Enumeration names = authenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = data.getAttribute(name); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + HashMap results = processor.processEnrollment(profile, data, request, credentials); + + IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); + for (IRequest req : reqs) { + CertRequestInfo info = CertRequestInfoFactory.create(req, uriInfo); + ret.addEntry(info); + } + + ret.setTotal(ret.getEntries().size()); + + // TODO - what happens if the errorCode is internal error ? + + return ret; + } + + /** + * Submits a renewal request and processes it. + * + * @param data + * @return info for the request submitted. + * @throws EBaseException + * @throws ServletException + */ + public CertRequestInfos submitRenewalRequest( + CertEnrollmentRequest data, + HttpServletRequest request, + UriInfo uriInfo, + Locale locale) throws EBaseException { + + CertRequestInfos ret = new CertRequestInfos(); + + RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); + String profileId = processor.getProfileID() == null ? data.getProfileId() : processor.getProfileID(); + CMS.debug("CertRequestDAO: profile: " + profileId); + + IProfile profile = ps.getProfile(profileId); + if (profile == null) { + CMS.debug("CertRequestDAO: Profile " + profileId + " not found"); + throw new BadRequestDataException("Profile " + profileId + " not found"); + } + + if (!ps.isProfileEnable(profileId)) { + CMS.debug("CertRequestDAO: Profile " + profileId + " not enabled"); + throw new BadRequestDataException("Profile " + profileId + " not enabled"); + } + + String serial = request.getParameter("serial_num"); + BigInteger certSerial = null; + + if (StringUtils.isNotEmpty(serial)) { + // if serial number is sent with request, then the authentication + // method is not ssl client auth. In this case, an alternative + // authentication method is used (default: ldap based) + // usr_origreq evaluator should be used to authorize ownership + // of the cert + CMS.debug("CertRequestDAO: renewal: serial number: " + serial); + certSerial = new BigInteger(serial); + } else { - EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request); + // ssl client auth is to be used + // this is not authentication. Just use the cert to search + // for orig request and find the right profile + CMS.debug("CertRequestDAO: renewal: serial_num not found, must do ssl client auth"); + certSerial = processor.getSerialNumberFromCert(request); + + if (certSerial == null) { + CMS.debug(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); + } } + ICertificateAuthority authority = (ICertificateAuthority) CMS.getSubsystem("ca"); + ICertificateRepository certdb = authority.getCertificateRepository(); + + CMS.debug("CertRequestDAO: serial number of cert to renew: " + certSerial); + ICertRecord record = certdb.readCertificateRecord(certSerial); + if (record == null) { + CMS.debug("CertRequestDAO: cert record not found for serial number " + certSerial); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); + } + + IRequest origReq = processor.getOriginalRequest(certSerial, record); + if (origReq == null) { + CMS.debug("CertRequestDAO: original request not found"); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); + } + + String origProfileId = origReq.getExtDataInString("profileId"); + IProfile origProfile = ps.getProfile(origProfileId); + + AuthCredentials credentials = new AuthCredentials(); + + // get credentials from request attributes + IProfileAuthenticator authenticator = profile.getAuthenticator(); + if (authenticator != null) { + CMS.debug("CertRequestDAO: authenticator " + authenticator.getName() + " found"); + + Enumeration names = authenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = data.getAttribute(name); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + // for renewal, this will override or add auth info to the profile context + IProfileAuthenticator origAuthenticator = origProfile.getAuthenticator(); + if (origAuthenticator != null) { + CMS.debug("RenewalProcessor: for renewal, original authenticator " + + origAuthenticator.getName() + " found"); + + Enumeration names = origAuthenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = data.getAttribute(name); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + HashMap results = processor.processRenewal(profile, origProfile, record, data, request, credentials); + IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); for (IRequest req : reqs) { CertRequestInfo info = CertRequestInfoFactory.create(req, uriInfo); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 8d9d05cb7676f012eed8ef199f4e65f34d5e6ebe..d9d0ef725fc97fcd4d3b9459e41295f317902ec2 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -25,7 +25,6 @@ import javax.servlet.http.HttpServletRequest; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.IAuthToken; -import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; @@ -37,8 +36,7 @@ import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.request.IRequest; -import com.netscape.cms.servlet.common.CMSRequest; -import com.netscape.cms.servlet.common.CMSTemplate; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.profile.SSLClientCertProvider; import com.netscape.cmsutil.ldap.LDAPUtil; @@ -82,26 +80,6 @@ public class EnrollmentProcessor extends CertProcessor { } /** - * Called by the legacy servlets to access the Processor function - * @param request - * @return - * @throws EBaseException - */ - public HashMap processEnrollment(CMSRequest cmsReq) throws EBaseException { - HttpServletRequest req = cmsReq.getHttpReq(); - String profileId = (this.profileID == null) ? req.getParameter("profileId") : this.profileID; - IProfile profile = ps.getProfile(profileId); - - if (profile == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", CMSTemplate.escapeJavaScriptStringHTML(profileId))); - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND",CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - - CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq()); - } - - /** * Process the HTTP request *

* @@ -118,7 +96,11 @@ public class EnrollmentProcessor extends CertProcessor { * @param cmsReq the object holding the request and response information * @exception EBaseException an error has occurred */ - public HashMap processEnrollment(CertEnrollmentRequest data, HttpServletRequest request) + public HashMap processEnrollment( + IProfile profile, + CertEnrollmentRequest data, + HttpServletRequest request, + AuthCredentials credentials) throws EBaseException { try { @@ -127,23 +109,10 @@ public class EnrollmentProcessor extends CertProcessor { printParameterValues(params); } - CMS.debug("EnrollmentSubmitter: isRenewal false"); startTiming("enrollment"); - // if we did not configure profileId in xml file, - // then accept the user-provided one - String profileId = (this.profileID == null) ? data.getProfileId() : this.profileID; - CMS.debug("EnrollmentSubmitter: profileId " + profileId); - - IProfile profile = ps.getProfile(profileId); - if (profile == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", CMSTemplate.escapeJavaScriptStringHTML(profileId))); - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - if (!ps.isProfileEnable(profileId)) { - CMS.debug("EnrollmentSubmitter: Profile " + profileId + " not enabled"); - throw new BadRequestDataException("Profile " + profileId + " not enabled"); - } + String profileId = profile.getId(); + CMS.debug("EnrollmentProcessor: profile: " + profileId); IProfileContext ctx = profile.createContext(); CMS.debug("EnrollmentSubmitter: set Inputs into profile Context"); @@ -151,8 +120,8 @@ public class EnrollmentProcessor extends CertProcessor { IProfileAuthenticator authenticator = profile.getAuthenticator(); if (authenticator != null) { - CMS.debug("EnrollmentSubmitter: authenticator " + authenticator.getName() + " found"); - setCredentialsIntoContext(request, authenticator, ctx); + CMS.debug("EnrollmentProcessor: authenticator " + authenticator.getName() + " found"); + setCredentialsIntoContext(authenticator, ctx, credentials); } // for ssl authentication; pass in servlet for retrieving ssl client certificates @@ -163,14 +132,17 @@ public class EnrollmentProcessor extends CertProcessor { CMS.debug("EnrollmentSubmitter: set sslClientCertProvider"); // before creating the request, authenticate the request - IAuthToken authToken = authenticate(request, null, authenticator, context, false); + CMS.debug("EnrollmentProcessor: authenticating request"); + IAuthToken authToken = authenticate(request, null, authenticator, context, false, credentials); // authentication success, now authorize + CMS.debug("EnrollmentProcessor: authorizing request"); authorize(profileId, profile, authToken); /////////////////////////////////////////////// // create and populate request /////////////////////////////////////////////// + CMS.debug("EnrollmentProcessor: create and populate request"); startTiming("request_population"); IRequest[] reqs = profile.createRequests(ctx, locale); populateRequests(data, false, locale, null, null, null, profileId, profile, @@ -193,13 +165,10 @@ public class EnrollmentProcessor extends CertProcessor { endTiming("enrollment"); return ret; + } finally { SessionContext.releaseContext(); endAllEvents(); } } - - - - } diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java index efd1d7b0cf799dc399257502cb3f4e3196174b50..d0e20172774be591ce77d4b26cec6df9cfa44b5b 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java @@ -26,11 +26,6 @@ import java.util.Locale; import javax.servlet.http.HttpServletRequest; -import netscape.security.x509.BasicConstraintsExtension; -import netscape.security.x509.X509CertImpl; - -import org.apache.commons.lang.StringUtils; - import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.IAuthToken; import com.netscape.certsrv.base.BadRequestDataException; @@ -45,33 +40,18 @@ import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.request.IRequest; -import com.netscape.cms.servlet.common.CMSRequest; -import com.netscape.cms.servlet.common.CMSTemplate; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.profile.SSLClientCertProvider; +import netscape.security.x509.BasicConstraintsExtension; +import netscape.security.x509.X509CertImpl; + public class RenewalProcessor extends CertProcessor { public RenewalProcessor(String id, Locale locale) throws EPropertyNotFound, EBaseException { super(id, locale); } - public HashMap processRenewal(CMSRequest cmsReq) throws EBaseException { - HttpServletRequest req = cmsReq.getHttpReq(); - String profileId = (this.profileID == null) ? req.getParameter("profileId") : this.profileID; - IProfile profile = ps.getProfile(profileId); - if (profile == null) { - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", - CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - - CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - - //only used in renewal - data.setSerialNum(req.getParameter("serial_num")); - - return processRenewal(data, req); - } - /* * Renewal - Renewal is retrofitted into the Profile Enrollment * Framework. The authentication and authorization are taken from @@ -81,8 +61,15 @@ public class RenewalProcessor extends CertProcessor { * Things to note: * * the renew request will contain the original profile instead of the new */ - public HashMap processRenewal(CertEnrollmentRequest data, HttpServletRequest request) + public HashMap processRenewal( + IProfile renewProfile, + IProfile origProfile, + ICertRecord record, + CertEnrollmentRequest data, + HttpServletRequest request, + AuthCredentials credentials) throws EBaseException { + try { if (CMS.debugOn()) { HashMap params = data.toParams(); @@ -98,58 +85,19 @@ public class RenewalProcessor extends CertProcessor { String renewProfileId = (this.profileID == null) ? data.getProfileId() : this.profileID; CMS.debug("processRenewal: renewProfileId " + renewProfileId); - IProfile renewProfile = ps.getProfile(renewProfileId); - if (renewProfile == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", - CMSTemplate.escapeJavaScriptStringHTML(renewProfileId))); - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND",CMSTemplate.escapeJavaScriptStringHTML(renewProfileId))); - } - if (!ps.isProfileEnable(renewProfileId)) { - CMS.debug("RenewalSubmitter: Profile " + renewProfileId + " not enabled"); - throw new BadRequestDataException("Profile " + renewProfileId + " not enabled"); - } - - String serial = data.getSerialNum(); - BigInteger certSerial = null; - - if (StringUtils.isNotEmpty(serial)) { - // if serial number is sent with request, then the authentication - // method is not ssl client auth. In this case, an alternative - // authentication method is used (default: ldap based) - // usr_origreq evaluator should be used to authorize ownership - // of the cert - CMS.debug("RenewalSubmitter: renewal: serial number: " + serial); - certSerial = new BigInteger(serial); - - } else { - // ssl client auth is to be used - // this is not authentication. Just use the cert to search - // for orig request and find the right profile - CMS.debug("RenewalSubmitter: renewal: serial_num not found, must do ssl client auth"); - certSerial = getSerialNumberFromCert(request); - - if (certSerial == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); - throw new EBaseException(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); - } - } + BigInteger certSerial = record.getSerialNumber(); CMS.debug("processRenewal: serial number of cert to renew:" + certSerial.toString()); - ICertRecord rec = certdb.readCertificateRecord(certSerial); - if (rec == null) { - CMS.debug("processRenewal: cert record not found for serial number " + certSerial.toString()); - throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); - } // check to see if the cert is revoked or revoked_expired - if ((rec.getStatus().equals(ICertRecord.STATUS_REVOKED)) - || (rec.getStatus().equals(ICertRecord.STATUS_REVOKED_EXPIRED))) { + if ((record.getStatus().equals(ICertRecord.STATUS_REVOKED)) + || (record.getStatus().equals(ICertRecord.STATUS_REVOKED_EXPIRED))) { CMS.debug("processRenewal: cert found to be revoked. Serial number = " + certSerial.toString()); throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_CA_CANNOT_RENEW_REVOKED_CERT")); } - X509CertImpl origCert = rec.getCertificate(); + X509CertImpl origCert = record.getCertificate(); if (origCert == null) { CMS.debug("processRenewal: original cert not found in cert record for serial number " + certSerial.toString()); @@ -162,45 +110,41 @@ public class RenewalProcessor extends CertProcessor { String origSubjectDN = origCert.getSubjectDN().getName(); CMS.debug("processRenewal: orig subj dn =" + origSubjectDN); - IRequest origReq = getOriginalRequest(certSerial, rec); + IRequest origReq = getOriginalRequest(certSerial, record); if (origReq == null) { CMS.debug("processRenewal: original request not found"); throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); } - String profileId = origReq.getExtDataInString("profileId"); - CMS.debug("RenewalSubmitter: renewal original profileId=" + profileId); + String origProfileId = origReq.getExtDataInString("profileId"); + CMS.debug("RenewalSubmitter: renewal original profileId=" + origProfileId); Integer origSeqNum = origReq.getExtDataInInteger(IEnrollProfile.REQUEST_SEQ_NUM); - IProfile profile = ps.getProfile(profileId); - if (profile == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND",CMSTemplate.escapeJavaScriptStringHTML(profileId))); - throw new EBaseException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - if (!ps.isProfileEnable(profileId)) { - CMS.debug("RenewalSubmitter: Profile " + profileId + " not enabled"); - throw new BadRequestDataException("Profile " + profileId + " not enabled"); + + if (!ps.isProfileEnable(origProfileId)) { + CMS.debug("RenewalSubmitter: Profile " + origProfileId + " not enabled"); + throw new BadRequestDataException("Profile " + origProfileId + " not enabled"); } - IProfileContext ctx = profile.createContext(); + IProfileContext ctx = origProfile.createContext(); IProfileAuthenticator authenticator = renewProfile.getAuthenticator(); - IProfileAuthenticator origAuthenticator = profile.getAuthenticator(); + IProfileAuthenticator origAuthenticator = origProfile.getAuthenticator(); if (authenticator != null) { CMS.debug("RenewalSubmitter: authenticator " + authenticator.getName() + " found"); - setCredentialsIntoContext(request, authenticator, ctx); + setCredentialsIntoContext(authenticator, ctx, credentials); } // for renewal, this will override or add auth info to the profile context if (origAuthenticator != null) { CMS.debug("RenewalSubmitter: for renewal, original authenticator " + origAuthenticator.getName() + " found"); - setCredentialsIntoContext(request, origAuthenticator, ctx); + setCredentialsIntoContext(origAuthenticator, ctx, credentials); } // for renewal, input needs to be retrieved from the orig req record CMS.debug("processRenewal: set original Inputs into profile Context"); - setInputsIntoContext(origReq, profile, ctx, locale); + setInputsIntoContext(origReq, origProfile, ctx, locale); ctx.set(IEnrollProfile.CTX_RENEWAL, "true"); ctx.set("renewProfileId", renewProfileId); ctx.set(IEnrollProfile.CTX_RENEWAL_SEQ_NUM, origSeqNum.toString()); @@ -215,31 +159,31 @@ public class RenewalProcessor extends CertProcessor { context.put("origSubjectDN", origSubjectDN); // before creating the request, authenticate the request - IAuthToken authToken = authenticate(request, origReq, authenticator, context, true); + IAuthToken authToken = authenticate(request, origReq, authenticator, context, true, credentials); // authentication success, now authorize - authorize(profileId, renewProfile, authToken); + authorize(origProfileId, renewProfile, authToken); /////////////////////////////////////////////// // create and populate requests /////////////////////////////////////////////// startTiming("request_population"); - IRequest[] reqs = profile.createRequests(ctx, locale); - populateRequests(data, true, locale, origNotAfter, origSubjectDN, origReq, profileId, - profile, ctx, authenticator, authToken, reqs); + IRequest[] reqs = origProfile.createRequests(ctx, locale); + populateRequests(data, true, locale, origNotAfter, origSubjectDN, origReq, origProfileId, + origProfile, ctx, authenticator, authToken, reqs); endTiming("request_population"); /////////////////////////////////////////////// // submit request /////////////////////////////////////////////// - String errorCode = submitRequests(locale, profile, authToken, reqs); + String errorCode = submitRequests(locale, origProfile, authToken, reqs); String errorReason = codeToReason(locale, errorCode); HashMap ret = new HashMap(); ret.put(ARG_REQUESTS, reqs); ret.put(ARG_ERROR_CODE, errorCode); ret.put(ARG_ERROR_REASON, errorReason); - ret.put(ARG_PROFILE, profile); + ret.put(ARG_PROFILE, origProfile); CMS.debug("RenewalSubmitter: done serving"); endTiming("enrollment"); @@ -251,7 +195,7 @@ public class RenewalProcessor extends CertProcessor { } } - private BigInteger getSerialNumberFromCert(HttpServletRequest request) throws EBaseException { + public BigInteger getSerialNumberFromCert(HttpServletRequest request) throws EBaseException { BigInteger certSerial; SSLClientCertProvider sslCCP = new SSLClientCertProvider(request); X509Certificate[] certs = sslCCP.getClientCertificateChain(); diff --git a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java index 28b1b5130901297ad6eac199f32f5de588bee94d..e5b63f8cc25e60a7dd3a7dbab86dd653e88c7ad8 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java @@ -34,8 +34,6 @@ import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; -import netscape.security.x509.X509CertImpl; - import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.AuthToken; import com.netscape.certsrv.authentication.IAuthToken; @@ -69,6 +67,8 @@ import com.netscape.cms.servlet.common.CMSGateway; import com.netscape.cms.servlet.common.ServletUtils; import com.netscape.cmsutil.util.Utils; +import netscape.security.x509.X509CertImpl; + public class CAProcessor extends Processor { public final static String ARG_AUTH_TOKEN = "auth_token"; @@ -196,6 +196,10 @@ public class CAProcessor extends Processor { } } + public String getProfileID() { + return profileID; + } + /****************************************** * Stats - to be moved to Stats module ******************************************/ @@ -237,7 +241,7 @@ public class CAProcessor extends Processor { return request; } - protected IRequest getOriginalRequest(BigInteger certSerial, ICertRecord rec) throws EBaseException { + public IRequest getOriginalRequest(BigInteger certSerial, ICertRecord rec) throws EBaseException { MetaInfo metaInfo = (MetaInfo) rec.get(ICertRecord.ATTR_META_INFO); if (metaInfo == null) { CMS.debug("getOriginalRequest: cert record locating MetaInfo failed for serial number " @@ -351,10 +355,14 @@ public class CAProcessor extends Processor { * authenticate for renewal - more to add necessary params/values * to the session context */ - public IAuthToken authenticate(IProfileAuthenticator authenticator, - HttpServletRequest request, IRequest origReq, SessionContext context) throws EBaseException + public IAuthToken authenticate( + IProfileAuthenticator authenticator, + HttpServletRequest request, + IRequest origReq, + SessionContext context, + AuthCredentials credentials) throws EBaseException { - IAuthToken authToken = authenticate(authenticator, request); + IAuthToken authToken = authenticate(authenticator, request, credentials); // For renewal, fill in necessary params if (authToken != null) { String ouid = origReq.getExtDataInString("auth_token.uid"); @@ -410,20 +418,10 @@ public class CAProcessor extends Processor { return authToken; } - public IAuthToken authenticate(IProfileAuthenticator authenticator, - HttpServletRequest request) throws EBaseException { - AuthCredentials credentials = new AuthCredentials(); - - // build credential - Enumeration authNames = authenticator.getValueNames(); - - if (authNames != null) { - while (authNames.hasMoreElements()) { - String authName = authNames.nextElement(); - - credentials.set(authName, request.getParameter(authName)); - } - } + public IAuthToken authenticate( + IProfileAuthenticator authenticator, + HttpServletRequest request, + AuthCredentials credentials) throws EBaseException { credentials.set("clientHost", request.getRemoteHost()); IAuthToken authToken = authenticator.authenticate(credentials); @@ -440,8 +438,14 @@ public class CAProcessor extends Processor { return authToken; } - public IAuthToken authenticate(HttpServletRequest request, IRequest origReq, IProfileAuthenticator authenticator, - SessionContext context, boolean isRenewal) throws EBaseException { + public IAuthToken authenticate( + HttpServletRequest request, + IRequest origReq, + IProfileAuthenticator authenticator, + SessionContext context, + boolean isRenewal, + AuthCredentials credentials) throws EBaseException { + startTiming("profile_authentication"); IAuthToken authToken = null; @@ -468,12 +472,14 @@ public class CAProcessor extends Processor { String auditMessage = null; try { if (isRenewal) { - authToken = authenticate(authenticator, request, origReq, context); + CMS.debug("CAProcessor: authenticating for renewal"); + authToken = authenticate(authenticator, request, origReq, context, credentials); } else { - authToken = authenticate(authenticator, request); + CMS.debug("CAProcessor: authenticating for enrollment"); + authToken = authenticate(authenticator, request, credentials); } } catch (EBaseException e) { - CMS.debug("CertProcessor: authentication error " + e.toString()); + CMS.debug("CAProcessor: authentication error " + e); authSubjectID += " : " + uid_cred; auditMessage = CMS.getLogMessage( diff --git a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java index 3f8d4c4791ed3fa49b1e0f3af68b62eba207de0c..c6ceb513c0c593da2b613c2aac5cd2f20886fd98 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java +++ b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.cms.servlet.profile; +import java.math.BigInteger; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; @@ -26,9 +27,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509CertInfo; - +import org.apache.commons.lang.StringUtils; import org.w3c.dom.Node; import com.netscape.certsrv.apps.CMS; @@ -36,21 +35,32 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.dbs.certdb.ICertRecord; +import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.profile.EProfileException; import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; +import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileOutput; +import com.netscape.certsrv.profile.IProfileSubsystem; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.template.ArgList; import com.netscape.certsrv.template.ArgSet; +import com.netscape.cms.servlet.cert.CertEnrollmentRequestFactory; import com.netscape.cms.servlet.cert.EnrollmentProcessor; import com.netscape.cms.servlet.cert.RenewalProcessor; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.common.CMSRequest; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cmsutil.util.Cert; import com.netscape.cmsutil.xml.XMLObject; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509CertInfo; + /** * This servlet submits end-user request into the profile framework. * @@ -114,12 +124,10 @@ public class ProfileSubmitServlet extends ProfileServlet { try { if ((renewal != null) && (renewal.equalsIgnoreCase("true"))) { CMS.debug("ProfileSubmitServlet: isRenewal true"); - RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); - results = processor.processRenewal(cmsReq); + results = processRenewal(cmsReq, locale); } else { CMS.debug("ProfileSubmitServlet: isRenewal false"); - EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(cmsReq); + results = processEnrollment(cmsReq, locale); } } catch (BadRequestDataException e) { CMS.debug("ProfileSubmitServlet: bad data provided in processing request: " + e.toString()); @@ -199,6 +207,162 @@ public class ProfileSubmitServlet extends ProfileServlet { } } + public HashMap processEnrollment(CMSRequest cmsReq, Locale locale) throws EBaseException { + + IProfileSubsystem ps = (IProfileSubsystem) CMS.getSubsystem(IProfileSubsystem.ID); + + EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); + HttpServletRequest req = cmsReq.getHttpReq(); + + // if we did not configure profileId in xml file, + // then accept the user-provided one + String profileId = processor.getProfileID() == null ? req.getParameter("profileId") : processor.getProfileID(); + IProfile profile = ps.getProfile(profileId); + + if (profile == null) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not found"); + throw new BadRequestDataException("Profile " + profileId + " not found"); + } + + if (!ps.isProfileEnable(profileId)) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not enabled"); + throw new BadRequestDataException("Profile " + profileId + " not enabled"); + } + + CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); + + IProfileAuthenticator authenticator = profile.getAuthenticator(); + AuthCredentials credentials = new AuthCredentials(); + + if (authenticator != null) { + CMS.debug("ProfileSubmitServlet: getting credentials from request parameters"); + + Enumeration names = authenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = req.getParameter(name); + + CMS.debug("ProfileSubmitServlet: - " + name + ": " + value); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + return processor.processEnrollment(profile, data, req, credentials); + } + + public HashMap processRenewal(CMSRequest cmsReq, Locale locale) throws EBaseException { + + IProfileSubsystem ps = (IProfileSubsystem) CMS.getSubsystem(IProfileSubsystem.ID); + + RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); + HttpServletRequest req = cmsReq.getHttpReq(); + + String profileId = processor.getProfileID() == null ? req.getParameter("profileId") : processor.getProfileID(); + + IProfile renewProfile = ps.getProfile(profileId); + if (renewProfile == null) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not found"); + throw new BadRequestDataException("Profile " + profileId + " not found"); + } + + if (!ps.isProfileEnable(profileId)) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not enabled"); + throw new BadRequestDataException("Profile " + profileId + " not enabled"); + } + + String serial = req.getParameter("serial_num"); + BigInteger certSerial = null; + + if (StringUtils.isNotEmpty(serial)) { + // if serial number is sent with request, then the authentication + // method is not ssl client auth. In this case, an alternative + // authentication method is used (default: ldap based) + // usr_origreq evaluator should be used to authorize ownership + // of the cert + CMS.debug("ProfileSubmitServlet: renewal: serial number: " + serial); + certSerial = new BigInteger(serial); + + } else { + // ssl client auth is to be used + // this is not authentication. Just use the cert to search + // for orig request and find the right profile + CMS.debug("ProfileSubmitServlet: renewal: serial_num not found, must do ssl client auth"); + certSerial = processor.getSerialNumberFromCert(req); + + if (certSerial == null) { + CMS.debug(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_GW_MISSING_CERTS_RENEW_FROM_AUTHMGR")); + } + } + + ICertificateAuthority authority = (ICertificateAuthority) CMS.getSubsystem("ca"); + ICertificateRepository certdb = authority.getCertificateRepository(); + + CMS.debug("ProfileSubmitServlet: serial number of cert to renew: " + certSerial); + ICertRecord record = certdb.readCertificateRecord(certSerial); + if (record == null) { + CMS.debug("ProfileSubmitServlet: cert record not found for serial number " + certSerial); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); + } + + IRequest origReq = processor.getOriginalRequest(certSerial, record); + if (origReq == null) { + CMS.debug("ProfileSubmitServlet: original request not found"); + throw new EBaseException(CMS.getUserMessage(locale, "CMS_INTERNAL_ERROR")); + } + + String origProfileId = origReq.getExtDataInString("profileId"); + IProfile origProfile = ps.getProfile(origProfileId); + + AuthCredentials credentials = new AuthCredentials(); + + // get credentials from request parameters + IProfileAuthenticator authenticator = renewProfile.getAuthenticator(); + if (authenticator != null) { + CMS.debug("ProfileSubmitServlet: authenticator " + authenticator.getName() + " found"); + + Enumeration names = authenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = req.getParameter(name); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + // for renewal, this will override or add auth info to the profile context + IProfileAuthenticator origAuthenticator = origProfile.getAuthenticator(); + if (origAuthenticator != null) { + CMS.debug("ProfileSubmitServlet: for renewal, original authenticator " + + origAuthenticator.getName() + " found"); + + Enumeration names = origAuthenticator.getValueNames(); + if (names != null) { + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = req.getParameter(name); + if (value == null) continue; + + credentials.set(name, value); + } + } + } + + CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, renewProfile, locale); + + //only used in renewal + data.setSerialNum(serial); + + return processor.processRenewal(renewProfile, origProfile, record, data, req, credentials); + } + private void setOutputIntoArgs(IProfile profile, ArgList outputlist, Locale locale, IRequest req) { Enumeration outputIds = profile.getProfileOutputIds(); diff --git a/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java index 137edb5c5a75916fb8a2b2fdf07ab0a6aa56f0fe..8e2c59c26a6b142c8d600c28e3facd6eef4e1913 100644 --- a/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java +++ b/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java @@ -195,6 +195,8 @@ public class AuthSubsystem implements IAuthSubsystem { while (instances.hasMoreElements()) { String insName = instances.nextElement(); + CMS.debug("AuthSubsystem: initializing authentication manager " + insName); + String implName = c.getString(insName + "." + PROP_PLUGIN); AuthMgrPlugin plugin = mAuthMgrPlugins.get(implName); @@ -233,6 +235,7 @@ public class AuthSubsystem implements IAuthSubsystem { throw new EAuthException(CMS.getUserMessage("CMS_ACL_CLASS_LOAD_FAIL", className), e); } catch (EBaseException e) { + CMS.debug(e); log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AUTH_INIT_ERROR", insName, e.toString())); // Skip the authenticaiton instance if // it is mis-configurated. This give @@ -240,6 +243,7 @@ public class AuthSubsystem implements IAuthSubsystem { // fix the problem via console } catch (Throwable e) { + CMS.debug(e); log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AUTH_INIT_ERROR", insName, e.toString())); // Skip the authenticaiton instance if // it is mis-configurated. This give -- 2.4.3 From jmagne at redhat.com Tue Sep 22 18:12:49 2015 From: jmagne at redhat.com (John Magne) Date: Tue, 22 Sep 2015 14:12:49 -0400 (EDT) Subject: [Pki-devel] [pki-devel][PATCH] 0052-KRA-key-archival-recovery-via-cli-should-honor-encry.patch In-Reply-To: <2073336428.55775954.1442945520791.JavaMail.zimbra@redhat.com> Message-ID: <1352286189.55776144.1442945569906.JavaMail.zimbra@redhat.com> [PATCH] KRA: key archival/recovery via cli - should honor encryption/decryption flags. Ticket # 1597 Currently, KRA allows sites to opt for doing encryption/decryption instead of wrapping/unwrapping for key archival and recovery. The new cli code was later added without such support. We should honor the same flags when cli is called to do key archival and recovery. This feature was due to a specific customer request. Here is what is now supported: 1. When the pki cli tool is used to recover a asymmetric private key, support is there to do so with encrypt / decrypt. 2. The passphrase and generic data facility already uses encrypt / decrypt so nothing here was needed. Calling it out since this will possibly be a customer issue. 3. While under the hood, it made sense to add this functionality to the Symmetric key archival and recovery operations. 4. All tests in DRMTest.java worked successfully when the kra was configured to support this feature and configured to not observe this feature. What is missing: We have since added a method to do a server side key generation of a asymmetric key pair in the kra and also archive it there at the same time. In order to do encrypt / decrypt in this case we need to extract the key contents out of a key object that is used to generate this key. It proved problematic to extract said key. This should be ok since the customer only needs to recover an asymmetric key in their test cases. We could look into doing this later if a pressing need arises. -------------- next part -------------- A non-text attachment was scrubbed... Name: 0052-KRA-key-archival-recovery-via-cli-should-honor-encry.patch Type: text/x-patch Size: 18243 bytes Desc: not available URL: From ftweedal at redhat.com Wed Sep 23 10:24:08 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Wed, 23 Sep 2015 20:24:08 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <55FC61DF.70906@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> Message-ID: <20150923102408.GB16937@dhcp-40-8.bne.redhat.com> Updated patches attached. I have addressed all items from the most recent review** and from my IRC conversations with Ade on Wednesday. ** not all items from earlier reviews addressed yet The subject and issuer DNs are now stored in LDAP but I have not yet refactored the lightweight CA loading code to read them instead of reading the DN from the cert in in the NSSDB. This will be fixed of necessity when implementing replication support. Also note that patches 0048..0050 which appeared earlier have been squashed down into 0026 and 0044. Thanks, Fraser -------------- next part -------------- From 83e27ac6cd04120d5dd8012327d8166cd1882647 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Lightweight CAs: initial support This commit adds initial support for "lightweight CAs" - CAs that inhabit an existing CA instance and share the request queue and certificate database of the "top-level CA". We initially support only sub-CAs under the top-level CA - either direct sub-CAs or nested. The general design will support hosting unrelated CAs but creation or import of unrelated CAs is not yet implemented. Part of: https://fedorahosted.org/pki/ticket/1213 --- base/ca/shared/conf/db.ldif | 5 + base/ca/src/com/netscape/ca/CAService.java | 53 +- .../src/com/netscape/ca/CertificateAuthority.java | 640 +++++++++++++++++++-- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 287 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../server/ca/rest/CertRequestService.java | 5 + .../netscape/certsrv/authority/AuthorityData.java | 118 ++++ .../certsrv/authority/AuthorityResource.java | 89 +++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 38 ++ .../netscape/certsrv/ca/CADisabledException.java | 13 + .../netscape/certsrv/ca/CANotFoundException.java | 12 + .../com/netscape/certsrv/ca/CATypeException.java | 14 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 63 ++ .../certsrv/ca/IssuerUnavailableException.java | 13 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 19 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../com/netscape/cms/servlet/base/PKIService.java | 5 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 2 +- .../cms/servlet/cert/EnrollmentProcessor.java | 15 +- .../cms/servlet/cert/RequestProcessor.java | 36 +- .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-authority.ldif | 8 + base/server/share/conf/schema.ldif | 13 + 29 files changed, 1440 insertions(+), 113 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CADisabledException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CATypeException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-authority.ldif diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=authorities,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: authorities diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..90aeba6e8e695e0dee9cbbfad69ccd2349009224 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -25,15 +25,20 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; @@ -53,6 +58,15 @@ import netscape.security.x509.X509CertInfo; import netscape.security.x509.X509ExtensionException; import netscape.security.x509.X509Key; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPModificationSet; +import netscape.ldap.LDAPSearchResults; + import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +74,9 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +90,22 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.IDBSSession; import com.netscape.certsrv.dbs.certdb.ICertRecord; import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.dbs.crldb.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +120,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +132,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +150,9 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import org.apache.commons.lang.StringUtils; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +166,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap(); + protected CertificateAuthority topCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected boolean authorityEnabled = true; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +271,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + topCA = this; + } + + /** + * Construct and initialise a sub-CA + */ + private CertificateAuthority( + CertificateAuthority topCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription, + boolean authorityEnabled + ) throws EBaseException { + setId(topCA.getId()); + this.topCA = topCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + this.authorityEnabled = authorityEnabled; + mNickname = signingKeyNickname; + init(topCA.mOwner, topCA.mConfig); + } + + private boolean isTopCA() { + return topCA == this; + } + + private void ensureEnabled() throws CADisabledException { + if (!authorityEnabled) + throw new CADisabledException("Authority is disabled"); + } + + public boolean getAuthorityEnabled() { + return authorityEnabled; } /** @@ -334,8 +406,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isTopCA()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = topCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +444,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The top-level CA owns these resources so skip these + * steps for sub-CAs. + */ + if (isTopCA()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +484,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isTopCA()) + loadLightweightCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +495,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isTopCA()) { + mByName = topCA.mByName; + mCRLPublisher = topCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +646,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isTopCA()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +658,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + if (!isTopCA()) return; // sub-CAs don't own these resources + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -967,6 +1072,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CRLImpl sign(X509CRLImpl crl, String algname) throws EBaseException { + ensureEnabled(); X509CRLImpl signedcrl = null; IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); @@ -1039,6 +1145,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CertImpl sign(X509CertInfo certInfo, String algname) throws EBaseException { + ensureEnabled(); X509CertImpl signedcert = null; @@ -1123,6 +1230,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public byte[] sign(byte[] data, String algname) throws EBaseException { + ensureEnabled(); return mSigningUnit.sign(data, algname); } @@ -1228,13 +1336,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isTopCA() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1412,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1551,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isTopCA()) { + mCertRepot = topCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1584,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isTopCA()) { + mCRLRepot = topCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1604,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1627,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isTopCA()) { + mPublisherProcessor = topCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1795,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isTopCA()) { + mPolicy = topCA.mPolicy; + mService = topCA.mService; + mNotify = topCA.mNotify; + mPNotify = topCA.mPNotify; + mRequestQueue = topCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1862,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isTopCA()) { + mCRLIssuePoints = topCA.mCRLIssuePoints; + mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1932,110 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register sub-CAs. + * + * This method must only be called by the top-level CA. + */ + private synchronized void loadLightweightCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + boolean foundTopCA = false; + boolean haveLightweightCAsContainer = true; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); + + if (aidAttr == null || nickAttr == null || dnAttr == null) + throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN()); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + + X500Name dn = null; + try { + dn = new X500Name((String) dnAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityDN: " + entry.getDN()); + } + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + if (dn.equals(mName)) { + foundTopCA = true; + this.authorityID = aid; + this.authorityDescription = desc; + caMap.put(aid, this); + continue; + } + + X500Name parentDN = null; + if (parentDNAttr != null) { + try { + dn = new X500Name((String) parentDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); + } + } + + String keyNick = (String) nickAttr.getStringValues().nextElement(); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + boolean enabled = true; + LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled"); + if (enabledAttr != null) { + String enabledString = (String) + enabledAttr.getStringValues().nextElement(); + enabled = enabledString.equalsIgnoreCase("TRUE"); + } + + CertificateAuthority subCA = new CertificateAuthority( + this, aid, parentAID, keyNick, desc, enabled); + caMap.put(aid, subCA); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + haveLightweightCAsContainer = false; + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + if (haveLightweightCAsContainer && !foundTopCA) { + CMS.debug("loadLightweightCAs: no entry for top-level CA"); + CMS.debug("loadLightweightCAs: adding entry for top-level CA"); + AuthorityID aid = addTopCAEntry(); + this.authorityID = aid; + caMap.put(aid, this); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -1960,6 +2197,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } private BasicOCSPResponse sign(ResponseData rd) throws EBaseException { + ensureEnabled(); try (DerOutputStream out = new DerOutputStream()) { DerOutputStream tmp = new DerOutputStream(); @@ -2083,4 +2321,294 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all sub-CAs. + */ + public synchronized List getCAs() { + List cas = new ArrayList(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get the sub-CA by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the top-level CA. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? topCA : caMap.get(aid); + } + + public ICertificateAuthority getCA(X500Name dn) { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getX500Name().equals(dn)) + return ca; + } + return null; + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new sub-CA. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA, or null to create + * sub-CA immediately below top-level CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority subCA = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(subCA.getAuthorityID(), subCA); + } + return subCA; + } + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check uniqueness of DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + for (ICertificateAuthority ca : caMap.values()) { + if (ca.getX500Name().equals(subjectX500Name)) + throw new IssuerUnavailableException( + "CA with Subject DN '" + subjectDN + "' already exists"); + } + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = topCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + String parentDNString = null; + try { + parentDNString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", subjectDN), + new LDAPAttribute("authorityParentDN", parentDNString) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate sub-CA signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating sub-CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + topCA, aid, this.authorityID, nickname, description, true); + } + + private AuthorityID addTopCAEntry() throws EBaseException { + if (!isTopCA()) + throw new EBaseException("Can only invoke from top-level CA"); + + // generate authority ID + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + String dnString = null; + try { + dnString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", getNickname()), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", dnString), + new LDAPAttribute("description", "Top-level (host) CA") + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addTopCAEntry"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + conn.add(ldapEntry); + } catch (LDAPException e) { + throw new ELdapException("Error adding top-level CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return aid; + } + + /** + * Update lightweight authority attributes. + * + * Pass null values to exclude an attribute from the update. + * + * If a passed value matches the current value, it is excluded + * from the update. + * + * To remove optional string values, pass the empty string. + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException { + if (isTopCA() && enabled != null && !enabled) + throw new CATypeException("Cannot disable the top-level CA"); + + LDAPModificationSet mods = new LDAPModificationSet(); + + boolean nextEnabled = authorityEnabled; + if (enabled != null && enabled.booleanValue() != authorityEnabled) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE")); + nextEnabled = enabled; + } + + String nextDesc = authorityDescription; + if (desc != null) { + if (!desc.isEmpty() && authorityDescription != null + && !desc.equals(authorityDescription)) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } else if (desc.isEmpty() && authorityDescription != null) { + mods.add( + LDAPModification.DELETE, + new LDAPAttribute("description", authorityDescription)); + nextDesc = null; + } else if (!desc.isEmpty() && authorityDescription == null) { + mods.add( + LDAPModification.ADD, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } + } + + if (mods.size() > 0) { + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + // update was successful; update CA's state + authorityEnabled = nextEnabled; + authorityDescription = nextDesc; + } + } + } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..e266ca98a1b8bdac7694ce7fc82864023303670c --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,287 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.jboss.resteasy.plugins.providers.atom.Link; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.ForbiddenException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.certsrv.common.NameValuePairs; +import com.netscape.certsrv.common.OpDef; +import com.netscape.certsrv.common.ScopeDef; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.cms.realm.PKIPrincipal; +import com.netscape.cms.servlet.base.PKIService; +import com.netscape.cmsutil.util.Utils; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority topCA; + + public AuthorityService() { + topCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList(); + for (ICertificateAuthority ca : topCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity entity = new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response getCert(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + return Response.ok(ca.getCaX509Cert().getEncoded()).build(); + } catch (CertificateEncodingException e) { + // this really is a 500 Internal Server Error + throw new PKIException("Error encoding certificate: " + e); + } + } + + @Override + public Response getCertPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("CERTIFICATE", der)).build(); + } + + @Override + public Response getChain(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ca.getCACertChain().encode(out); + } catch (IOException e) { + throw new PKIException("Error encoding certificate chain: " + e); + } + + return Response.ok(out.toByteArray()).build(); + } + + @Override + public Response getChainPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("PKCS7", der)).build(); + } + + @Override + public Response createCA(AuthorityData data) { + String parentAIDString = data.getParentID(); + AuthorityID parentAID = null; + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad Authority ID: " + parentAIDString); + } + + try { + ICertificateAuthority subCA = topCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating CA: " + e.toString()); + } + } + + @Override + public Response modifyCA(String aidString, AuthorityData data) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.modifyAuthority(data.getEnabled(), data.getDescription()); + return createOKResponse(readAuthorityData(ca)); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + + @Override + public Response enableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, true, null)); + } + + @Override + public Response disableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, false, null)); + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityEnabled(), + ca.getAuthorityDescription() + ); + } + + private String toPem(String name, byte[] data) { + return "-----BEGIN " + name + "-----\n" + + Utils.base64encode(data) + + "-----END " + name + "-----\n"; + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..1da1ce1713541e52164e9e8fbcbf39ca2332540d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -38,9 +38,11 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -210,6 +212,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("changeRequestState: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("changeRequestState: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (EPropertyException e) { CMS.debug("changeRequestState: execution error " + e); throw new PKIException(CMS.getUserMessage(getLocale(headers), diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..13c0cecab03816eee8f77409902a0ee17ec50f80 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,118 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.jboss.resteasy.plugins.providers.atom.Link; + + at XmlRootElement(name = "ca") + at XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @XmlAttribute + protected String aid; + + public String getID() { + return aid; + } + + + @XmlAttribute + protected String parentAID; + + public String getParentID() { + return parentAID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + String dn, String aid, String parentAID, + Boolean enabled, String description) { + this.dn = dn; + this.aid = aid; + this.parentAID = parentAID; + this.enabled = enabled; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..74a5a39070fe325dba38a809b8c02168ab892f01 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,89 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + + at Path("authorities") +public interface AuthorityResource { + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/pkix-cert") + @ClientResponseType(entityType=byte[].class) + public Response getCert(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getCertPEM(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/pkcs7-mime") + @ClientResponseType(entityType=byte[].class) + public Response getChain(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getChainPEM(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + //@ACLMapping("certs") + //@AuthMethodMapping("certs") + public Response createCA(AuthorityData data); + + /** + * Modify a CA (supports partial updates). + * + * AuthorityID, AuthorityParentID and DN are immutable; + * differences in these values are ignored. + * + * Other values, if null, are ignored, otherwise they are + * set to the new value. To remove the description, use an + * empty string. + */ + @PUT + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response modifyCA( + @PathParam("id") String caIDString, + AuthorityData data); + + @POST + @Path("{id}/enable") + @ClientResponseType(entityType=AuthorityData.class) + public Response enableCA(@PathParam("id") String caIDString); + + @POST + @Path("{id}/disable") + @ClientResponseType(entityType=AuthorityData.class) + public Response disableCA(@PathParam("id") String caIDString); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..854b56d79da38bb7895951b096523dd7385a9e1c --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,38 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + if (s == null) + throw new IllegalArgumentException("null AuthorityID string"); + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(Object o) { + if (o instanceof AuthorityID) + return uuid.compareTo(((AuthorityID) o).uuid); + throw new ClassCastException("Invalid comparsion operand for AuthorityID"); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..df5fdf809f716202c38287e670a88e55eb93ce75 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot perform an operation + * because it is disabled. + */ +public class CADisabledException extends ECAException { + + public CADisabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b7574d4243d9941e30aba506b4ad5fd7b8394386 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,12 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CATypeException.java b/base/common/src/com/netscape/certsrv/ca/CATypeException.java new file mode 100644 index 0000000000000000000000000000000000000000..b51925127356737bea8f84271d6fc7ceb845f4b5 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java @@ -0,0 +1,14 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be completed + * because the CA is the wrong type (e.g., an operation that + * only applies to lightweight CAs). + */ +public class CATypeException extends ECAException { + + public CATypeException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..d63f92aa2b2b9040fb14dac8cced4ecc8a5039ec 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,66 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all sub-CA handles. + */ + public List getCAs(); + + /** + * Get the CA ID of this CA. Returns null for the top-level CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the CA ID of this CA's parent CA. Returns null for + * authorities signed by the top-level CA. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public boolean getAuthorityEnabled(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Get the CA by DN. Returns null if CA not found. + */ + public ICertificateAuthority getCA(X500Name dn); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; + + /** + * Update authority configurables. + * + * @param enabled Whether CA is enabled or disabled + * @param desc Description; null or empty removes it + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..541df308d413676da51e5a1bce27232464118920 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (unused for top-level CA) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the public key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..54cfafed22a0654dd993c9c67f247eefac09c1a3 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -26,8 +26,12 @@ import netscape.security.x509.PKIXExtensions; import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; @@ -161,18 +165,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java index 4ebf075cb709e813fb6a919c507e9847455e70b2..fe77fd567922a49641938cde99d533c091398b75 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java +++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java @@ -56,7 +56,10 @@ public class PKIService { public static List MESSAGE_FORMATS = Arrays.asList( MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_FORM_URLENCODED_TYPE + MediaType.APPLICATION_FORM_URLENCODED_TYPE, + MediaType.valueOf("application/pkix-cert"), + MediaType.valueOf("application/pkcs7-mime"), + MediaType.valueOf("application/x-pem-file") ); public final static int MIN_FILTER_LENGTH = 3; diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index c94ee14961ef39681a53f506b24e4ca5ab06a27e..27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -175,7 +175,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request); + results = processor.processEnrollment(data, request, null); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..e5b9a14df99f29da8ad5c4f76c088c98ff766540 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,8 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -98,7 +100,7 @@ public class EnrollmentProcessor extends CertProcessor { } CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq()); + return processEnrollment(data, cmsReq.getHttpReq(), null); } /** @@ -118,8 +120,11 @@ public class EnrollmentProcessor extends CertProcessor { * @param cmsReq the object holding the request and response information * @exception EBaseException an error has occurred */ - public HashMap processEnrollment(CertEnrollmentRequest data, HttpServletRequest request) - throws EBaseException { + public HashMap processEnrollment( + CertEnrollmentRequest data, + HttpServletRequest request, + AuthorityID aid) + throws EBaseException { try { if (CMS.debugOn()) { @@ -146,6 +151,10 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + if (aid != null) + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aid.toString()); + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java index 2826f477e358a5e16657e985d7f13079cdb14a33..ca2d6f6739906aad06289708a05f0983155b72e8 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java @@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EDeferException; @@ -327,6 +331,31 @@ public class RequestProcessor extends CertProcessor { } /** + * Ensure validity of AuthorityID and that CA exists and is enabled. + */ + private void ensureCAEnabled(String aidString) throws EBaseException { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + // this shouldn't happen because request was already accepted + throw new BadRequestDataException("Invalid AuthorityID in request data"); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem("ca"); + if (ca == null) + // this shouldn't happen + throw new CANotFoundException("Could not get top-level CA"); // shouldn't happen + ca = ca.getCA(aid); + if (ca == null) + // this shouldn't happen because request was already accepted + throw new CANotFoundException("Unknown CA: " + aidString); + if (!ca.getAuthorityEnabled()) + // authority was disabled after request was accepted + throw new CADisabledException("CA '" + aidString + "' is disabled"); + } + + /** * Approve request *

* @@ -346,11 +375,16 @@ public class RequestProcessor extends CertProcessor { * occurred */ private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale) - throws EProfileException { + throws EBaseException { String auditMessage = null; String auditSubjectID = auditSubjectID(); String auditRequesterID = auditRequesterID(req); + // ensure target CA is enabled + String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ensureCAEnabled(aidString); + try { profile.execute(req); req.setRequestStatus(RequestStatus.COMPLETE); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif new file mode 100644 index 0000000000000000000000000000000000000000..7d261f18fbc9475983bf93b1cddcc184d7f9d178 --- /dev/null +++ b/base/server/share/conf/schema-authority.ldif @@ -0,0 +1,8 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..a15601ae7a362635bc398b92b9bfda1c72f0dfc8 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,16 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From c16c7051f8c178efe711d0b27ef8c02266b74ed4 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 10 Jun 2015 03:02:35 -0400 Subject: [PATCH] Lightweight CAs: add ca-authority CLI Add CLI commands for creating, listing and showing lightweight CAs. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../certsrv/authority/AuthorityClient.java | 62 +++++++++++++++ .../src/com/netscape/certsrv/ca/CAClient.java | 3 +- .../netscape/cmstools/authority/AuthorityCLI.java | 61 +++++++++++++++ .../cmstools/authority/AuthorityCreateCLI.java | 90 ++++++++++++++++++++++ .../cmstools/authority/AuthorityDisableCLI.java | 59 ++++++++++++++ .../cmstools/authority/AuthorityEnableCLI.java | 59 ++++++++++++++ .../cmstools/authority/AuthorityFindCLI.java | 62 +++++++++++++++ .../cmstools/authority/AuthorityShowCLI.java | 57 ++++++++++++++ .../src/com/netscape/cmstools/cli/CACLI.java | 2 + 9 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..86de3352e2424211125c146edf759481448a2694 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -0,0 +1,62 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- +package com.netscape.certsrv.authority; + +import java.net.URISyntaxException; +import java.util.List; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; + +import com.netscape.certsrv.client.Client; +import com.netscape.certsrv.client.PKIClient; + +/** + * @author Fraser Tweedale + */ +public class AuthorityClient extends Client { + + public AuthorityResource proxy; + + public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException { + super(client, subsystem, "authority"); + proxy = createProxy(AuthorityResource.class); + } + + public List listCAs() { + Response response = proxy.listCAs(); + GenericType> type = new GenericType>() {}; + return client.getEntity(response, type); + } + + public AuthorityData getCA(String caIDString) { + Response response = proxy.getCA(caIDString); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData createCA(AuthorityData data) { + Response response = proxy.createCA(data); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData modifyCA(AuthorityData data) { + Response response = proxy.modifyCA(data.getID(), data); + return client.getEntity(response, AuthorityData.class); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 100644 --- a/base/common/src/com/netscape/certsrv/ca/CAClient.java +++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java @@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient; import com.netscape.certsrv.profile.ProfileClient; import com.netscape.certsrv.selftests.SelfTestClient; import com.netscape.certsrv.user.UserClient; +import com.netscape.certsrv.authority.AuthorityClient; public class CAClient extends SubsystemClient { @@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient { } public void init() throws URISyntaxException { - + addClient(new AuthorityClient(client, name)); addClient(new CertClient(client, name)); addClient(new GroupClient(client, name)); addClient(new ProfileClient(client, name)); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..3aacd86c76061de22f8fff2a1cda7f5b76b97350 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -0,0 +1,61 @@ +package com.netscape.cmstools.authority; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.net.URI; +import java.util.Locale; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityClient; +import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCLI extends CLI { + + public AuthorityClient authorityClient; + + public AuthorityCLI(CLI parent) { + super("authority", "CA management commands", parent); + + addModule(new AuthorityFindCLI(this)); + addModule(new AuthorityShowCLI(this)); + addModule(new AuthorityCreateCLI(this)); + addModule(new AuthorityDisableCLI(this)); + addModule(new AuthorityEnableCLI(this)); + } + + public String getFullName() { + if (parent instanceof MainCLI) { + // do not include MainCLI's name + return name; + } else { + return parent.getFullName() + "-" + name; + } + } + + public void execute(String[] args) throws Exception { + client = parent.getClient(); + authorityClient = new AuthorityClient(client, "ca"); + super.execute(args); + } + + protected static void printAuthorityData(AuthorityData data) { + System.out.println(" Authority DN: " + data.getDN()); + System.out.println(" ID: " + data.getID()); + String parentAID = data.getParentID(); + if (parentAID != null) + System.out.println(" Parent DN: " + data.getParentID()); + System.out.println(" Enabled: " + data.getEnabled()); + String desc = data.getDescription(); + if (desc != null) + System.out.println(" Description: " + desc); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..ec27132079814d47bd2d54383d114c089baae931 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -0,0 +1,90 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCreateCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityCreateCLI(AuthorityCLI authorityCLI) { + super("create", "Create CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option(null, "parent", true, "ID of parent CA"); + optParent.setArgName("id"); + options.addOption(optParent); + + Option optDesc = new Option(null, "desc", true, "Optional description"); + optDesc.setArgName("string"); + options.addOption(optDesc); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No DN specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String parentAIDString = null; + if (cmd.hasOption("parent")) { + parentAIDString = cmd.getOptionValue("parent"); + try { + new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + System.err.println("Bad CA ID: " + parentAIDString); + printHelp(); + System.exit(-1); + } + } else { + System.err.println("Must specify parent authority"); + printHelp(); + System.exit(-1); + } + + String desc = null; + if (cmd.hasOption("desc")) + desc = cmd.getOptionValue("desc"); + + String dn = cmdArgs[0]; + AuthorityData data = new AuthorityData( + dn, null, parentAIDString, true /* enabled */, desc); + AuthorityData newData = authorityCLI.authorityClient.createCA(data); + AuthorityCLI.printAuthorityData(newData); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..56bb28e3431dbebc0ecc3faa4738631dcadf8dbe --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java @@ -0,0 +1,59 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityDisableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityDisableCLI(AuthorityCLI authorityCLI) { + super("disable", "Disable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, false, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..bb67665b73b4fac3d9469ec766161abea054df27 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java @@ -0,0 +1,59 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityEnableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityEnableCLI(AuthorityCLI authorityCLI) { + super("enable", "Enable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, true, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..4a5684671d6a778146de183a0d122aaa58c45d8d --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java @@ -0,0 +1,62 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityFindCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityFindCLI(AuthorityCLI authorityCLI) { + super("find", "Find CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName(), options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + List datas = authorityCLI.authorityClient.listCAs(); + + MainCLI.printMessage(datas.size() + " entries matched"); + if (datas.size() == 0) return; + + boolean first = true; + for (AuthorityData data : datas) { + if (first) + first = false; + else + System.out.println(); + AuthorityCLI.printAuthorityData(data); + } + + MainCLI.printMessage("Number of entries returned " + datas.size()); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..a252f3001ae2581f770e58e68a077eb909a5490b --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java @@ -0,0 +1,57 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityShowCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityShowCLI(AuthorityCLI authorityCLI) { + super("show", "Show CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + String caIDString = cmdArgs[0]; + AuthorityData data = authorityCLI.authorityClient.getCA(caIDString); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java @@ -20,6 +20,7 @@ package com.netscape.cmstools.cli; import com.netscape.certsrv.ca.CAClient; import com.netscape.certsrv.client.Client; +import com.netscape.cmstools.authority.AuthorityCLI; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.group.GroupCLI; import com.netscape.cmstools.profile.ProfileCLI; @@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI { public CACLI(CLI parent) { super("ca", "CA management commands", parent); + addModule(new AuthorityCLI(this)); addModule(new CertCLI(this)); addModule(new GroupCLI(this)); addModule(new KRAConnectorCLI(this)); -- 2.4.3 -------------- next part -------------- From 73f9c503229ba1a414a48fe17771e146a0d1e0b8 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 1 Sep 2015 09:57:42 -0400 Subject: [PATCH] Lightweight CAs: REST cert request param to specify authority Add the optional "ca" query parameter for REST cert request submission. Also update the ca-cert-request-submit CLI command with an option to provide an AuthorityID. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../src/com/netscape/cms/servlet/test/CATest.java | 4 +- .../server/ca/rest/CertRequestService.java | 41 ++++++++++++++++++-- .../src/com/netscape/certsrv/cert/CertClient.java | 16 +++++++- .../netscape/certsrv/cert/CertRequestResource.java | 5 ++- .../cmstools/cert/CertRequestSubmitCLI.java | 44 +++++++++++++++++++++- .../cmstools/client/ClientCertRequestCLI.java | 2 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 12 ++++-- 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java index 15023cad939abb11927abc64fe5916e04cb65661..5876c57f985caa38ad5895f4368113620370910d 100644 --- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java +++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java @@ -288,7 +288,7 @@ public class CATest { private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); @@ -308,7 +308,7 @@ public class CATest { private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 1da1ce1713541e52164e9e8fbcbf39ca2332540d..7cb4ff71e18b6e29bf55c11dc99bbfb9b83dd60f 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -18,6 +18,7 @@ package org.dogtagpki.server.ca.rest; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Enumeration; @@ -41,8 +42,11 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -63,6 +67,7 @@ import com.netscape.certsrv.request.RequestNotFoundException; import com.netscape.cms.servlet.base.PKIService; import com.netscape.cms.servlet.cert.CertRequestDAO; import com.netscape.cmsutil.ldap.LDAPUtil; +import netscape.security.x509.X500Name; /** * @author alee @@ -115,13 +120,43 @@ public class CertRequestService extends PKIService implements CertRequestResourc } @Override - public Response enrollCert(CertEnrollmentRequest data) { - + public Response enrollCert(CertEnrollmentRequest data, String aidString, String adnString) { if (data == null) { CMS.debug("enrollCert: data is null"); throw new BadRequestException("Unable to create enrollment reequest: Invalid input data"); } + if (aidString != null && adnString != null) + throw new BadRequestException("Cannot provide both issuer-id and issuer-dn"); + + AuthorityID aid = null; + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (aidString != null) { + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("invalid AuthorityID: " + aidString); + } + ca = ca.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + aidString); + } + if (adnString != null) { + X500Name adn = null; + try { + adn = new X500Name(adnString); + } catch (IOException e) { + throw new BadRequestException("invalid DN: " + adnString); + } + ca = ca.getCA(adn); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + adnString); + aid = ca.getAuthorityID(); + } + if (!ca.getAuthorityEnabled()) + throw new ConflictingOperationException("CA not enabled: " + aid.toString()); + data.setRemoteHost(servletRequest.getRemoteHost()); data.setRemoteAddr(servletRequest.getRemoteAddr()); @@ -129,7 +164,7 @@ public class CertRequestService extends PKIService implements CertRequestResourc CertRequestInfos infos; try { - infos = dao.submitRequest(data, servletRequest, uriInfo, getLocale(headers)); + infos = dao.submitRequest(aid, data, servletRequest, uriInfo, getLocale(headers)); } catch (EAuthException e) { CMS.debug("enrollCert: authentication failed: " + e); throw new UnauthorizedException(e.toString()); diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java index 42b04b7021f0063894c340c177915d799b621ddd..1d4ccd2cf7e83a8ed3b33253b1416110d5504125 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertClient.java +++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java @@ -17,16 +17,19 @@ //--- END COPYRIGHT BLOCK --- package com.netscape.certsrv.cert; +import java.io.IOException; import java.net.URISyntaxException; import javax.ws.rs.core.Response; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.client.Client; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.SubsystemClient; import com.netscape.certsrv.dbs.certdb.CertId; import com.netscape.certsrv.profile.ProfileDataInfos; import com.netscape.certsrv.request.RequestId; +import netscape.security.x509.X500Name; /** * @author Endi S. Dewata @@ -85,8 +88,17 @@ public class CertClient extends Client { return client.getEntity(response, CertRequestInfo.class); } - public CertRequestInfos enrollRequest(CertEnrollmentRequest data) { - Response response = certRequestClient.enrollCert(data); + public CertRequestInfos enrollRequest( + CertEnrollmentRequest data, AuthorityID aid, X500Name adn) { + String aidString = aid != null ? aid.toString() : null; + String adnString = null; + if (adn != null) { + try { + adnString = adn.toLdapDNString(); + } catch (IOException e) { + } + } + Response response = certRequestClient.enrollCert(data, aidString, adnString); return client.getEntity(response, CertRequestInfos.class); } diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java index 7f08b4af392e3e56419abdad7cb66bd191688222..493f6f53a5a5e30804532305b199d44a66eecd24 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java +++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java @@ -37,7 +37,10 @@ public interface CertRequestResource { @POST @Path("certrequests") @ClientResponseType(entityType=CertRequestInfos.class) - public Response enrollCert(CertEnrollmentRequest data); + public Response enrollCert( + CertEnrollmentRequest data, + @QueryParam("issuer-id") String caIDString, + @QueryParam("issuer-dn") String caDNString); /** * Used to retrieve cert request info for a specific request diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..e6b59a5d7fcaced100699ea067a8f95b992722f3 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -2,18 +2,22 @@ package com.netscape.cmstools.cert; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Arrays; import java.util.Scanner; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; +import netscape.security.x509.X500Name; public class CertRequestSubmitCLI extends CLI { @@ -22,6 +26,14 @@ public class CertRequestSubmitCLI extends CLI { public CertRequestSubmitCLI(CertCLI certCLI) { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; + + Option optAID = new Option(null, "issuer-id", true, "Authority ID (top-level CA if omitted)"); + optAID.setArgName("id"); + options.addOption(optAID); + + Option optADN = new Option(null, "issuer-dn", true, "Authority DN (top-level CA if omitted)"); + optADN.setArgName("dn"); + options.addOption(optADN); } public void printHelp() { @@ -55,9 +67,39 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } + AuthorityID aid = null; + if (cmd.hasOption("issuer-id")) { + String aidString = cmd.getOptionValue("issuer-id"); + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + System.err.println("Bad AuthorityID: " + aidString); + printHelp(); + System.exit(-1); + } + } + + X500Name adn = null; + if (cmd.hasOption("issuer-dn")) { + String adnString = cmd.getOptionValue("issuer-dn"); + try { + adn = new X500Name(adnString); + } catch (IOException e) { + System.err.println("Bad DN: " + adnString); + printHelp(); + System.exit(-1); + } + } + + if (aid != null && adn != null) { + System.err.println("--issuer-id and --issuer-dn options are mutually exclusive"); + printHelp(); + System.exit(-1); + } + try { CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd); + CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid, adn); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(cri); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..db71c8a0f7db4644290efb766178b76668c22377 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI { System.out.println("Sending certificate request."); } - CertRequestInfos infos = certClient.enrollRequest(request); + CertRequestInfos infos = certClient.enrollRequest(request, null, null); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index 27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b..a2e4b583d318ac8412361850d91233b77a447e13 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -30,6 +30,7 @@ import javax.ws.rs.core.UriInfo; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; @@ -164,8 +165,13 @@ public class CertRequestDAO extends CMSRequestDAO { * @throws EBaseException * @throws ServletException */ - public CertRequestInfos submitRequest(CertEnrollmentRequest data, HttpServletRequest request, UriInfo uriInfo, - Locale locale) throws EBaseException { + public CertRequestInfos submitRequest( + AuthorityID aid, + CertEnrollmentRequest data, + HttpServletRequest request, + UriInfo uriInfo, + Locale locale) + throws EBaseException { CertRequestInfos ret = new CertRequestInfos(); @@ -175,7 +181,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request, null); + results = processor.processEnrollment(data, request, aid); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); -- 2.4.3 From ftweedal at redhat.com Thu Sep 24 14:20:02 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Fri, 25 Sep 2015 00:20:02 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <55FC61DF.70906@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> Message-ID: <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> Latest patches attached. Relative to previous patchset this one: - fixes a compile error in CATest.java - fixes a ton of warnings and some poorly ordered imports - adds ACLs and ACL enforcement for privileged operations on AuthorityResource Here's an ldif snippet for adding the ACLs to an existing database dn: cn=aclResources,o=ipaca changetype: modify add: resourceACLS resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities Cheers, Fraser On Fri, Sep 18, 2015 at 02:11:27PM -0500, Endi Sukma Dewata wrote: > On 9/18/2015 1:46 PM, Ade Lee wrote: > >>6. Assuming authority DN is unique, we can add --issuer option > >>tothese commands: > >>* pki ca-cert-find --issuer > >>* pki ca-cert-request-submit --issuer > >>* pki client-cert-find --issuer > >>* pki client-cert-request --issuer > >> > > > >If we do this, then we need to be sure that the DN is normalized - both > >on input -- ie. when the subca is created (we need to do this in any > >case) and also on processing in the CLI. > > > >I'm ok with offering this as an option (maybe --issuer_dn), but the > >primary (and initially required option) will be using UUID. We can > >defer this mechanism to another ticket/patch. Please open one. > > Per IRC discussion we agreed with these options: > * --issuer-id > * --issuer-dn > to be added to the ca-cert-* and client-cert-request commands. > > For the client-cert-find command we can only provide this option: > * --issuer-dn > since issuer ID is irrelevant on the client. > > Personally I think the issuer DN would be more useful since that's the value > that you see in certificates, so it's more consistent everywhere, and no > need to do a lookup to find the issuer ID. Also, although most likely we > will copy & paste the ID or DN anyway, the DN is easier to read and confirm > that you're submitting the request to the right authority. > > -- > Endi S. Dewata -------------- next part -------------- From f5bf94e2cac23970e5a8b673a0dfbe9100610102 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Lightweight CAs: initial support This commit adds initial support for "lightweight CAs" - CAs that inhabit an existing CA instance and share the request queue and certificate database of the "top-level CA". We initially support only sub-CAs under the top-level CA - either direct sub-CAs or nested. The general design will support hosting unrelated CAs but creation or import of unrelated CAs is not yet implemented. Part of: https://fedorahosted.org/pki/ticket/1213 --- base/ca/shared/conf/acl.ldif | 2 + base/ca/shared/conf/acl.properties | 4 + base/ca/shared/conf/db.ldif | 5 + base/ca/src/com/netscape/ca/CAService.java | 53 +- .../src/com/netscape/ca/CertificateAuthority.java | 664 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 275 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../server/ca/rest/CertRequestService.java | 5 + .../netscape/certsrv/authority/AuthorityData.java | 114 ++++ .../certsrv/authority/AuthorityResource.java | 91 +++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CADisabledException.java | 15 + .../netscape/certsrv/ca/CANotFoundException.java | 14 + .../com/netscape/certsrv/ca/CATypeException.java | 16 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 63 ++ .../certsrv/ca/IssuerUnavailableException.java | 15 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 28 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../com/netscape/cms/servlet/base/PKIService.java | 5 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 2 +- .../cms/servlet/cert/EnrollmentProcessor.java | 15 +- .../cms/servlet/cert/RequestProcessor.java | 36 +- .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-authority.ldif | 8 + base/server/share/conf/schema.ldif | 13 + 31 files changed, 1452 insertions(+), 132 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CADisabledException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CATypeException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-authority.ldif diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 0da10939fc64dc88b32016c308fc13a1bab2d14f..54c9f1d5c64b6578de83f1b7ffdff922a69975f4 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -57,3 +57,5 @@ resourceACLS: certServer.ca.certs:execute:allow (execute) group="Certificate Man resourceACLS: certServer.ca.groups:execute:allow (execute) group="Administrators":Admins may execute group operations resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="Administrators":Only admins can access selftests. resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations +resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities +resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index d14d1832c7cff7db055f2274f1ed30223d16cad8..f0b5b9f650ad2fc4bde531ade94347a7280d3089 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -21,3 +21,7 @@ securityDomain.installToken = certServer.securitydomain.domainxml,read selftests.read = certServer.ca.selftests,read selftests.execute = certServer.ca.selftests,execute users = certServer.ca.users,execute +authorities.create = certServer.ca.authorities,create +authorities.list = certServer.ca.authorities,list +authorities.modify = certServer.ca.authorities,modify +authorities.read = certServer.ca.authorities,read diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=authorities,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: authorities diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..a58788bc5b85ba5c393c4c5a4798e21c24608e21 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -23,36 +23,26 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger; +import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import netscape.security.util.DerOutputStream; -import netscape.security.util.DerValue; -import netscape.security.x509.AlgorithmId; -import netscape.security.x509.CertificateChain; -import netscape.security.x509.CertificateIssuerName; -import netscape.security.x509.CertificateSubjectName; -import netscape.security.x509.CertificateVersion; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CRLImpl; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509CertInfo; -import netscape.security.x509.X509ExtensionException; -import netscape.security.x509.X509Key; - import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +50,9 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +66,21 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; 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.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +95,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +107,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +125,29 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPModificationSet; +import netscape.ldap.LDAPSearchResults; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.CertificateIssuerName; +import netscape.security.x509.CertificateSubjectName; +import netscape.security.x509.CertificateVersion; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CRLImpl; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509CertInfo; +import netscape.security.x509.X509ExtensionException; +import netscape.security.x509.X509Key; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +161,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap<>(); + protected CertificateAuthority topCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected boolean authorityEnabled = true; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + topCA = this; + } + + /** + * Construct and initialise a sub-CA + */ + private CertificateAuthority( + CertificateAuthority topCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription, + boolean authorityEnabled + ) throws EBaseException { + setId(topCA.getId()); + this.topCA = topCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + this.authorityEnabled = authorityEnabled; + mNickname = signingKeyNickname; + init(topCA.mOwner, topCA.mConfig); + } + + private boolean isTopCA() { + return topCA == this; + } + + private void ensureEnabled() throws CADisabledException { + if (!authorityEnabled) + throw new CADisabledException("Authority is disabled"); + } + + public boolean getAuthorityEnabled() { + return authorityEnabled; } /** @@ -334,8 +401,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isTopCA()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = topCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +439,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The top-level CA owns these resources so skip these + * steps for sub-CAs. + */ + if (isTopCA()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +479,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isTopCA()) + loadLightweightCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +490,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isTopCA()) { + mByName = topCA.mByName; + mCRLPublisher = topCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +641,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isTopCA()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +653,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + if (!isTopCA()) return; // sub-CAs don't own these resources + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -967,6 +1067,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CRLImpl sign(X509CRLImpl crl, String algname) throws EBaseException { + ensureEnabled(); X509CRLImpl signedcrl = null; IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); @@ -1039,6 +1140,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CertImpl sign(X509CertInfo certInfo, String algname) throws EBaseException { + ensureEnabled(); X509CertImpl signedcert = null; @@ -1123,6 +1225,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public byte[] sign(byte[] data, String algname) throws EBaseException { + ensureEnabled(); return mSigningUnit.sign(data, algname); } @@ -1228,13 +1331,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isTopCA() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1407,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1546,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isTopCA()) { + mCertRepot = topCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1579,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isTopCA()) { + mCRLRepot = topCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1599,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1622,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isTopCA()) { + mPublisherProcessor = topCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1790,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isTopCA()) { + mPolicy = topCA.mPolicy; + mService = topCA.mService; + mNotify = topCA.mNotify; + mPNotify = topCA.mPNotify; + mRequestQueue = topCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1857,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isTopCA()) { + mCRLIssuePoints = topCA.mCRLIssuePoints; + mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1927,111 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register sub-CAs. + * + * This method must only be called by the top-level CA. + */ + private synchronized void loadLightweightCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + boolean foundTopCA = false; + boolean haveLightweightCAsContainer = true; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); + + if (aidAttr == null || nickAttr == null || dnAttr == null) + throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN()); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + + X500Name dn = null; + try { + dn = new X500Name((String) dnAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityDN: " + entry.getDN()); + } + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + if (dn.equals(mName)) { + foundTopCA = true; + this.authorityID = aid; + this.authorityDescription = desc; + caMap.put(aid, this); + continue; + } + + @SuppressWarnings("unused") + X500Name parentDN = null; + if (parentDNAttr != null) { + try { + parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); + } + } + + String keyNick = (String) nickAttr.getStringValues().nextElement(); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + boolean enabled = true; + LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled"); + if (enabledAttr != null) { + String enabledString = (String) + enabledAttr.getStringValues().nextElement(); + enabled = enabledString.equalsIgnoreCase("TRUE"); + } + + CertificateAuthority subCA = new CertificateAuthority( + this, aid, parentAID, keyNick, desc, enabled); + caMap.put(aid, subCA); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + haveLightweightCAsContainer = false; + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + if (haveLightweightCAsContainer && !foundTopCA) { + CMS.debug("loadLightweightCAs: no entry for top-level CA"); + CMS.debug("loadLightweightCAs: adding entry for top-level CA"); + AuthorityID aid = addTopCAEntry(); + this.authorityID = aid; + caMap.put(aid, this); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -1960,6 +2193,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } private BasicOCSPResponse sign(ResponseData rd) throws EBaseException { + ensureEnabled(); try (DerOutputStream out = new DerOutputStream()) { DerOutputStream tmp = new DerOutputStream(); @@ -2083,4 +2317,294 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all sub-CAs. + */ + public synchronized List getCAs() { + List cas = new ArrayList<>(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get the sub-CA by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the top-level CA. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? topCA : caMap.get(aid); + } + + public ICertificateAuthority getCA(X500Name dn) { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getX500Name().equals(dn)) + return ca; + } + return null; + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new sub-CA. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA, or null to create + * sub-CA immediately below top-level CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority subCA = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(subCA.getAuthorityID(), subCA); + } + return subCA; + } + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check uniqueness of DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + for (ICertificateAuthority ca : caMap.values()) { + if (ca.getX500Name().equals(subjectX500Name)) + throw new IssuerUnavailableException( + "CA with Subject DN '" + subjectDN + "' already exists"); + } + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = topCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + String parentDNString = null; + try { + parentDNString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", subjectDN), + new LDAPAttribute("authorityParentDN", parentDNString) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate sub-CA signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating sub-CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + topCA, aid, this.authorityID, nickname, description, true); + } + + private AuthorityID addTopCAEntry() throws EBaseException { + if (!isTopCA()) + throw new EBaseException("Can only invoke from top-level CA"); + + // generate authority ID + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + String dnString = null; + try { + dnString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", getNickname()), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", dnString), + new LDAPAttribute("description", "Top-level (host) CA") + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addTopCAEntry"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + conn.add(ldapEntry); + } catch (LDAPException e) { + throw new ELdapException("Error adding top-level CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return aid; + } + + /** + * Update lightweight authority attributes. + * + * Pass null values to exclude an attribute from the update. + * + * If a passed value matches the current value, it is excluded + * from the update. + * + * To remove optional string values, pass the empty string. + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException { + if (isTopCA() && enabled != null && !enabled) + throw new CATypeException("Cannot disable the top-level CA"); + + LDAPModificationSet mods = new LDAPModificationSet(); + + boolean nextEnabled = authorityEnabled; + if (enabled != null && enabled.booleanValue() != authorityEnabled) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE")); + nextEnabled = enabled; + } + + String nextDesc = authorityDescription; + if (desc != null) { + if (!desc.isEmpty() && authorityDescription != null + && !desc.equals(authorityDescription)) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } else if (desc.isEmpty() && authorityDescription != null) { + mods.add( + LDAPModification.DELETE, + new LDAPAttribute("description", authorityDescription)); + nextDesc = null; + } else if (!desc.isEmpty() && authorityDescription == null) { + mods.add( + LDAPModification.ADD, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } + } + + if (mods.size() > 0) { + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + // update was successful; update CA's state + authorityEnabled = nextEnabled; + authorityDescription = nextDesc; + } + } + } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..2ada9139f469903a61a0b10c2d4b43f9e07f8c77 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,275 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.ForbiddenException; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.cms.servlet.base.PKIService; +import com.netscape.cmsutil.util.Utils; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority topCA; + + public AuthorityService() { + topCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList<>(); + for (ICertificateAuthority ca : topCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity> entity = + new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response getCert(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + return Response.ok(ca.getCaX509Cert().getEncoded()).build(); + } catch (CertificateEncodingException e) { + // this really is a 500 Internal Server Error + throw new PKIException("Error encoding certificate: " + e); + } + } + + @Override + public Response getCertPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("CERTIFICATE", der)).build(); + } + + @Override + public Response getChain(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ca.getCACertChain().encode(out); + } catch (IOException e) { + throw new PKIException("Error encoding certificate chain: " + e); + } + + return Response.ok(out.toByteArray()).build(); + } + + @Override + public Response getChainPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("PKCS7", der)).build(); + } + + @Override + public Response createCA(AuthorityData data) { + String parentAIDString = data.getParentID(); + AuthorityID parentAID = null; + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad Authority ID: " + parentAIDString); + } + + try { + ICertificateAuthority subCA = topCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating CA: " + e.toString()); + } + } + + @Override + public Response modifyCA(String aidString, AuthorityData data) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.modifyAuthority(data.getEnabled(), data.getDescription()); + return createOKResponse(readAuthorityData(ca)); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + + @Override + public Response enableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, true, null)); + } + + @Override + public Response disableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, false, null)); + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityEnabled(), + ca.getAuthorityDescription() + ); + } + + private String toPem(String name, byte[] data) { + return "-----BEGIN " + name + "-----\n" + + Utils.base64encode(data) + + "-----END " + name + "-----\n"; + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..1da1ce1713541e52164e9e8fbcbf39ca2332540d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -38,9 +38,11 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -210,6 +212,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("changeRequestState: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("changeRequestState: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (EPropertyException e) { CMS.debug("changeRequestState: execution error " + e); throw new PKIException(CMS.getUserMessage(getLocale(headers), diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..b2479f2977657108faa23dc7c3a1f7b0fa33e104 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,114 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import org.jboss.resteasy.plugins.providers.atom.Link; + + at XmlRootElement(name = "ca") + at XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @XmlAttribute + protected String aid; + + public String getID() { + return aid; + } + + + @XmlAttribute + protected String parentAID; + + public String getParentID() { + return parentAID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + String dn, String aid, String parentAID, + Boolean enabled, String description) { + this.dn = dn; + this.aid = aid; + this.parentAID = parentAID; + this.enabled = enabled; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..e1fb2a7b7f46601aa89143579badbfc6f6c8d555 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,91 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + + at Path("authorities") + at AuthMethodMapping("authorities") +public interface AuthorityResource { + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/pkix-cert") + @ClientResponseType(entityType=byte[].class) + public Response getCert(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getCertPEM(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/pkcs7-mime") + @ClientResponseType(entityType=byte[].class) + public Response getChain(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getChainPEM(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + @ACLMapping("authorities.create") + public Response createCA(AuthorityData data); + + /** + * Modify a CA (supports partial updates). + * + * AuthorityID, AuthorityParentID and DN are immutable; + * differences in these values are ignored. + * + * Other values, if null, are ignored, otherwise they are + * set to the new value. To remove the description, use an + * empty string. + */ + @PUT + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + @ACLMapping("authorities.modify") + public Response modifyCA( + @PathParam("id") String caIDString, + AuthorityData data); + + @POST + @Path("{id}/enable") + @ClientResponseType(entityType=AuthorityData.class) + @ACLMapping("authorities.modify") + public Response enableCA(@PathParam("id") String caIDString); + + @POST + @Path("{id}/disable") + @ClientResponseType(entityType=AuthorityData.class) + @ACLMapping("authorities.modify") + public Response disableCA(@PathParam("id") String caIDString); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..daac587b75843f5faf75e556d4d0135c8ffc8fd7 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + if (s == null) + throw new IllegalArgumentException("null AuthorityID string"); + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(AuthorityID aid) { + return uuid.compareTo(aid.uuid); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..9b3f16b90a8d28b87e993575037a2a19517e17b9 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot perform an operation + * because it is disabled. + */ +public class CADisabledException extends ECAException { + + private static final long serialVersionUID = -8827509070155037699L; + + public CADisabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..f292077ece3089d398ad0cf3a008b7e5218a6bcd --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,14 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + private static final long serialVersionUID = -4618887355685066120L; + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CATypeException.java b/base/common/src/com/netscape/certsrv/ca/CATypeException.java new file mode 100644 index 0000000000000000000000000000000000000000..19eb680e88aee3baac51abd95439c46487bf5720 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java @@ -0,0 +1,16 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be completed + * because the CA is the wrong type (e.g., an operation that + * only applies to lightweight CAs). + */ +public class CATypeException extends ECAException { + + private static final long serialVersionUID = -6004456461295692150L; + + public CATypeException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..d63f92aa2b2b9040fb14dac8cced4ecc8a5039ec 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,66 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all sub-CA handles. + */ + public List getCAs(); + + /** + * Get the CA ID of this CA. Returns null for the top-level CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the CA ID of this CA's parent CA. Returns null for + * authorities signed by the top-level CA. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public boolean getAuthorityEnabled(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Get the CA by DN. Returns null if CA not found. + */ + public ICertificateAuthority getCA(X500Name dn); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; + + /** + * Update authority configurables. + * + * @param enabled Whether CA is enabled or disabled + * @param desc Description; null or empty removes it + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..75bf88251bfadb687c7bf85944a806339f6f3b9f --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + private static final long serialVersionUID = -6247493607604418446L; + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (unused for top-level CA) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the public key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..bd71a4ef8cf710008fc861a022a553d5064c37ba 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -20,20 +20,23 @@ package com.netscape.cms.profile.def; import java.io.IOException; import java.util.Locale; -import netscape.security.x509.AuthorityKeyIdentifierExtension; -import netscape.security.x509.KeyIdentifier; -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.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; +import netscape.security.x509.AuthorityKeyIdentifierExtension; +import netscape.security.x509.KeyIdentifier; +import netscape.security.x509.PKIXExtensions; +import netscape.security.x509.X509CertInfo; + /** * This class implements an enrollment default policy * that populates Authority Key Identifier extension @@ -161,18 +164,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java index 4ebf075cb709e813fb6a919c507e9847455e70b2..fe77fd567922a49641938cde99d533c091398b75 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java +++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java @@ -56,7 +56,10 @@ public class PKIService { public static List MESSAGE_FORMATS = Arrays.asList( MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_FORM_URLENCODED_TYPE + MediaType.APPLICATION_FORM_URLENCODED_TYPE, + MediaType.valueOf("application/pkix-cert"), + MediaType.valueOf("application/pkcs7-mime"), + MediaType.valueOf("application/x-pem-file") ); public final static int MIN_FILTER_LENGTH = 3; diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index c94ee14961ef39681a53f506b24e4ca5ab06a27e..27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -175,7 +175,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request); + results = processor.processEnrollment(data, request, null); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..e5b9a14df99f29da8ad5c4f76c088c98ff766540 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,8 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -98,7 +100,7 @@ public class EnrollmentProcessor extends CertProcessor { } CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq()); + return processEnrollment(data, cmsReq.getHttpReq(), null); } /** @@ -118,8 +120,11 @@ public class EnrollmentProcessor extends CertProcessor { * @param cmsReq the object holding the request and response information * @exception EBaseException an error has occurred */ - public HashMap processEnrollment(CertEnrollmentRequest data, HttpServletRequest request) - throws EBaseException { + public HashMap processEnrollment( + CertEnrollmentRequest data, + HttpServletRequest request, + AuthorityID aid) + throws EBaseException { try { if (CMS.debugOn()) { @@ -146,6 +151,10 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + if (aid != null) + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aid.toString()); + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java index 2826f477e358a5e16657e985d7f13079cdb14a33..ca2d6f6739906aad06289708a05f0983155b72e8 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java @@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EDeferException; @@ -327,6 +331,31 @@ public class RequestProcessor extends CertProcessor { } /** + * Ensure validity of AuthorityID and that CA exists and is enabled. + */ + private void ensureCAEnabled(String aidString) throws EBaseException { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + // this shouldn't happen because request was already accepted + throw new BadRequestDataException("Invalid AuthorityID in request data"); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem("ca"); + if (ca == null) + // this shouldn't happen + throw new CANotFoundException("Could not get top-level CA"); // shouldn't happen + ca = ca.getCA(aid); + if (ca == null) + // this shouldn't happen because request was already accepted + throw new CANotFoundException("Unknown CA: " + aidString); + if (!ca.getAuthorityEnabled()) + // authority was disabled after request was accepted + throw new CADisabledException("CA '" + aidString + "' is disabled"); + } + + /** * Approve request *

* @@ -346,11 +375,16 @@ public class RequestProcessor extends CertProcessor { * occurred */ private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale) - throws EProfileException { + throws EBaseException { String auditMessage = null; String auditSubjectID = auditSubjectID(); String auditRequesterID = auditRequesterID(req); + // ensure target CA is enabled + String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ensureCAEnabled(aidString); + try { profile.execute(req); req.setRequestStatus(RequestStatus.COMPLETE); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif new file mode 100644 index 0000000000000000000000000000000000000000..7d261f18fbc9475983bf93b1cddcc184d7f9d178 --- /dev/null +++ b/base/server/share/conf/schema-authority.ldif @@ -0,0 +1,8 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..a15601ae7a362635bc398b92b9bfda1c72f0dfc8 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,16 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From 4c67f33f613c9b4ab5e0a75cc8709b1acaf5c2f4 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 10 Jun 2015 03:02:35 -0400 Subject: [PATCH] Lightweight CAs: add ca-authority CLI Add CLI commands for creating, listing and showing lightweight CAs. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../certsrv/authority/AuthorityClient.java | 62 +++++++++++++++ .../src/com/netscape/certsrv/ca/CAClient.java | 3 +- .../netscape/cmstools/authority/AuthorityCLI.java | 49 ++++++++++++ .../cmstools/authority/AuthorityCreateCLI.java | 89 ++++++++++++++++++++++ .../cmstools/authority/AuthorityDisableCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityEnableCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityFindCLI.java | 62 +++++++++++++++ .../cmstools/authority/AuthorityShowCLI.java | 55 +++++++++++++ .../src/com/netscape/cmstools/cli/CACLI.java | 2 + 9 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..86de3352e2424211125c146edf759481448a2694 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -0,0 +1,62 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- +package com.netscape.certsrv.authority; + +import java.net.URISyntaxException; +import java.util.List; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; + +import com.netscape.certsrv.client.Client; +import com.netscape.certsrv.client.PKIClient; + +/** + * @author Fraser Tweedale + */ +public class AuthorityClient extends Client { + + public AuthorityResource proxy; + + public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException { + super(client, subsystem, "authority"); + proxy = createProxy(AuthorityResource.class); + } + + public List listCAs() { + Response response = proxy.listCAs(); + GenericType> type = new GenericType>() {}; + return client.getEntity(response, type); + } + + public AuthorityData getCA(String caIDString) { + Response response = proxy.getCA(caIDString); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData createCA(AuthorityData data) { + Response response = proxy.createCA(data); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData modifyCA(AuthorityData data) { + Response response = proxy.modifyCA(data.getID(), data); + return client.getEntity(response, AuthorityData.class); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 100644 --- a/base/common/src/com/netscape/certsrv/ca/CAClient.java +++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java @@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient; import com.netscape.certsrv.profile.ProfileClient; import com.netscape.certsrv.selftests.SelfTestClient; import com.netscape.certsrv.user.UserClient; +import com.netscape.certsrv.authority.AuthorityClient; public class CAClient extends SubsystemClient { @@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient { } public void init() throws URISyntaxException { - + addClient(new AuthorityClient(client, name)); addClient(new CertClient(client, name)); addClient(new GroupClient(client, name)); addClient(new ProfileClient(client, name)); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..cd0609204dd3e757925c6fec9488f293d31b2c9f --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -0,0 +1,49 @@ +package com.netscape.cmstools.authority; + +import com.netscape.certsrv.authority.AuthorityClient; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCLI extends CLI { + + public AuthorityClient authorityClient; + + public AuthorityCLI(CLI parent) { + super("authority", "CA management commands", parent); + + addModule(new AuthorityFindCLI(this)); + addModule(new AuthorityShowCLI(this)); + addModule(new AuthorityCreateCLI(this)); + addModule(new AuthorityDisableCLI(this)); + addModule(new AuthorityEnableCLI(this)); + } + + public String getFullName() { + if (parent instanceof MainCLI) { + // do not include MainCLI's name + return name; + } else { + return parent.getFullName() + "-" + name; + } + } + + public void execute(String[] args) throws Exception { + client = parent.getClient(); + authorityClient = new AuthorityClient(client, "ca"); + super.execute(args); + } + + protected static void printAuthorityData(AuthorityData data) { + System.out.println(" Authority DN: " + data.getDN()); + System.out.println(" ID: " + data.getID()); + String parentAID = data.getParentID(); + if (parentAID != null) + System.out.println(" Parent DN: " + data.getParentID()); + System.out.println(" Enabled: " + data.getEnabled()); + String desc = data.getDescription(); + if (desc != null) + System.out.println(" Description: " + desc); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..9799c7db8eb00d59384754684aea2c3a3bdeec67 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -0,0 +1,89 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityCreateCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityCreateCLI(AuthorityCLI authorityCLI) { + super("create", "Create CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option(null, "parent", true, "ID of parent CA"); + optParent.setArgName("id"); + options.addOption(optParent); + + Option optDesc = new Option(null, "desc", true, "Optional description"); + optDesc.setArgName("string"); + options.addOption(optDesc); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No DN specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String parentAIDString = null; + if (cmd.hasOption("parent")) { + parentAIDString = cmd.getOptionValue("parent"); + try { + new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + System.err.println("Bad CA ID: " + parentAIDString); + printHelp(); + System.exit(-1); + } + } else { + System.err.println("Must specify parent authority"); + printHelp(); + System.exit(-1); + } + + String desc = null; + if (cmd.hasOption("desc")) + desc = cmd.getOptionValue("desc"); + + String dn = cmdArgs[0]; + AuthorityData data = new AuthorityData( + dn, null, parentAIDString, true /* enabled */, desc); + AuthorityData newData = authorityCLI.authorityClient.createCA(data); + AuthorityCLI.printAuthorityData(newData); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..c3d24439f0dfefc85b210e65abb252ab4c7c7b25 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityDisableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityDisableCLI(AuthorityCLI authorityCLI) { + super("disable", "Disable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, false, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..23eb9167b761c519b2e1b6ae1f933248476541e0 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityEnableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityEnableCLI(AuthorityCLI authorityCLI) { + super("enable", "Enable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, cmdArgs[0], null, true, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..c1aa99fc627e8e0ccfd1f12a23610a13dd5cfbbb --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java @@ -0,0 +1,62 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityFindCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityFindCLI(AuthorityCLI authorityCLI) { + super("find", "Find CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName(), options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + @SuppressWarnings("unused") + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + List datas = authorityCLI.authorityClient.listCAs(); + + MainCLI.printMessage(datas.size() + " entries matched"); + if (datas.size() == 0) return; + + boolean first = true; + for (AuthorityData data : datas) { + if (first) + first = false; + else + System.out.println(); + AuthorityCLI.printAuthorityData(data); + } + + MainCLI.printMessage("Number of entries returned " + datas.size()); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..2993430f0a8b2d0750720d2b251cad26a6707751 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java @@ -0,0 +1,55 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityShowCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityShowCLI(AuthorityCLI authorityCLI) { + super("show", "Show CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + String caIDString = cmdArgs[0]; + AuthorityData data = authorityCLI.authorityClient.getCA(caIDString); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java @@ -20,6 +20,7 @@ package com.netscape.cmstools.cli; import com.netscape.certsrv.ca.CAClient; import com.netscape.certsrv.client.Client; +import com.netscape.cmstools.authority.AuthorityCLI; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.group.GroupCLI; import com.netscape.cmstools.profile.ProfileCLI; @@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI { public CACLI(CLI parent) { super("ca", "CA management commands", parent); + addModule(new AuthorityCLI(this)); addModule(new CertCLI(this)); addModule(new GroupCLI(this)); addModule(new KRAConnectorCLI(this)); -- 2.4.3 -------------- next part -------------- From 874e7860b4dce6f92e9e99c5561a8f725b265d37 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 1 Sep 2015 09:57:42 -0400 Subject: [PATCH] Lightweight CAs: REST cert request param to specify authority Add the optional "ca" query parameter for REST cert request submission. Also update the ca-cert-request-submit CLI command with an option to provide an AuthorityID. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../src/com/netscape/cms/servlet/test/CATest.java | 4 +- .../server/ca/rest/CertRequestService.java | 41 ++++++++++++++++++-- .../src/com/netscape/certsrv/cert/CertClient.java | 16 +++++++- .../netscape/certsrv/cert/CertRequestResource.java | 5 ++- .../cmstools/cert/CertRequestSubmitCLI.java | 44 +++++++++++++++++++++- .../cmstools/client/ClientCertRequestCLI.java | 2 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 12 ++++-- 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java index 15023cad939abb11927abc64fe5916e04cb65661..92c776d17a3993fdd50ecbdfd3e5e3b5692888f2 100644 --- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java +++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java @@ -288,7 +288,7 @@ public class CATest { private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); @@ -308,7 +308,7 @@ public class CATest { private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 1da1ce1713541e52164e9e8fbcbf39ca2332540d..7cb4ff71e18b6e29bf55c11dc99bbfb9b83dd60f 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -18,6 +18,7 @@ package org.dogtagpki.server.ca.rest; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Enumeration; @@ -41,8 +42,11 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -63,6 +67,7 @@ import com.netscape.certsrv.request.RequestNotFoundException; import com.netscape.cms.servlet.base.PKIService; import com.netscape.cms.servlet.cert.CertRequestDAO; import com.netscape.cmsutil.ldap.LDAPUtil; +import netscape.security.x509.X500Name; /** * @author alee @@ -115,13 +120,43 @@ public class CertRequestService extends PKIService implements CertRequestResourc } @Override - public Response enrollCert(CertEnrollmentRequest data) { - + public Response enrollCert(CertEnrollmentRequest data, String aidString, String adnString) { if (data == null) { CMS.debug("enrollCert: data is null"); throw new BadRequestException("Unable to create enrollment reequest: Invalid input data"); } + if (aidString != null && adnString != null) + throw new BadRequestException("Cannot provide both issuer-id and issuer-dn"); + + AuthorityID aid = null; + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (aidString != null) { + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("invalid AuthorityID: " + aidString); + } + ca = ca.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + aidString); + } + if (adnString != null) { + X500Name adn = null; + try { + adn = new X500Name(adnString); + } catch (IOException e) { + throw new BadRequestException("invalid DN: " + adnString); + } + ca = ca.getCA(adn); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + adnString); + aid = ca.getAuthorityID(); + } + if (!ca.getAuthorityEnabled()) + throw new ConflictingOperationException("CA not enabled: " + aid.toString()); + data.setRemoteHost(servletRequest.getRemoteHost()); data.setRemoteAddr(servletRequest.getRemoteAddr()); @@ -129,7 +164,7 @@ public class CertRequestService extends PKIService implements CertRequestResourc CertRequestInfos infos; try { - infos = dao.submitRequest(data, servletRequest, uriInfo, getLocale(headers)); + infos = dao.submitRequest(aid, data, servletRequest, uriInfo, getLocale(headers)); } catch (EAuthException e) { CMS.debug("enrollCert: authentication failed: " + e); throw new UnauthorizedException(e.toString()); diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java index 42b04b7021f0063894c340c177915d799b621ddd..1d4ccd2cf7e83a8ed3b33253b1416110d5504125 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertClient.java +++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java @@ -17,16 +17,19 @@ //--- END COPYRIGHT BLOCK --- package com.netscape.certsrv.cert; +import java.io.IOException; import java.net.URISyntaxException; import javax.ws.rs.core.Response; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.client.Client; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.SubsystemClient; import com.netscape.certsrv.dbs.certdb.CertId; import com.netscape.certsrv.profile.ProfileDataInfos; import com.netscape.certsrv.request.RequestId; +import netscape.security.x509.X500Name; /** * @author Endi S. Dewata @@ -85,8 +88,17 @@ public class CertClient extends Client { return client.getEntity(response, CertRequestInfo.class); } - public CertRequestInfos enrollRequest(CertEnrollmentRequest data) { - Response response = certRequestClient.enrollCert(data); + public CertRequestInfos enrollRequest( + CertEnrollmentRequest data, AuthorityID aid, X500Name adn) { + String aidString = aid != null ? aid.toString() : null; + String adnString = null; + if (adn != null) { + try { + adnString = adn.toLdapDNString(); + } catch (IOException e) { + } + } + Response response = certRequestClient.enrollCert(data, aidString, adnString); return client.getEntity(response, CertRequestInfos.class); } diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java index 7f08b4af392e3e56419abdad7cb66bd191688222..493f6f53a5a5e30804532305b199d44a66eecd24 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java +++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java @@ -37,7 +37,10 @@ public interface CertRequestResource { @POST @Path("certrequests") @ClientResponseType(entityType=CertRequestInfos.class) - public Response enrollCert(CertEnrollmentRequest data); + public Response enrollCert( + CertEnrollmentRequest data, + @QueryParam("issuer-id") String caIDString, + @QueryParam("issuer-dn") String caDNString); /** * Used to retrieve cert request info for a specific request diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..e6b59a5d7fcaced100699ea067a8f95b992722f3 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -2,18 +2,22 @@ package com.netscape.cmstools.cert; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Arrays; import java.util.Scanner; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; +import netscape.security.x509.X500Name; public class CertRequestSubmitCLI extends CLI { @@ -22,6 +26,14 @@ public class CertRequestSubmitCLI extends CLI { public CertRequestSubmitCLI(CertCLI certCLI) { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; + + Option optAID = new Option(null, "issuer-id", true, "Authority ID (top-level CA if omitted)"); + optAID.setArgName("id"); + options.addOption(optAID); + + Option optADN = new Option(null, "issuer-dn", true, "Authority DN (top-level CA if omitted)"); + optADN.setArgName("dn"); + options.addOption(optADN); } public void printHelp() { @@ -55,9 +67,39 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } + AuthorityID aid = null; + if (cmd.hasOption("issuer-id")) { + String aidString = cmd.getOptionValue("issuer-id"); + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + System.err.println("Bad AuthorityID: " + aidString); + printHelp(); + System.exit(-1); + } + } + + X500Name adn = null; + if (cmd.hasOption("issuer-dn")) { + String adnString = cmd.getOptionValue("issuer-dn"); + try { + adn = new X500Name(adnString); + } catch (IOException e) { + System.err.println("Bad DN: " + adnString); + printHelp(); + System.exit(-1); + } + } + + if (aid != null && adn != null) { + System.err.println("--issuer-id and --issuer-dn options are mutually exclusive"); + printHelp(); + System.exit(-1); + } + try { CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd); + CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid, adn); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(cri); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..db71c8a0f7db4644290efb766178b76668c22377 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI { System.out.println("Sending certificate request."); } - CertRequestInfos infos = certClient.enrollRequest(request); + CertRequestInfos infos = certClient.enrollRequest(request, null, null); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index 27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b..a2e4b583d318ac8412361850d91233b77a447e13 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -30,6 +30,7 @@ import javax.ws.rs.core.UriInfo; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; @@ -164,8 +165,13 @@ public class CertRequestDAO extends CMSRequestDAO { * @throws EBaseException * @throws ServletException */ - public CertRequestInfos submitRequest(CertEnrollmentRequest data, HttpServletRequest request, UriInfo uriInfo, - Locale locale) throws EBaseException { + public CertRequestInfos submitRequest( + AuthorityID aid, + CertEnrollmentRequest data, + HttpServletRequest request, + UriInfo uriInfo, + Locale locale) + throws EBaseException { CertRequestInfos ret = new CertRequestInfos(); @@ -175,7 +181,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request, null); + results = processor.processEnrollment(data, request, aid); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); -- 2.4.3 From edewata at redhat.com Thu Sep 24 16:40:53 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Thu, 24 Sep 2015 11:40:53 -0500 Subject: [Pki-devel] PATCH 005] Replace legacy Python base64 invocations with Py3-safe code In-Reply-To: <560002B6.2020708@redhat.com> References: <55912FF5.1000201@redhat.com> <55D48CA2.8070003@redhat.com> <55DE01BF.7050201@redhat.com> <560002B6.2020708@redhat.com> Message-ID: <56042795.2060200@redhat.com> On 9/21/2015 8:14 AM, Christian Heimes wrote: > On 2015-08-26 20:13, Endi Sukma Dewata wrote: >> As discussed on IRC, in b64encode() there's a code that converts Unicode >> string data into ASCII: >> >> if isinstance(data, six.text_type): >> data = data.encode('ascii') >> >> This conversion will not work if the string contains non-ASCII >> characters, which limits the usage of this method. >> >> It's not that Python 3's base64.b64encode() doesn't support ASCII text >> as noted in the method description, but it cannot encode Unicode string >> because Unicode doesn't have a binary representation unless it's encoded >> first. >> >> I think in this case the proper encoding for Unicode is UTF-8. So the >> line should be changed to: >> >> if isinstance(data, six.text_type): >> data = data.encode('utf-8') >> >> In b64decode(), the incoming data is a Unicode string containing the >> base-64 encoding characters which are all ASCII, so data.encode('ascii') >> will work, but to be more consistent it can also use data.encode('utf-8'). > > We discussed the ticket a couple of weeks ago on IRC. The function is > deliberately limited to ASCII only text in order to avoid encoding hell. > Python 3 tries to avoid encoding bugs by removing implicit encoding of > text and decoding of bytes. > > The special treatment is only required for encoding/decoding X.509 data > in JSON strings for Python 3. Since it's a special case I changed the > patch. The additional two functions are now called decode_cert() and > encode_cert(). The functions are only used for X.509 PEM <-> DER in JSON. > > Christian > ACK. -- Endi S. Dewata From edewata at redhat.com Thu Sep 24 23:26:39 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Thu, 24 Sep 2015 18:26:39 -0500 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> Message-ID: <560486AF.2010508@redhat.com> On 9/24/2015 9:20 AM, Fraser Tweedale wrote: > Latest patches attached. Relative to previous patchset this one: > > - fixes a compile error in CATest.java > - fixes a ton of warnings and some poorly ordered imports > - adds ACLs and ACL enforcement for privileged operations > on AuthorityResource > > Here's an ldif snippet for adding the ACLs to an existing database > > dn: cn=aclResources,o=ipaca > changetype: modify > add: resourceACLS > resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities > resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities > > Cheers, > Fraser Some comments: 1. Right now the create & modify operations over non-secure URL will fail: $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf-dcc34367e836 ForbiddenException: No user principal provided. It works with the secure URL: $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u caadmin -w Secret123 ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf-dcc34367e836 Authority DN: O=test ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4 Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836 Enabled: true This can be fixed by adding into the web.xml and registering it in auth-method.properties. 2. The "Parent DN" field in the output above should show the DN of the parent authority instead of the ID. We probably should show both Parent DN and Parent ID. 3. Per discussion with alee, we need a way to find the host/main CA using something like: $ pki ca-authority-show --host-authority 4. I think we also need a way to translate a DN into ID: $ pki ca-authority-show --dn 5. Also per discussion with alee, the authority DN should be unique only among active CAs. So you should be able to create a CA, disable it, then create another one with the same DN. If you try to enable the old CA it should fail. This can be implemented later. 6. In AuthorityData.java the @XmlRootElement probably should be changed to "authority" for consistency. Also the following fields can be renamed because the "a" is redundant: * aid -> id * parentAID -> parentID I think the XML output will look better that way. 7. The method description in ISigningUnit.java doesn't match the method name (public vs. private). I think these are not difficult to fix, and once fixed it should be sufficient to push as initial implementation, so consider this a conditional ACK (unless alee has other comments). Item #5 (or #4 too) can be implemented later. I also created this page to document the CLI: http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI Feel free to expand it further. -- Endi S. Dewata From ftweedal at redhat.com Fri Sep 25 13:30:12 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Fri, 25 Sep 2015 23:30:12 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <560486AF.2010508@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> <560486AF.2010508@redhat.com> Message-ID: <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> Latest patches attached. Most issued addressed; see inline for comments and ticket URLs for deferred items. There is a problem with allowing authority DNs to be reused - when adding the cert to the NSSDB, despite what nickname you tell it to you, it will put the cert under the nickname of the existing cert with that subject DN. Thus when you go to find the cert by nickname, it cannot locate it. Failure ensues. This is possibly a bug in NSS (it's certainly surprising), but I need more time to analyse it. I've attached an experimental (builds, but as yet completely untested with high chance of brokens) patch on top of my current patches that switches to looking up the cert by issuer DN and serial. I need to consider implications of this switch including for renewal in replicated environments. It might not be the right approach but afaict it is the only other way CryptoManager gives you to look up a cert. Ideally we discover that NSS/JSS is doing the wrong thing with what it is doing with nicknames and we can get a fix there are move on with life. Anyhow, other comments inline. Thanks, Fraser On Thu, Sep 24, 2015 at 06:26:39PM -0500, Endi Sukma Dewata wrote: > Some comments: > > 1. Right now the create & modify operations over non-secure URL will fail: > > $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin > ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf-dcc34367e836 > ForbiddenException: No user principal provided. > > It works with the secure URL: > > $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u caadmin > -w Secret123 ca-authority-create o=test --parent > 85a2c5c2-869d-467c-9adf-dcc34367e836 > Authority DN: O=test > ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4 > Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836 > Enabled: true > > This can be fixed by adding into the web.xml and > registering it in auth-method.properties. > Thanks for this explanation. Worked a treat! > 2. The "Parent DN" field in the output above should show the DN of the > parent authority instead of the ID. We probably should show both Parent DN > and Parent ID. > Fixed the label, filed ticket for including the Parent/Issuer DN: https://fedorahosted.org/pki/ticket/1618 > 3. Per discussion with alee, we need a way to find the host/main CA using > something like: > > $ pki ca-authority-show --host-authority > Done. > 4. I think we also need a way to translate a DN into ID: > > $ pki ca-authority-show --dn > Filed ticket: https://fedorahosted.org/pki/ticket/1617 > 5. Also per discussion with alee, the authority DN should be unique only > among active CAs. So you should be able to create a CA, disable it, then > create another one with the same DN. If you try to enable the old CA it > should fail. This can be implemented later. > Per discussion above, implemented, but breaks your CA if you try it! > 6. In AuthorityData.java the @XmlRootElement probably should be changed to > "authority" for consistency. Also the following fields can be renamed > because the "a" is redundant: > * aid -> id > * parentAID -> parentID > I think the XML output will look better that way. > Done. > 7. The method description in ISigningUnit.java doesn't match the method name > (public vs. private). > Fixed; well spotted. > I think these are not difficult to fix, and once fixed it should be > sufficient to push as initial implementation, so consider this a conditional > ACK (unless alee has other comments). Item #5 (or #4 too) can be implemented > later. > > I also created this page to document the CLI: > http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI > Feel free to expand it further. > Thanks a bunch; I will review this Monday. This also reminds me to spend some time updating the design pages as well - there have been many changes! > -- > Endi S. Dewata -------------- next part -------------- From 037e62d46fa38d56c558291202b62f0e73e0b5d7 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Lightweight CAs: initial support This commit adds initial support for "lightweight CAs" - CAs that inhabit an existing CA instance and share the request queue and certificate database of the "top-level CA". We initially support only sub-CAs under the top-level CA - either direct sub-CAs or nested. The general design will support hosting unrelated CAs but creation or import of unrelated CAs is not yet implemented. Part of: https://fedorahosted.org/pki/ticket/1213 --- base/ca/shared/conf/acl.ldif | 2 + base/ca/shared/conf/acl.properties | 4 + base/ca/shared/conf/auth-method.properties | 1 + base/ca/shared/conf/db.ldif | 5 + base/ca/shared/webapps/ca/WEB-INF/web.xml | 10 + base/ca/src/com/netscape/ca/CAService.java | 53 +- .../src/com/netscape/ca/CertificateAuthority.java | 682 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 282 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../server/ca/rest/CertRequestService.java | 5 + .../netscape/certsrv/authority/AuthorityData.java | 123 ++++ .../certsrv/authority/AuthorityResource.java | 96 +++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CADisabledException.java | 15 + .../netscape/certsrv/ca/CANotFoundException.java | 14 + .../com/netscape/certsrv/ca/CATypeException.java | 16 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 68 ++ .../certsrv/ca/IssuerUnavailableException.java | 15 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 28 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../com/netscape/cms/servlet/base/PKIService.java | 5 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 2 +- .../cms/servlet/cert/EnrollmentProcessor.java | 15 +- .../cms/servlet/cert/RequestProcessor.java | 36 +- .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-authority.ldif | 8 + base/server/share/conf/schema.ldif | 13 + 33 files changed, 1507 insertions(+), 132 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CADisabledException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CATypeException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-authority.ldif diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 0da10939fc64dc88b32016c308fc13a1bab2d14f..54c9f1d5c64b6578de83f1b7ffdff922a69975f4 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -57,3 +57,5 @@ resourceACLS: certServer.ca.certs:execute:allow (execute) group="Certificate Man resourceACLS: certServer.ca.groups:execute:allow (execute) group="Administrators":Admins may execute group operations resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="Administrators":Only admins can access selftests. resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations +resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities +resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index d14d1832c7cff7db055f2274f1ed30223d16cad8..f0b5b9f650ad2fc4bde531ade94347a7280d3089 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -21,3 +21,7 @@ securityDomain.installToken = certServer.securitydomain.domainxml,read selftests.read = certServer.ca.selftests,read selftests.execute = certServer.ca.selftests,execute users = certServer.ca.users,execute +authorities.create = certServer.ca.authorities,create +authorities.list = certServer.ca.authorities,list +authorities.modify = certServer.ca.authorities,modify +authorities.read = certServer.ca.authorities,read diff --git a/base/ca/shared/conf/auth-method.properties b/base/ca/shared/conf/auth-method.properties index a213534adcbf427c3c76b9981624edb8c93ea28d..8d67690af88d387f38fd8fcf1c2fdfa8bbb492fe 100644 --- a/base/ca/shared/conf/auth-method.properties +++ b/base/ca/shared/conf/auth-method.properties @@ -8,6 +8,7 @@ default = * account = certUserDBAuthMgr,passwdUserDBAuthMgr +authorities = certUserDBAuthMgr certs = certUserDBAuthMgr certrequests = certUserDBAuthMgr groups = certUserDBAuthMgr diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=authorities,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: authorities diff --git a/base/ca/shared/webapps/ca/WEB-INF/web.xml b/base/ca/shared/webapps/ca/WEB-INF/web.xml index bba40e203463ac97138e6c6749bca8f979711444..628eea2a2d78147560e2ce4bd5eddaf634dd11ea 100644 --- a/base/ca/shared/webapps/ca/WEB-INF/web.xml +++ b/base/ca/shared/webapps/ca/WEB-INF/web.xml @@ -2417,6 +2417,16 @@ + Authority Services + /rest/authorities/* + + + CONFIDENTIAL + + + + + Security Domain Services /rest/securityDomain/installToken diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..e9ac6a9712f58e8024623956d3a6dce6a718a9db 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -23,36 +23,26 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger; +import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import netscape.security.util.DerOutputStream; -import netscape.security.util.DerValue; -import netscape.security.x509.AlgorithmId; -import netscape.security.x509.CertificateChain; -import netscape.security.x509.CertificateIssuerName; -import netscape.security.x509.CertificateSubjectName; -import netscape.security.x509.CertificateVersion; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CRLImpl; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509CertInfo; -import netscape.security.x509.X509ExtensionException; -import netscape.security.x509.X509Key; - import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +50,9 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +66,21 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; 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.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +95,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +107,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +125,29 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPModificationSet; +import netscape.ldap.LDAPSearchResults; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.CertificateIssuerName; +import netscape.security.x509.CertificateSubjectName; +import netscape.security.x509.CertificateVersion; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CRLImpl; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509CertInfo; +import netscape.security.x509.X509ExtensionException; +import netscape.security.x509.X509Key; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +161,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap<>(); + protected CertificateAuthority hostCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected boolean authorityEnabled = true; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + hostCA = this; + } + + /** + * Construct and initialise a lightweight authority + */ + private CertificateAuthority( + CertificateAuthority hostCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription, + boolean authorityEnabled + ) throws EBaseException { + setId(hostCA.getId()); + this.hostCA = hostCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + this.authorityEnabled = authorityEnabled; + mNickname = signingKeyNickname; + init(hostCA.mOwner, hostCA.mConfig); + } + + public boolean isHostAuthority() { + return hostCA == this; + } + + private void ensureEnabled() throws CADisabledException { + if (!authorityEnabled) + throw new CADisabledException("Authority is disabled"); + } + + public boolean getAuthorityEnabled() { + return authorityEnabled; } /** @@ -334,8 +401,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isHostAuthority()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = hostCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +439,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The host CA owns these resources so skip these + * steps for lightweight CAs. + */ + if (isHostAuthority()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +479,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isHostAuthority()) + loadLightweightCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +490,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isHostAuthority()) { + mByName = hostCA.mByName; + mCRLPublisher = hostCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +641,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isHostAuthority()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +653,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + // lightweight authorities don't own these resources + if (!isHostAuthority()) + return; + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -967,6 +1069,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CRLImpl sign(X509CRLImpl crl, String algname) throws EBaseException { + ensureEnabled(); X509CRLImpl signedcrl = null; IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); @@ -1039,6 +1142,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CertImpl sign(X509CertInfo certInfo, String algname) throws EBaseException { + ensureEnabled(); X509CertImpl signedcert = null; @@ -1123,6 +1227,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public byte[] sign(byte[] data, String algname) throws EBaseException { + ensureEnabled(); return mSigningUnit.sign(data, algname); } @@ -1228,13 +1333,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isHostAuthority() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1409,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isHostAuthority() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1548,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isHostAuthority()) { + mCertRepot = hostCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1581,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isHostAuthority()) { + mCRLRepot = hostCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1601,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1624,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isHostAuthority()) { + mPublisherProcessor = hostCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1792,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isHostAuthority()) { + mPolicy = hostCA.mPolicy; + mService = hostCA.mService; + mNotify = hostCA.mNotify; + mPNotify = hostCA.mPNotify; + mRequestQueue = hostCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1859,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isHostAuthority()) { + mCRLIssuePoints = hostCA.mCRLIssuePoints; + mMasterCRLIssuePoint = hostCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1929,109 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register lightweight CAs. + * + * This method must only be called by the host CA. + */ + private synchronized void loadLightweightCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + boolean foundHostAuthority = false; + boolean haveLightweightCAsContainer = true; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); + + if (aidAttr == null || nickAttr == null || dnAttr == null) + throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN()); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + + X500Name dn = null; + try { + dn = new X500Name((String) dnAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityDN: " + entry.getDN()); + } + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + if (dn.equals(mName)) { + foundHostAuthority = true; + this.authorityID = aid; + this.authorityDescription = desc; + caMap.put(aid, this); + continue; + } + + @SuppressWarnings("unused") + X500Name parentDN = null; + if (parentDNAttr != null) { + try { + parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); + } + } + + String keyNick = (String) nickAttr.getStringValues().nextElement(); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + boolean enabled = true; + LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled"); + if (enabledAttr != null) { + String enabledString = (String) + enabledAttr.getStringValues().nextElement(); + enabled = enabledString.equalsIgnoreCase("TRUE"); + } + + CertificateAuthority ca = new CertificateAuthority( + this, aid, parentAID, keyNick, desc, enabled); + caMap.put(aid, ca); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + haveLightweightCAsContainer = false; + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + if (haveLightweightCAsContainer && !foundHostAuthority) { + CMS.debug("loadLightweightCAs: no entry for host authority"); + CMS.debug("loadLightweightCAs: adding entry for host authority"); + caMap.put(addHostAuthorityEntry(), this); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -1960,6 +2193,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } private BasicOCSPResponse sign(ResponseData rd) throws EBaseException { + ensureEnabled(); try (DerOutputStream out = new DerOutputStream()) { DerOutputStream tmp = new DerOutputStream(); @@ -2083,4 +2317,312 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all authorities (including host authority) + */ + public synchronized List getCAs() { + List cas = new ArrayList<>(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get authority by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the host authority. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? hostCA : caMap.get(aid); + } + + public ICertificateAuthority getCA(X500Name dn) { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getX500Name().equals(dn)) + return ca; + } + return null; + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new lightweight authority. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority ca = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(ca.getAuthorityID(), ca); + } + return ca; + } + + private void ensureAuthorityDNAvailable(X500Name dn) + throws IssuerUnavailableException { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getAuthorityEnabled() && ca.getX500Name().equals(dn)) + throw new IssuerUnavailableException( + "DN '" + dn + "' is in use by an enabled authority"); + } + } + + /** + * Create a new lightweight authority signed by this authority. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check requested DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + ensureAuthorityDNAvailable(subjectX500Name); + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = hostCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + String parentDNString = null; + try { + parentDNString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", subjectDN), + new LDAPAttribute("authorityParentDN", parentDNString) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating lightweight CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + hostCA, aid, this.authorityID, nickname, description, true); + } + + /** + * Add an LDAP entry for the host authority. + * + * This method also sets the authorityID and authorityDescription + * fields. + * + * It is the caller's responsibility to add the returned + * AuthorityID to the caMap. + */ + private AuthorityID addHostAuthorityEntry() throws EBaseException { + if (!isHostAuthority()) + throw new EBaseException("Can only invoke from host CA"); + + // generate authority ID + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + String dnString = null; + try { + dnString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + String desc = "Host authority"; + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", getNickname()), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", dnString), + new LDAPAttribute("description", desc) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addHostAuthorityEntry"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + conn.add(ldapEntry); + } catch (LDAPException e) { + throw new ELdapException("Error adding host authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + this.authorityID = aid; + this.authorityDescription = desc; + return aid; + } + + /** + * Update lightweight authority attributes. + * + * Pass null values to exclude an attribute from the update. + * + * If a passed value matches the current value, it is excluded + * from the update. + * + * To remove optional string values, pass the empty string. + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException { + if (isHostAuthority() && enabled != null && !enabled) + throw new CATypeException("Cannot disable the host CA"); + + LDAPModificationSet mods = new LDAPModificationSet(); + + boolean nextEnabled = authorityEnabled; + if (enabled != null && enabled.booleanValue() != authorityEnabled) { + if (enabled) + ensureAuthorityDNAvailable(mName); + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE")); + nextEnabled = enabled; + } + + String nextDesc = authorityDescription; + if (desc != null) { + if (!desc.isEmpty() && authorityDescription != null + && !desc.equals(authorityDescription)) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } else if (desc.isEmpty() && authorityDescription != null) { + mods.add( + LDAPModification.DELETE, + new LDAPAttribute("description", authorityDescription)); + nextDesc = null; + } else if (!desc.isEmpty() && authorityDescription == null) { + mods.add( + LDAPModification.ADD, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } + } + + if (mods.size() > 0) { + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new EBaseException("Error adding authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + // update was successful; update CA's state + authorityEnabled = nextEnabled; + authorityDescription = nextDesc; + } + } + } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..820f8ab6499eed9fdb8e3d8d782df64c71ad1fc3 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,282 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.ForbiddenException; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.cms.servlet.base.PKIService; +import com.netscape.cmsutil.util.Utils; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority hostCA; + + public AuthorityService() { + hostCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList<>(); + for (ICertificateAuthority ca : hostCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity> entity = + new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + ICertificateAuthority ca = hostCA; + + if (!AuthorityResource.HOST_AUTHORITY.equals(aidString)) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + } + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response getCert(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + return Response.ok(ca.getCaX509Cert().getEncoded()).build(); + } catch (CertificateEncodingException e) { + // this really is a 500 Internal Server Error + throw new PKIException("Error encoding certificate: " + e); + } + } + + @Override + public Response getCertPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("CERTIFICATE", der)).build(); + } + + @Override + public Response getChain(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ca.getCACertChain().encode(out); + } catch (IOException e) { + throw new PKIException("Error encoding certificate chain: " + e); + } + + return Response.ok(out.toByteArray()).build(); + } + + @Override + public Response getChainPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("PKCS7", der)).build(); + } + + @Override + public Response createCA(AuthorityData data) { + String parentAIDString = data.getParentID(); + AuthorityID parentAID = null; + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad Authority ID: " + parentAIDString); + } + + try { + ICertificateAuthority subCA = hostCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating CA: " + e.toString()); + } + } + + @Override + public Response modifyCA(String aidString, AuthorityData data) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.modifyAuthority(data.getEnabled(), data.getDescription()); + return createOKResponse(readAuthorityData(ca)); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + + @Override + public Response enableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, null, true, null)); + } + + @Override + public Response disableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, null, false, null)); + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + ca.isHostAuthority(), + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityEnabled(), + ca.getAuthorityDescription() + ); + } + + private String toPem(String name, byte[] data) { + return "-----BEGIN " + name + "-----\n" + + Utils.base64encode(data) + + "-----END " + name + "-----\n"; + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..1da1ce1713541e52164e9e8fbcbf39ca2332540d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -38,9 +38,11 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -210,6 +212,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("changeRequestState: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("changeRequestState: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (EPropertyException e) { CMS.debug("changeRequestState: execution error " + e); throw new PKIException(CMS.getUserMessage(getLocale(headers), diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..2312c39895c09a4b7dbf994d43c2c068eeaec2d4 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,123 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import org.jboss.resteasy.plugins.providers.atom.Link; + + at XmlRootElement(name = "authority") + at XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @XmlAttribute + protected Boolean isHostAuthority; + + public Boolean getIsHostAuthority() { + return isHostAuthority; + } + + + @XmlAttribute + protected String id; + + public String getID() { + return id; + } + + + @XmlAttribute + protected String parentID; + + public String getParentID() { + return parentID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + Boolean isHostAuthority, + String dn, String id, String parentID, + Boolean enabled, String description) { + this.isHostAuthority = isHostAuthority; + this.dn = dn; + this.id = id; + this.parentID = parentID; + this.enabled = enabled; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..eaef903db444512dbea6c87b11800130d94a944d --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,96 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + + at Path("authorities") +public interface AuthorityResource { + + public static final String HOST_AUTHORITY = "host-authority"; + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/pkix-cert") + @ClientResponseType(entityType=byte[].class) + public Response getCert(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getCertPEM(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/pkcs7-mime") + @ClientResponseType(entityType=byte[].class) + public Response getChain(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getChainPEM(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.create") + public Response createCA(AuthorityData data); + + /** + * Modify a CA (supports partial updates). + * + * isHostEnabled, authorityID, authorityParentID and DN are + * immutable; differences in these values are ignored. + * + * Other values, if null, are ignored, otherwise they are + * set to the new value. To remove the description, use an + * empty string. + */ + @PUT + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response modifyCA( + @PathParam("id") String caIDString, + AuthorityData data); + + @POST + @Path("{id}/enable") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response enableCA(@PathParam("id") String caIDString); + + @POST + @Path("{id}/disable") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response disableCA(@PathParam("id") String caIDString); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..daac587b75843f5faf75e556d4d0135c8ffc8fd7 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + if (s == null) + throw new IllegalArgumentException("null AuthorityID string"); + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(AuthorityID aid) { + return uuid.compareTo(aid.uuid); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..9b3f16b90a8d28b87e993575037a2a19517e17b9 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot perform an operation + * because it is disabled. + */ +public class CADisabledException extends ECAException { + + private static final long serialVersionUID = -8827509070155037699L; + + public CADisabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..f292077ece3089d398ad0cf3a008b7e5218a6bcd --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,14 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + private static final long serialVersionUID = -4618887355685066120L; + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CATypeException.java b/base/common/src/com/netscape/certsrv/ca/CATypeException.java new file mode 100644 index 0000000000000000000000000000000000000000..19eb680e88aee3baac51abd95439c46487bf5720 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java @@ -0,0 +1,16 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be completed + * because the CA is the wrong type (e.g., an operation that + * only applies to lightweight CAs). + */ +public class CATypeException extends ECAException { + + private static final long serialVersionUID = -6004456461295692150L; + + public CATypeException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..31d5c9277a63ca7c916f39651300b0c9a9061c1e 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,71 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all authorities, including host authority. + */ + public List getCAs(); + + /** + * Return whether this CA is the host authority (not a + * lightweight authority). + */ + public boolean isHostAuthority(); + + /** + * Get the AuthorityID of this CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the AuthorityID of this CA's parent CA, if available. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public boolean getAuthorityEnabled(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Get the CA by DN. Returns null if CA not found. + */ + public ICertificateAuthority getCA(X500Name dn); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; + + /** + * Update authority configurables. + * + * @param enabled Whether CA is enabled or disabled + * @param desc Description; null or empty removes it + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..75bf88251bfadb687c7bf85944a806339f6f3b9f --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + private static final long serialVersionUID = -6247493607604418446L; + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..12667120e3d87deecb786965b4abcef492ac556d 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (absense implies host authority) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..a5d641e93f9aca9cd47dac3084227dbc637d83d9 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the private key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..bd71a4ef8cf710008fc861a022a553d5064c37ba 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -20,20 +20,23 @@ package com.netscape.cms.profile.def; import java.io.IOException; import java.util.Locale; -import netscape.security.x509.AuthorityKeyIdentifierExtension; -import netscape.security.x509.KeyIdentifier; -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.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; +import netscape.security.x509.AuthorityKeyIdentifierExtension; +import netscape.security.x509.KeyIdentifier; +import netscape.security.x509.PKIXExtensions; +import netscape.security.x509.X509CertInfo; + /** * This class implements an enrollment default policy * that populates Authority Key Identifier extension @@ -161,18 +164,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java index 4ebf075cb709e813fb6a919c507e9847455e70b2..fe77fd567922a49641938cde99d533c091398b75 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java +++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java @@ -56,7 +56,10 @@ public class PKIService { public static List MESSAGE_FORMATS = Arrays.asList( MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_FORM_URLENCODED_TYPE + MediaType.APPLICATION_FORM_URLENCODED_TYPE, + MediaType.valueOf("application/pkix-cert"), + MediaType.valueOf("application/pkcs7-mime"), + MediaType.valueOf("application/x-pem-file") ); public final static int MIN_FILTER_LENGTH = 3; diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index c94ee14961ef39681a53f506b24e4ca5ab06a27e..27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -175,7 +175,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request); + results = processor.processEnrollment(data, request, null); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..e5b9a14df99f29da8ad5c4f76c088c98ff766540 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,8 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -98,7 +100,7 @@ public class EnrollmentProcessor extends CertProcessor { } CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq()); + return processEnrollment(data, cmsReq.getHttpReq(), null); } /** @@ -118,8 +120,11 @@ public class EnrollmentProcessor extends CertProcessor { * @param cmsReq the object holding the request and response information * @exception EBaseException an error has occurred */ - public HashMap processEnrollment(CertEnrollmentRequest data, HttpServletRequest request) - throws EBaseException { + public HashMap processEnrollment( + CertEnrollmentRequest data, + HttpServletRequest request, + AuthorityID aid) + throws EBaseException { try { if (CMS.debugOn()) { @@ -146,6 +151,10 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + if (aid != null) + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aid.toString()); + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java index 2826f477e358a5e16657e985d7f13079cdb14a33..8558ec23f6489407dc5f41951a363d22548851c0 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java @@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EDeferException; @@ -327,6 +331,31 @@ public class RequestProcessor extends CertProcessor { } /** + * Ensure validity of AuthorityID and that CA exists and is enabled. + */ + private void ensureCAEnabled(String aidString) throws EBaseException { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + // this shouldn't happen because request was already accepted + throw new BadRequestDataException("Invalid AuthorityID in request data"); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem("ca"); + if (ca == null) + // this shouldn't happen + throw new CANotFoundException("Could not get host authority"); // shouldn't happen + ca = ca.getCA(aid); + if (ca == null) + // this shouldn't happen because request was already accepted + throw new CANotFoundException("Unknown CA: " + aidString); + if (!ca.getAuthorityEnabled()) + // authority was disabled after request was accepted + throw new CADisabledException("CA '" + aidString + "' is disabled"); + } + + /** * Approve request *

* @@ -346,11 +375,16 @@ public class RequestProcessor extends CertProcessor { * occurred */ private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale) - throws EProfileException { + throws EBaseException { String auditMessage = null; String auditSubjectID = auditSubjectID(); String auditRequesterID = auditRequesterID(req); + // ensure target CA is enabled + String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ensureCAEnabled(aidString); + try { profile.execute(req); req.setRequestStatus(RequestStatus.COMPLETE); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif new file mode 100644 index 0000000000000000000000000000000000000000..7d261f18fbc9475983bf93b1cddcc184d7f9d178 --- /dev/null +++ b/base/server/share/conf/schema-authority.ldif @@ -0,0 +1,8 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..a15601ae7a362635bc398b92b9bfda1c72f0dfc8 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,16 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 -------------- next part -------------- From 214d44ddc0985e3d49febd99cf66a2290455b8ca Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 10 Jun 2015 03:02:35 -0400 Subject: [PATCH] Lightweight CAs: add ca-authority CLI Add CLI commands for creating, listing and showing lightweight CAs. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../certsrv/authority/AuthorityClient.java | 62 +++++++++++++++ .../src/com/netscape/certsrv/ca/CAClient.java | 3 +- .../netscape/cmstools/authority/AuthorityCLI.java | 52 +++++++++++++ .../cmstools/authority/AuthorityCreateCLI.java | 89 ++++++++++++++++++++++ .../cmstools/authority/AuthorityDisableCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityEnableCLI.java | 56 ++++++++++++++ .../cmstools/authority/AuthorityFindCLI.java | 62 +++++++++++++++ .../cmstools/authority/AuthorityShowCLI.java | 78 +++++++++++++++++++ .../src/com/netscape/cmstools/cli/CACLI.java | 2 + 9 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityClient.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java new file mode 100644 index 0000000000000000000000000000000000000000..86de3352e2424211125c146edf759481448a2694 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -0,0 +1,62 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- +package com.netscape.certsrv.authority; + +import java.net.URISyntaxException; +import java.util.List; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.Response; + +import com.netscape.certsrv.client.Client; +import com.netscape.certsrv.client.PKIClient; + +/** + * @author Fraser Tweedale + */ +public class AuthorityClient extends Client { + + public AuthorityResource proxy; + + public AuthorityClient(PKIClient client, String subsystem) throws URISyntaxException { + super(client, subsystem, "authority"); + proxy = createProxy(AuthorityResource.class); + } + + public List listCAs() { + Response response = proxy.listCAs(); + GenericType> type = new GenericType>() {}; + return client.getEntity(response, type); + } + + public AuthorityData getCA(String caIDString) { + Response response = proxy.getCA(caIDString); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData createCA(AuthorityData data) { + Response response = proxy.createCA(data); + return client.getEntity(response, AuthorityData.class); + } + + public AuthorityData modifyCA(AuthorityData data) { + Response response = proxy.modifyCA(data.getID(), data); + return client.getEntity(response, AuthorityData.class); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CAClient.java b/base/common/src/com/netscape/certsrv/ca/CAClient.java index e1a0a8c02f8a840acbdea924c164020b88557fc4..1fbd2a0b286ed09854373846510c392c5202307a 100644 --- a/base/common/src/com/netscape/certsrv/ca/CAClient.java +++ b/base/common/src/com/netscape/certsrv/ca/CAClient.java @@ -26,6 +26,7 @@ import com.netscape.certsrv.group.GroupClient; import com.netscape.certsrv.profile.ProfileClient; import com.netscape.certsrv.selftests.SelfTestClient; import com.netscape.certsrv.user.UserClient; +import com.netscape.certsrv.authority.AuthorityClient; public class CAClient extends SubsystemClient { @@ -35,7 +36,7 @@ public class CAClient extends SubsystemClient { } public void init() throws URISyntaxException { - + addClient(new AuthorityClient(client, name)); addClient(new CertClient(client, name)); addClient(new GroupClient(client, name)); addClient(new ProfileClient(client, name)); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..99d38ad1b989e171079df78ddd8b2774817ccb33 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -0,0 +1,52 @@ +package com.netscape.cmstools.authority; + +import com.netscape.certsrv.authority.AuthorityClient; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityCLI extends CLI { + + public AuthorityClient authorityClient; + + public AuthorityCLI(CLI parent) { + super("authority", "CA management commands", parent); + + addModule(new AuthorityFindCLI(this)); + addModule(new AuthorityShowCLI(this)); + addModule(new AuthorityCreateCLI(this)); + addModule(new AuthorityDisableCLI(this)); + addModule(new AuthorityEnableCLI(this)); + } + + public String getFullName() { + if (parent instanceof MainCLI) { + // do not include MainCLI's name + return name; + } else { + return parent.getFullName() + "-" + name; + } + } + + public void execute(String[] args) throws Exception { + client = parent.getClient(); + authorityClient = new AuthorityClient(client, "ca"); + super.execute(args); + } + + protected static void printAuthorityData(AuthorityData data) { + Boolean isHostAuthority = data.getIsHostAuthority(); + if (isHostAuthority != null && isHostAuthority) + System.out.println(" Host authority: true"); + System.out.println(" Authority DN: " + data.getDN()); + System.out.println(" ID: " + data.getID()); + String parentAID = data.getParentID(); + if (parentAID != null) + System.out.println(" Parent ID: " + data.getParentID()); + System.out.println(" Enabled: " + data.getEnabled()); + String desc = data.getDescription(); + if (desc != null) + System.out.println(" Description: " + desc); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..d1688fbd1933a567940164d86ac726df1489f7d2 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -0,0 +1,89 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityCreateCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityCreateCLI(AuthorityCLI authorityCLI) { + super("create", "Create CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option(null, "parent", true, "ID of parent CA"); + optParent.setArgName("id"); + options.addOption(optParent); + + Option optDesc = new Option(null, "desc", true, "Optional description"); + optDesc.setArgName("string"); + options.addOption(optDesc); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No DN specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String parentAIDString = null; + if (cmd.hasOption("parent")) { + parentAIDString = cmd.getOptionValue("parent"); + try { + new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + System.err.println("Bad CA ID: " + parentAIDString); + printHelp(); + System.exit(-1); + } + } else { + System.err.println("Must specify parent authority"); + printHelp(); + System.exit(-1); + } + + String desc = null; + if (cmd.hasOption("desc")) + desc = cmd.getOptionValue("desc"); + + String dn = cmdArgs[0]; + AuthorityData data = new AuthorityData( + null, dn, null, parentAIDString, true /* enabled */, desc); + AuthorityData newData = authorityCLI.authorityClient.createCA(data); + AuthorityCLI.printAuthorityData(newData); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..fc4cbf30be947233a9289089bb25bef70d532bb6 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityDisableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityDisableCLI(AuthorityCLI authorityCLI) { + super("disable", "Disable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, null, cmdArgs[0], null, false, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..f6fdab12f527975d2d0688b65968bfb992b5a97a --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java @@ -0,0 +1,56 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityEnableCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityEnableCLI(AuthorityCLI authorityCLI) { + super("enable", "Enable CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = new AuthorityData( + null, null, cmdArgs[0], null, true, null); + data = authorityCLI.authorityClient.modifyCA(data); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..c1aa99fc627e8e0ccfd1f12a23610a13dd5cfbbb --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityFindCLI.java @@ -0,0 +1,62 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityFindCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityFindCLI(AuthorityCLI authorityCLI) { + super("find", "Find CAs", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName(), options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + @SuppressWarnings("unused") + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + List datas = authorityCLI.authorityClient.listCAs(); + + MainCLI.printMessage(datas.size() + " entries matched"); + if (datas.size() == 0) return; + + boolean first = true; + for (AuthorityData data : datas) { + if (first) + first = false; + else + System.out.println(); + AuthorityCLI.printAuthorityData(data); + } + + MainCLI.printMessage("Number of entries returned " + datas.size()); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..c9566024872fdfa781f956ed7acfd494367a7d32 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityShowCLI.java @@ -0,0 +1,78 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.cmstools.cli.CLI; + +public class AuthorityShowCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityShowCLI(AuthorityCLI authorityCLI) { + super("show", "Show CAs", authorityCLI); + this.authorityCLI = authorityCLI; + + Option optParent = new Option( + null, "host-authority", false, "Show host authority"); + options.addOption(optParent); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + String caIDString = null; + if (cmdArgs.length > 1) { + System.err.println("Error: too many arguments."); + printHelp(); + System.exit(-1); + } else if (cmdArgs.length == 1) { + caIDString = cmdArgs[0]; + } + + if (cmd.hasOption("host-authority")) { + if (caIDString != null) { + System.err.println("Error: authority ID and --host-authority are mutually exclusive."); + printHelp(); + System.exit(-1); + } + caIDString = AuthorityResource.HOST_AUTHORITY; + } + + if (caIDString == null) { + System.err.println("Error: No ID specified."); + printHelp(); + System.exit(-1); + } + + AuthorityData data = authorityCLI.authorityClient.getCA(caIDString); + AuthorityCLI.printAuthorityData(data); + } + +} diff --git a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java index 17fb4866f38f05f7ead02b6145ef7d09140a90c5..5c41f00c2eb6e393cc95d3b174cb14eefc7307ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/CACLI.java @@ -20,6 +20,7 @@ package com.netscape.cmstools.cli; import com.netscape.certsrv.ca.CAClient; import com.netscape.certsrv.client.Client; +import com.netscape.cmstools.authority.AuthorityCLI; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.group.GroupCLI; import com.netscape.cmstools.profile.ProfileCLI; @@ -37,6 +38,7 @@ public class CACLI extends SubsystemCLI { public CACLI(CLI parent) { super("ca", "CA management commands", parent); + addModule(new AuthorityCLI(this)); addModule(new CertCLI(this)); addModule(new GroupCLI(this)); addModule(new KRAConnectorCLI(this)); -- 2.4.3 -------------- next part -------------- From c3ce826f529094f35fecd5cea70ebe3670389f92 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 1 Sep 2015 09:57:42 -0400 Subject: [PATCH] Lightweight CAs: REST cert request param to specify authority Add the optional "ca" query parameter for REST cert request submission. Also update the ca-cert-request-submit CLI command with an option to provide an AuthorityID. Part of: https://fedorahosted.org/pki/ticket/1213 --- .../src/com/netscape/cms/servlet/test/CATest.java | 4 +- .../server/ca/rest/CertRequestService.java | 41 ++++++++++++++++++-- .../src/com/netscape/certsrv/cert/CertClient.java | 16 +++++++- .../netscape/certsrv/cert/CertRequestResource.java | 5 ++- .../cmstools/cert/CertRequestSubmitCLI.java | 44 +++++++++++++++++++++- .../cmstools/client/ClientCertRequestCLI.java | 2 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 12 ++++-- 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java index 15023cad939abb11927abc64fe5916e04cb65661..92c776d17a3993fdd50ecbdfd3e5e3b5692888f2 100644 --- a/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java +++ b/base/ca/functional/src/com/netscape/cms/servlet/test/CATest.java @@ -288,7 +288,7 @@ public class CATest { private static void enrollAndApproveCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); @@ -308,7 +308,7 @@ public class CATest { private static void enrollCertRequest(CertClient client, CertEnrollmentRequest data) { CertRequestInfos reqInfo = null; try { - reqInfo = client.enrollRequest(data); + reqInfo = client.enrollRequest(data, null, null); } catch (Exception e) { e.printStackTrace(); log(e.toString()); diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 1da1ce1713541e52164e9e8fbcbf39ca2332540d..7cb4ff71e18b6e29bf55c11dc99bbfb9b83dd60f 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -18,6 +18,7 @@ package org.dogtagpki.server.ca.rest; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Enumeration; @@ -41,8 +42,11 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -63,6 +67,7 @@ import com.netscape.certsrv.request.RequestNotFoundException; import com.netscape.cms.servlet.base.PKIService; import com.netscape.cms.servlet.cert.CertRequestDAO; import com.netscape.cmsutil.ldap.LDAPUtil; +import netscape.security.x509.X500Name; /** * @author alee @@ -115,13 +120,43 @@ public class CertRequestService extends PKIService implements CertRequestResourc } @Override - public Response enrollCert(CertEnrollmentRequest data) { - + public Response enrollCert(CertEnrollmentRequest data, String aidString, String adnString) { if (data == null) { CMS.debug("enrollCert: data is null"); throw new BadRequestException("Unable to create enrollment reequest: Invalid input data"); } + if (aidString != null && adnString != null) + throw new BadRequestException("Cannot provide both issuer-id and issuer-dn"); + + AuthorityID aid = null; + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (aidString != null) { + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("invalid AuthorityID: " + aidString); + } + ca = ca.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + aidString); + } + if (adnString != null) { + X500Name adn = null; + try { + adn = new X500Name(adnString); + } catch (IOException e) { + throw new BadRequestException("invalid DN: " + adnString); + } + ca = ca.getCA(adn); + if (ca == null) + throw new ResourceNotFoundException("CA not found: " + adnString); + aid = ca.getAuthorityID(); + } + if (!ca.getAuthorityEnabled()) + throw new ConflictingOperationException("CA not enabled: " + aid.toString()); + data.setRemoteHost(servletRequest.getRemoteHost()); data.setRemoteAddr(servletRequest.getRemoteAddr()); @@ -129,7 +164,7 @@ public class CertRequestService extends PKIService implements CertRequestResourc CertRequestInfos infos; try { - infos = dao.submitRequest(data, servletRequest, uriInfo, getLocale(headers)); + infos = dao.submitRequest(aid, data, servletRequest, uriInfo, getLocale(headers)); } catch (EAuthException e) { CMS.debug("enrollCert: authentication failed: " + e); throw new UnauthorizedException(e.toString()); diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java index 42b04b7021f0063894c340c177915d799b621ddd..1d4ccd2cf7e83a8ed3b33253b1416110d5504125 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertClient.java +++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java @@ -17,16 +17,19 @@ //--- END COPYRIGHT BLOCK --- package com.netscape.certsrv.cert; +import java.io.IOException; import java.net.URISyntaxException; import javax.ws.rs.core.Response; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.client.Client; import com.netscape.certsrv.client.PKIClient; import com.netscape.certsrv.client.SubsystemClient; import com.netscape.certsrv.dbs.certdb.CertId; import com.netscape.certsrv.profile.ProfileDataInfos; import com.netscape.certsrv.request.RequestId; +import netscape.security.x509.X500Name; /** * @author Endi S. Dewata @@ -85,8 +88,17 @@ public class CertClient extends Client { return client.getEntity(response, CertRequestInfo.class); } - public CertRequestInfos enrollRequest(CertEnrollmentRequest data) { - Response response = certRequestClient.enrollCert(data); + public CertRequestInfos enrollRequest( + CertEnrollmentRequest data, AuthorityID aid, X500Name adn) { + String aidString = aid != null ? aid.toString() : null; + String adnString = null; + if (adn != null) { + try { + adnString = adn.toLdapDNString(); + } catch (IOException e) { + } + } + Response response = certRequestClient.enrollCert(data, aidString, adnString); return client.getEntity(response, CertRequestInfos.class); } diff --git a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java index 7f08b4af392e3e56419abdad7cb66bd191688222..493f6f53a5a5e30804532305b199d44a66eecd24 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java +++ b/base/common/src/com/netscape/certsrv/cert/CertRequestResource.java @@ -37,7 +37,10 @@ public interface CertRequestResource { @POST @Path("certrequests") @ClientResponseType(entityType=CertRequestInfos.class) - public Response enrollCert(CertEnrollmentRequest data); + public Response enrollCert( + CertEnrollmentRequest data, + @QueryParam("issuer-id") String caIDString, + @QueryParam("issuer-dn") String caDNString); /** * Used to retrieve cert request info for a specific request diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java index 608490bb73d7df482d87e67e9c15322ddc2e5f5a..9611159681b65844c1fc32937ca0a65c2c31980d 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -2,18 +2,22 @@ package com.netscape.cmstools.cert; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Arrays; import java.util.Scanner; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; +import netscape.security.x509.X500Name; public class CertRequestSubmitCLI extends CLI { @@ -22,6 +26,14 @@ public class CertRequestSubmitCLI extends CLI { public CertRequestSubmitCLI(CertCLI certCLI) { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; + + Option optAID = new Option(null, "issuer-id", true, "Authority ID (host authority if omitted)"); + optAID.setArgName("id"); + options.addOption(optAID); + + Option optADN = new Option(null, "issuer-dn", true, "Authority DN (host authority if omitted)"); + optADN.setArgName("dn"); + options.addOption(optADN); } public void printHelp() { @@ -55,9 +67,39 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } + AuthorityID aid = null; + if (cmd.hasOption("issuer-id")) { + String aidString = cmd.getOptionValue("issuer-id"); + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + System.err.println("Bad AuthorityID: " + aidString); + printHelp(); + System.exit(-1); + } + } + + X500Name adn = null; + if (cmd.hasOption("issuer-dn")) { + String adnString = cmd.getOptionValue("issuer-dn"); + try { + adn = new X500Name(adnString); + } catch (IOException e) { + System.err.println("Bad DN: " + adnString); + printHelp(); + System.exit(-1); + } + } + + if (aid != null && adn != null) { + System.err.println("--issuer-id and --issuer-dn options are mutually exclusive"); + printHelp(); + System.exit(-1); + } + try { CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd); + CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid, adn); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(cri); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index e6bd0d98120295ef8e798925f4e9aceb3a0d43f6..db71c8a0f7db4644290efb766178b76668c22377 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -283,7 +283,7 @@ public class ClientCertRequestCLI extends CLI { System.out.println("Sending certificate request."); } - CertRequestInfos infos = certClient.enrollRequest(request); + CertRequestInfos infos = certClient.enrollRequest(request, null, null); MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index 27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b..a2e4b583d318ac8412361850d91233b77a447e13 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -30,6 +30,7 @@ import javax.ws.rs.core.UriInfo; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; @@ -164,8 +165,13 @@ public class CertRequestDAO extends CMSRequestDAO { * @throws EBaseException * @throws ServletException */ - public CertRequestInfos submitRequest(CertEnrollmentRequest data, HttpServletRequest request, UriInfo uriInfo, - Locale locale) throws EBaseException { + public CertRequestInfos submitRequest( + AuthorityID aid, + CertEnrollmentRequest data, + HttpServletRequest request, + UriInfo uriInfo, + Locale locale) + throws EBaseException { CertRequestInfos ret = new CertRequestInfos(); @@ -175,7 +181,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request, null); + results = processor.processEnrollment(data, request, aid); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); -- 2.4.3 -------------- next part -------------- From 99722211582103bd5185b21c5fde3cab44783c84 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Fri, 25 Sep 2015 09:00:29 -0400 Subject: [PATCH] [EXPERIMENTAL] Lightweight CAs: find cert by issuer and serial --- .../src/com/netscape/ca/CertificateAuthority.java | 56 ++++++++++++++-------- base/ca/src/com/netscape/ca/SigningUnit.java | 28 +++++++++-- base/server/share/conf/schema-authority.ldif | 7 +-- base/server/share/conf/schema.ldif | 7 +-- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index e9ac6a9712f58e8024623956d3a6dce6a718a9db..b07276d0929e22d67531a3eac2ec62438626e220 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -167,6 +167,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori protected AuthorityID authorityParentID = null; protected String authorityDescription = null; protected boolean authorityEnabled = true; + protected X500Name authorityIssuerDN = null; + protected BigInteger authoritySerial = null; protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; @@ -278,7 +280,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori AuthorityID parentAID, String signingKeyNickname, String authorityDescription, - boolean authorityEnabled + boolean authorityEnabled, + X500Name issuerDN, + BigInteger serial ) throws EBaseException { setId(hostCA.getId()); this.hostCA = hostCA; @@ -286,6 +290,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori this.authorityParentID = parentAID; this.authorityDescription = authorityDescription; this.authorityEnabled = authorityEnabled; + this.authorityIssuerDN = issuerDN; + this.authoritySerial = serial; mNickname = signingKeyNickname; init(hostCA.mOwner, hostCA.mConfig); } @@ -1333,7 +1339,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg, mNickname); + mSigningUnit.init( + this, caSigningCfg, mNickname, + authorityIssuerDN, authoritySerial); CMS.debug("CA signing unit inited"); // for identrus @@ -1955,9 +1963,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); - LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); + LDAPAttribute issuerDNAttr = entry.getAttribute("authorityIssuerDN"); + LDAPAttribute serialAttr = entry.getAttribute("authoritySerial"); - if (aidAttr == null || nickAttr == null || dnAttr == null) + if (aidAttr == null || nickAttr == null || dnAttr == null + || issuerDNAttr == null || serialAttr == null) throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN()); AuthorityID aid = new AuthorityID((String) @@ -1983,14 +1993,18 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori continue; } - @SuppressWarnings("unused") - X500Name parentDN = null; - if (parentDNAttr != null) { - try { - parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement()); - } catch (IOException e) { - throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); - } + X500Name issuerDN = null; + try { + issuerDN = new X500Name((String) issuerDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityIssuerDN: " + entry.getDN()); + } + + BigInteger serial = null; + try { + serial = new BigInteger((String) serialAttr.getStringValues().nextElement()); + } catch (NumberFormatException e) { + throw new ECAException("Malformed authority object; invalid serial: " + entry.getDN()); } String keyNick = (String) nickAttr.getStringValues().nextElement(); @@ -2008,7 +2022,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } CertificateAuthority ca = new CertificateAuthority( - this, aid, parentAID, keyNick, desc, enabled); + this, aid, parentAID, keyNick, desc, enabled, + issuerDN, serial); caMap.put(aid, ca); } } catch (LDAPException e) { @@ -2423,9 +2438,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori String dn = "cn=" + aidString + ",ou=authorities,ou=" + getId() + "," + getDBSubsystem().getBaseDN(); CMS.debug("createSubCA: DN = " + dn); - String parentDNString = null; + String issuerDNString = null; try { - parentDNString = mName.toLdapDNString(); + issuerDNString = mName.toLdapDNString(); } catch (IOException e) { throw new EBaseException("Failed to convert issuer DN to string: " + e); } @@ -2437,7 +2452,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori new LDAPAttribute("authorityKeyNickname", nickname), new LDAPAttribute("authorityEnabled", "TRUE"), new LDAPAttribute("authorityDN", subjectDN), - new LDAPAttribute("authorityParentDN", parentDNString) + new LDAPAttribute("authorityParentDN", issuerDNString) }; LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); if (this.authorityID != null) @@ -2452,6 +2467,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); LDAPConnection conn = dbFactory.getConn(); + X509CertImpl cert = null; try { // add entry to database conn.add(ldapEntry); @@ -2474,8 +2490,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori cs.put(".profile", "caCert.profile"); cs.put(".dn", subjectDN); cs.put(".keyalgorithm", algName); - X509CertImpl cert = - CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + cert = CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); // Add certificate to nssdb cryptoManager.importCertPackage(cert.getEncoded(), nickname); @@ -2492,7 +2507,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } return new CertificateAuthority( - hostCA, aid, this.authorityID, nickname, description, true); + hostCA, aid, this.authorityID, nickname, description, true, + mName /* this CA is the issuer */, cert.getSerialNumber()); } /** @@ -2530,6 +2546,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori new LDAPAttribute("authorityKeyNickname", getNickname()), new LDAPAttribute("authorityEnabled", "TRUE"), new LDAPAttribute("authorityDN", dnString), + new LDAPAttribute("authorityIssuerDN", mIssuerObj.toString()), + new LDAPAttribute("authoritySerial", mCaCert.getSerialNumber().toString()), new LDAPAttribute("description", desc) }; LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 0410bd2909bc71ca7d7443b3c9db0b085d62eaea..9fd3687eb01553fbc1701772da75c8ada186a175 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -17,6 +17,8 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.ca; +import java.io.IOException; +import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -25,9 +27,11 @@ import java.security.SignatureException; import netscape.security.x509.AlgorithmId; import netscape.security.x509.X509CertImpl; import netscape.security.x509.X509Key; +import netscape.security.x509.X500Name; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.NoSuchTokenException; +import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.ObjectNotFoundException; import org.mozilla.jss.crypto.PrivateKey; @@ -126,10 +130,11 @@ public final class SigningUnit implements ISigningUnit { public void init(ISubsystem owner, IConfigStore config) throws EBaseException { - init(owner, config, null); + init(owner, config, null, null, null); } - public void init(ISubsystem owner, IConfigStore config, String nickname) + public void init(ISubsystem owner, IConfigStore config, + String nickname, X500Name issuerDN, BigInteger serial) throws EBaseException { mOwner = owner; mConfig = config; @@ -165,8 +170,23 @@ public final class SigningUnit implements ISigningUnit { mToken.login(cb); // ONE_TIME by default. - mCert = mManager.findCertByNickname(mNickname); - CMS.debug("Found cert by nickname: '" + mNickname + "' with serial number: " + mCert.getSerialNumber()); + byte[] derIssuer = null; + if (issuerDN != null) { + try { + derIssuer = issuerDN.getEncoded(); + } catch (IOException e) { + CMS.debug("Error getting DER encoding of issuer DN; falling back to nickname"); + } + } + + if (derIssuer != null && serial != null) { + mCert = mManager.findCertByIssuerAndSerialNumber( + derIssuer, new INTEGER(serial)); + CMS.debug("Found cert by issuer: '" + issuerDN + "' and serial number: " + serial); + } else { + mCert = mManager.findCertByNickname(mNickname); + CMS.debug("Found cert by nickname: '" + mNickname + "' with serial number: " + mCert.getSerialNumber()); + } mCertImpl = new X509CertImpl(mCert.getEncoded()); CMS.debug("converted to x509CertImpl"); diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif index 7d261f18fbc9475983bf93b1cddcc184d7f9d178..fb831c9b40cb79fbc17867c0e2904d288f205207 100644 --- a/base/server/share/conf/schema-authority.ldif +++ b/base/server/share/conf/schema-authority.ldif @@ -3,6 +3,7 @@ attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) -attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) -attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) -objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Subject DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityIssuerDN-oid NAME 'authorityIssuerDN' DESC 'Authority Issuer DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority Serial Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN $ authorityIssuerDN $ authoritySerial ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index a15601ae7a362635bc398b92b9bfda1c72f0dfc8..cbba4bfcd7798fec6bcd72200b7ec6b1e71d946a 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -675,8 +675,9 @@ attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) -attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) -attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Subject DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityIssuerDN-oid NAME 'authorityParentDN' DESC 'Authority Issuer DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority Serial Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'user defined' ) - add: objectClasses -objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN $ authorityIssuerDN $ authoritySerial ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) -- 2.4.3 From alee at redhat.com Fri Sep 25 17:55:38 2015 From: alee at redhat.com (Ade Lee) Date: Fri, 25 Sep 2015 13:55:38 -0400 Subject: [Pki-devel] [PATCH] 271 python client code for subcas Message-ID: <1443203738.8416.8.camel@redhat.com> This may need a couple of tweaks when Fraser's initial commit goes in - maybe aid -> id in the Java json code, but should be pretty much correct at this point. This supersedes a previous patch on this. Please review, Ade -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-vakwetu-0271-Python-client-for-subcas.patch Type: text/x-patch Size: 32284 bytes Desc: not available URL: From alee at redhat.com Fri Sep 25 17:56:40 2015 From: alee at redhat.com (Ade Lee) Date: Fri, 25 Sep 2015 13:56:40 -0400 Subject: [Pki-devel] [PATCH] 270 - features feature .. Message-ID: <1443203800.8416.9.camel@redhat.com> Just the server and python client bits. Ade -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-vakwetu-0270-Added-Features-REST-API-resource.patch Type: text/x-patch Size: 19523 bytes Desc: not available URL: From alee at redhat.com Fri Sep 25 18:41:30 2015 From: alee at redhat.com (Ade Lee) Date: Fri, 25 Sep 2015 14:41:30 -0400 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> <560486AF.2010508@redhat.com> <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> Message-ID: <1443206490.8416.11.camel@redhat.com> I'm inclined to not allow reuse of DNs until we can figure this out. If we can't, then we need to add the DELETE functionality. Ade On Fri, 2015-09-25 at 23:30 +1000, Fraser Tweedale wrote: > Latest patches attached. Most issued addressed; see inline for > comments and ticket URLs for deferred items. > > There is a problem with allowing authority DNs to be reused - when > adding the cert to the NSSDB, despite what nickname you tell it to > you, it will put the cert under the nickname of the existing cert > with that subject DN. Thus when you go to find the cert by > nickname, it cannot locate it. Failure ensues. This is possibly a > bug in NSS (it's certainly surprising), but I need more time to > analyse it. > > I've attached an experimental (builds, but as yet completely > untested with high chance of brokens) patch on top of my current > patches that switches to looking up the cert by issuer DN and > serial. I need to consider implications of this switch including > for renewal in replicated environments. It might not be the right > approach but afaict it is the only other way CryptoManager gives you > to look up a cert. > > Ideally we discover that NSS/JSS is doing the wrong thing with what > it is doing with nicknames and we can get a fix there are move on > with life. > > Anyhow, other comments inline. > > Thanks, > Fraser > > On Thu, Sep 24, 2015 at 06:26:39PM -0500, Endi Sukma Dewata wrote: > > Some comments: > > > > 1. Right now the create & modify operations over non-secure URL > > will fail: > > > > $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin > > ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf > > -dcc34367e836 > > ForbiddenException: No user principal provided. > > > > It works with the secure URL: > > > > $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u > > caadmin > > -w Secret123 ca-authority-create o=test --parent > > 85a2c5c2-869d-467c-9adf-dcc34367e836 > > Authority DN: O=test > > ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4 > > Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836 > > Enabled: true > > > > This can be fixed by adding into the web.xml > > and > > registering it in auth-method.properties. > > > Thanks for this explanation. Worked a treat! > > > 2. The "Parent DN" field in the output above should show the DN of > > the > > parent authority instead of the ID. We probably should show both > > Parent DN > > and Parent ID. > > > Fixed the label, filed ticket for including the Parent/Issuer DN: > https://fedorahosted.org/pki/ticket/1618 > > > 3. Per discussion with alee, we need a way to find the host/main CA > > using > > something like: > > > > $ pki ca-authority-show --host-authority > > > Done. > > > 4. I think we also need a way to translate a DN into ID: > > > > $ pki ca-authority-show --dn > > > Filed ticket: https://fedorahosted.org/pki/ticket/1617 > > > 5. Also per discussion with alee, the authority DN should be unique > > only > > among active CAs. So you should be able to create a CA, disable it, > > then > > create another one with the same DN. If you try to enable the old > > CA it > > should fail. This can be implemented later. > > > Per discussion above, implemented, but breaks your CA if you try it! > > > 6. In AuthorityData.java the @XmlRootElement probably should be > > changed to > > "authority" for consistency. Also the following fields can be > > renamed > > because the "a" is redundant: > > * aid -> id > > * parentAID -> parentID > > I think the XML output will look better that way. > > > Done. > > > 7. The method description in ISigningUnit.java doesn't match the > > method name > > (public vs. private). > > > Fixed; well spotted. > > > I think these are not difficult to fix, and once fixed it should be > > sufficient to push as initial implementation, so consider this a > > conditional > > ACK (unless alee has other comments). Item #5 (or #4 too) can be > > implemented > > later. > > > > I also created this page to document the CLI: > > http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI > > Feel free to expand it further. > > > Thanks a bunch; I will review this Monday. This also reminds me to > spend some time updating the design pages as well - there have been > many changes! > From jmagne at redhat.com Fri Sep 25 21:48:15 2015 From: jmagne at redhat.com (John Magne) Date: Fri, 25 Sep 2015 17:48:15 -0400 (EDT) Subject: [Pki-devel] [pki-devel][PATCH] 0052-KRA-key-archival-recovery-via-cli-should-honor-encry.patch In-Reply-To: <1352286189.55776144.1442945569906.JavaMail.zimbra@redhat.com> References: <1352286189.55776144.1442945569906.JavaMail.zimbra@redhat.com> Message-ID: <2053932789.58083521.1443217695684.JavaMail.zimbra@redhat.com> Approved by alee: Pushed to master. Closing ticket #1597. Commit: a5a50e95a691587e22335018538b4f578dfee6d1 ----- Original Message ----- > From: "John Magne" > To: "pki-devel" > Sent: Tuesday, September 22, 2015 11:12:49 AM > Subject: [pki-devel][PATCH] 0052-KRA-key-archival-recovery-via-cli-should-honor-encry.patch > > [PATCH] KRA: key archival/recovery via cli - should honor > encryption/decryption flags. > > Ticket # 1597 > > Currently, KRA allows sites to opt for doing encryption/decryption instead of > wrapping/unwrapping for key archival and recovery. > > The new cli code was later added without such support. We should honor the > same flags when cli is called to do key archival and recovery. > > This feature was due to a specific customer request. Here is what is now > supported: > > 1. When the pki cli tool is used to recover a asymmetric private key, support > is there to do so with encrypt / decrypt. > 2. The passphrase and generic data facility already uses encrypt / decrypt so > nothing here was needed. Calling it out since this will possibly be a > customer issue. > > 3. While under the hood, it made sense to add this functionality to the > Symmetric key archival and recovery operations. > 4. All tests in DRMTest.java worked successfully when the kra was configured > to support this feature and configured to not observe this feature. > > What is missing: > > We have since added a method to do a server side key generation of a > asymmetric key pair in the kra and also archive it there at the same time. > In order to do encrypt / decrypt in this case we need to extract the key > contents out of a key object that is used to generate this key. It proved > problematic to extract said key. This should be ok since the customer only > needs to recover an asymmetric key in their test cases. We could look into > doing this later if a pressing need arises. > From ftweedal at redhat.com Sat Sep 26 02:25:44 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Sat, 26 Sep 2015 12:25:44 +1000 Subject: [Pki-devel] [PATCH] 270 - features feature .. In-Reply-To: <1443203800.8416.9.camel@redhat.com> References: <1443203800.8416.9.camel@redhat.com> Message-ID: <20150926022544.GP16937@dhcp-40-8.bne.redhat.com> On Fri, Sep 25, 2015 at 01:56:40PM -0400, Ade Lee wrote: > Just the server and python client bits. > > Ade > I think that we should store the config in LDAP so that a change on one replica is effected on clones. Obviously this is a fair bit more work I am OK with using CS.cfg initially but we should have this discussion. I will conduct a review of this patch and the Python lightweight-CAs patch on Monday. Cheers, Fraser From ftweedal at redhat.com Sat Sep 26 02:30:02 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Sat, 26 Sep 2015 12:30:02 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <1443206490.8416.11.camel@redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> <560486AF.2010508@redhat.com> <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> <1443206490.8416.11.camel@redhat.com> Message-ID: <20150926023002.GQ16937@dhcp-40-8.bne.redhat.com> On Fri, Sep 25, 2015 at 02:41:30PM -0400, Ade Lee wrote: > I'm inclined to not allow reuse of DNs until we can figure this out. > If we can't, then we need to add the DELETE functionality. > I'll reinstate the previous restriction and merge the patches. Investigation of the issue and possible solutions to continue next week. For now, during testing if you really need to reuse a DN you'll have to ldapdelete the entry and delete the corresponding certificate from the NSSDB. Cheers, Fraser > Ade > > On Fri, 2015-09-25 at 23:30 +1000, Fraser Tweedale wrote: > > Latest patches attached. Most issued addressed; see inline for > > comments and ticket URLs for deferred items. > > > > There is a problem with allowing authority DNs to be reused - when > > adding the cert to the NSSDB, despite what nickname you tell it to > > you, it will put the cert under the nickname of the existing cert > > with that subject DN. Thus when you go to find the cert by > > nickname, it cannot locate it. Failure ensues. This is possibly a > > bug in NSS (it's certainly surprising), but I need more time to > > analyse it. > > > > I've attached an experimental (builds, but as yet completely > > untested with high chance of brokens) patch on top of my current > > patches that switches to looking up the cert by issuer DN and > > serial. I need to consider implications of this switch including > > for renewal in replicated environments. It might not be the right > > approach but afaict it is the only other way CryptoManager gives you > > to look up a cert. > > > > Ideally we discover that NSS/JSS is doing the wrong thing with what > > it is doing with nicknames and we can get a fix there are move on > > with life. > > > > Anyhow, other comments inline. > > > > Thanks, > > Fraser > > > > On Thu, Sep 24, 2015 at 06:26:39PM -0500, Endi Sukma Dewata wrote: > > > Some comments: > > > > > > 1. Right now the create & modify operations over non-secure URL > > > will fail: > > > > > > $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin > > > ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf > > > -dcc34367e836 > > > ForbiddenException: No user principal provided. > > > > > > It works with the secure URL: > > > > > > $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u > > > caadmin > > > -w Secret123 ca-authority-create o=test --parent > > > 85a2c5c2-869d-467c-9adf-dcc34367e836 > > > Authority DN: O=test > > > ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4 > > > Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836 > > > Enabled: true > > > > > > This can be fixed by adding into the web.xml > > > and > > > registering it in auth-method.properties. > > > > > Thanks for this explanation. Worked a treat! > > > > > 2. The "Parent DN" field in the output above should show the DN of > > > the > > > parent authority instead of the ID. We probably should show both > > > Parent DN > > > and Parent ID. > > > > > Fixed the label, filed ticket for including the Parent/Issuer DN: > > https://fedorahosted.org/pki/ticket/1618 > > > > > 3. Per discussion with alee, we need a way to find the host/main CA > > > using > > > something like: > > > > > > $ pki ca-authority-show --host-authority > > > > > Done. > > > > > 4. I think we also need a way to translate a DN into ID: > > > > > > $ pki ca-authority-show --dn > > > > > Filed ticket: https://fedorahosted.org/pki/ticket/1617 > > > > > 5. Also per discussion with alee, the authority DN should be unique > > > only > > > among active CAs. So you should be able to create a CA, disable it, > > > then > > > create another one with the same DN. If you try to enable the old > > > CA it > > > should fail. This can be implemented later. > > > > > Per discussion above, implemented, but breaks your CA if you try it! > > > > > 6. In AuthorityData.java the @XmlRootElement probably should be > > > changed to > > > "authority" for consistency. Also the following fields can be > > > renamed > > > because the "a" is redundant: > > > * aid -> id > > > * parentAID -> parentID > > > I think the XML output will look better that way. > > > > > Done. > > > > > 7. The method description in ISigningUnit.java doesn't match the > > > method name > > > (public vs. private). > > > > > Fixed; well spotted. > > > > > I think these are not difficult to fix, and once fixed it should be > > > sufficient to push as initial implementation, so consider this a > > > conditional > > > ACK (unless alee has other comments). Item #5 (or #4 too) can be > > > implemented > > > later. > > > > > > I also created this page to document the CLI: > > > http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI > > > Feel free to expand it further. > > > > > Thanks a bunch; I will review this Monday. This also reminds me to > > spend some time updating the design pages as well - there have been > > many changes! > > From ftweedal at redhat.com Sat Sep 26 04:14:56 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Sat, 26 Sep 2015 14:14:56 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150926023002.GQ16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> <560486AF.2010508@redhat.com> <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> <1443206490.8416.11.camel@redhat.com> <20150926023002.GQ16937@dhcp-40-8.bne.redhat.com> Message-ID: <20150926041456.GR16937@dhcp-40-8.bne.redhat.com> On Sat, Sep 26, 2015 at 12:30:02PM +1000, Fraser Tweedale wrote: > On Fri, Sep 25, 2015 at 02:41:30PM -0400, Ade Lee wrote: > > I'm inclined to not allow reuse of DNs until we can figure this out. > > If we can't, then we need to add the DELETE functionality. > > > I'll reinstate the previous restriction and merge the patches. > Pushed to master: 058f1cf Lightweight CAs: REST cert request param to specify authority 5cdad30 Lightweight CAs: add ca-authority CLI 2a9f56d Lightweight CAs: initial support Cheers, Fraser > Investigation of the issue and possible solutions to continue next > week. > > For now, during testing if you really need to reuse a DN you'll have > to ldapdelete the entry and delete the corresponding certificate > from the NSSDB. > > Cheers, > Fraser > > > Ade > > > > On Fri, 2015-09-25 at 23:30 +1000, Fraser Tweedale wrote: > > > Latest patches attached. Most issued addressed; see inline for > > > comments and ticket URLs for deferred items. > > > > > > There is a problem with allowing authority DNs to be reused - when > > > adding the cert to the NSSDB, despite what nickname you tell it to > > > you, it will put the cert under the nickname of the existing cert > > > with that subject DN. Thus when you go to find the cert by > > > nickname, it cannot locate it. Failure ensues. This is possibly a > > > bug in NSS (it's certainly surprising), but I need more time to > > > analyse it. > > > > > > I've attached an experimental (builds, but as yet completely > > > untested with high chance of brokens) patch on top of my current > > > patches that switches to looking up the cert by issuer DN and > > > serial. I need to consider implications of this switch including > > > for renewal in replicated environments. It might not be the right > > > approach but afaict it is the only other way CryptoManager gives you > > > to look up a cert. > > > > > > Ideally we discover that NSS/JSS is doing the wrong thing with what > > > it is doing with nicknames and we can get a fix there are move on > > > with life. > > > > > > Anyhow, other comments inline. > > > > > > Thanks, > > > Fraser > > > > > > On Thu, Sep 24, 2015 at 06:26:39PM -0500, Endi Sukma Dewata wrote: > > > > Some comments: > > > > > > > > 1. Right now the create & modify operations over non-secure URL > > > > will fail: > > > > > > > > $ pki -d ~/.dogtag/pki-tomcat/ca/alias -c Secret123 -n caadmin > > > > ca-authority-create o=test --parent 85a2c5c2-869d-467c-9adf > > > > -dcc34367e836 > > > > ForbiddenException: No user principal provided. > > > > > > > > It works with the secure URL: > > > > > > > > $ pki -U https://$HOSTNAME:8443 -d ~/.dogtag/pki-tomcat/ca/alias -u > > > > caadmin > > > > -w Secret123 ca-authority-create o=test --parent > > > > 85a2c5c2-869d-467c-9adf-dcc34367e836 > > > > Authority DN: O=test > > > > ID: 14004c0f-3531-49c2-ae7a-99f715af7cc4 > > > > Parent DN: 85a2c5c2-869d-467c-9adf-dcc34367e836 > > > > Enabled: true > > > > > > > > This can be fixed by adding into the web.xml > > > > and > > > > registering it in auth-method.properties. > > > > > > > Thanks for this explanation. Worked a treat! > > > > > > > 2. The "Parent DN" field in the output above should show the DN of > > > > the > > > > parent authority instead of the ID. We probably should show both > > > > Parent DN > > > > and Parent ID. > > > > > > > Fixed the label, filed ticket for including the Parent/Issuer DN: > > > https://fedorahosted.org/pki/ticket/1618 > > > > > > > 3. Per discussion with alee, we need a way to find the host/main CA > > > > using > > > > something like: > > > > > > > > $ pki ca-authority-show --host-authority > > > > > > > Done. > > > > > > > 4. I think we also need a way to translate a DN into ID: > > > > > > > > $ pki ca-authority-show --dn > > > > > > > Filed ticket: https://fedorahosted.org/pki/ticket/1617 > > > > > > > 5. Also per discussion with alee, the authority DN should be unique > > > > only > > > > among active CAs. So you should be able to create a CA, disable it, > > > > then > > > > create another one with the same DN. If you try to enable the old > > > > CA it > > > > should fail. This can be implemented later. > > > > > > > Per discussion above, implemented, but breaks your CA if you try it! > > > > > > > 6. In AuthorityData.java the @XmlRootElement probably should be > > > > changed to > > > > "authority" for consistency. Also the following fields can be > > > > renamed > > > > because the "a" is redundant: > > > > * aid -> id > > > > * parentAID -> parentID > > > > I think the XML output will look better that way. > > > > > > > Done. > > > > > > > 7. The method description in ISigningUnit.java doesn't match the > > > > method name > > > > (public vs. private). > > > > > > > Fixed; well spotted. > > > > > > > I think these are not difficult to fix, and once fixed it should be > > > > sufficient to push as initial implementation, so consider this a > > > > conditional > > > > ACK (unless alee has other comments). Item #5 (or #4 too) can be > > > > implemented > > > > later. > > > > > > > > I also created this page to document the CLI: > > > > http://pki.fedoraproject.org/wiki/PKI_CA_Authority_CLI > > > > Feel free to expand it further. > > > > > > > Thanks a bunch; I will review this Monday. This also reminds me to > > > spend some time updating the design pages as well - there have been > > > many changes! > > > > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com > https://www.redhat.com/mailman/listinfo/pki-devel From ftweedal at redhat.com Mon Sep 28 07:59:45 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Mon, 28 Sep 2015 17:59:45 +1000 Subject: [Pki-devel] [PATCH] Lightweight CAs In-Reply-To: <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> References: <20150914072500.GC16937@dhcp-40-8.bne.redhat.com> <55FBEEA9.1090200@redhat.com> <1442601971.6855.17.camel@redhat.com> <55FC61DF.70906@redhat.com> <20150924142002.GL16937@dhcp-40-8.bne.redhat.com> <560486AF.2010508@redhat.com> <20150925133012.GN16937@dhcp-40-8.bne.redhat.com> Message-ID: <20150928075945.GS16937@dhcp-40-8.bne.redhat.com> On Fri, Sep 25, 2015 at 11:30:12PM +1000, Fraser Tweedale wrote: > There is a problem with allowing authority DNs to be reused - when > adding the cert to the NSSDB, despite what nickname you tell it to > you, it will put the cert under the nickname of the existing cert > with that subject DN. Thus when you go to find the cert by > nickname, it cannot locate it. Failure ensues. This is possibly a > bug in NSS (it's certainly surprising), but I need more time to > analyse it. > The observed NSS behaviour (one nickname for all certs with a given Subject DN) is by design. It was a limitation in the old nssdb design, but is now an artifical restriction to maintain the old behaviour. There is apparently no intention / desire to remove it. I will push forward with the subject+issuer patch, at least to get a working proof of concept and assess how it impacts the renewal process. Cheers, Fraser From cheimes at redhat.com Tue Sep 29 14:26:58 2015 From: cheimes at redhat.com (Christian Heimes) Date: Tue, 29 Sep 2015 16:26:58 +0200 Subject: [Pki-devel] [PATCH ] 041 Python packaging of PKI client library Message-ID: <560A9FB2.7060605@redhat.com> Hi, Ade has asked me to modify setup.py so we can upload the client library parts of Dogtag on the Python package index. I ran into trouble with setuptools and had to remove setuptools again. The latest version of setuptools broke the data_files option. Christian -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-cheimes-0041-Python-packaging-of-PKI-client-library.patch Type: text/x-patch Size: 5561 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 455 bytes Desc: OpenPGP digital signature URL: From ftweedal at redhat.com Tue Sep 29 15:25:33 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Wed, 30 Sep 2015 01:25:33 +1000 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion Message-ID: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> The attached patches fix some incorrect synchronization of the lightweight CAs index (patch 0048) and implement deletion of lightweight CAs (patch 0049). These patches replace earlier patches 0048 and 0049 which I rescind. There is a commented out throw in CertificateAuthority.deleteAuthority(); I don't yet understand what causes this failure case but a) everything seems to work (at least with the small numbers of lightweight CAs I've tested with) and b) I'm seeking clarification from NSS experts on the matter, so stay tuned. Cheers, Fraser -------------- next part -------------- From cc19c2e76db61475981c9bf5e860bca9b998c338 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 29 Sep 2015 05:59:38 -0400 Subject: [PATCH 48/49] Lightweight CAs: fix caMap synchronization Some access to caMap was not correctly synchronized, with authorities (of which there could be many) acquiring their own intrinsic lock rather than the shared caMap. Use 'Collections.synchronizedSortedMap' to fix this. As a bonus, locking is now more fine-grained. --- base/ca/src/com/netscape/ca/CertificateAuthority.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 42a0ec4d1d362c9b615cb9483530590e2b785a42..b3663ed1d497d03651ad1fa753b4e23ae4aea6b0 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -161,7 +161,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); - private static final Map caMap = new TreeMap<>(); + private static final Map caMap = + Collections.synchronizedSortedMap(new TreeMap<>()); protected CertificateAuthority hostCA = null; protected AuthorityID authorityID = null; protected AuthorityID authorityParentID = null; @@ -1934,7 +1935,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * * This method must only be called by the host CA. */ - private synchronized void loadLightweightCAs() throws EBaseException { + private void loadLightweightCAs() throws EBaseException { ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs"); dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); LDAPConnection conn = dbFactory.getConn(); @@ -2321,10 +2322,12 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * Enumerate all authorities (including host authority) */ - public synchronized List getCAs() { + public List getCAs() { List cas = new ArrayList<>(); - for (ICertificateAuthority ca : caMap.values()) { - cas.add(ca); + synchronized (caMap) { + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } } return cas; } @@ -2379,9 +2382,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori ICertificateAuthority ca = parentCA.createSubCA( subjectDN, description); - synchronized (this) { - caMap.put(ca.getAuthorityID(), ca); - } + caMap.put(ca.getAuthorityID(), ca); return ca; } -- 2.4.3 -------------- next part -------------- From c922474f0968d71ec903873ced8e4dc6799a6a22 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 29 Sep 2015 11:17:21 -0400 Subject: [PATCH 49/49] Lightweight CAs: implement deletion API and CLI Fixes: https://fedorahosted.org/pki/ticket/1324 --- base/ca/shared/conf/acl.ldif | 2 +- base/ca/shared/conf/acl.properties | 1 + .../src/com/netscape/ca/CertificateAuthority.java | 59 ++++++++++++++++++++++ .../dogtagpki/server/ca/rest/AuthorityService.java | 32 ++++++++++-- .../certsrv/authority/AuthorityClient.java | 5 ++ .../certsrv/authority/AuthorityResource.java | 8 +++ .../netscape/certsrv/ca/ICertificateAuthority.java | 6 +++ .../netscape/cmstools/authority/AuthorityCLI.java | 1 + .../cmstools/authority/AuthorityRemoveCLI.java | 59 ++++++++++++++++++++++ 9 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 54c9f1d5c64b6578de83f1b7ffdff922a69975f4..97c122b2f4575df14e61013a34ddd25adfca30b7 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -58,4 +58,4 @@ resourceACLS: certServer.ca.groups:execute:allow (execute) group="Administrators resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="Administrators":Only admins can access selftests. resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities -resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities +resourceACLS: certServer.ca.authorities:create,modify,delete:allow (create,modify,delete) group="Administrators":Administrators may create, modify and delete lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index f0b5b9f650ad2fc4bde531ade94347a7280d3089..8b3e9d0eea09e5e3ab8271888ab0532d47b69348 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -25,3 +25,4 @@ authorities.create = certServer.ca.authorities,create authorities.list = certServer.ca.authorities,list authorities.modify = certServer.ca.authorities,modify authorities.read = certServer.ca.authorities,read +authorities.delete = certServer.ca.authorities,delete diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index b3663ed1d497d03651ad1fa753b4e23ae4aea6b0..525b9bd8c0dc45a1ad0419ab35f702c57a9e8bfc 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -50,9 +50,11 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoStore; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.KeyPairAlgorithm; import org.mozilla.jss.crypto.KeyPairGenerator; +import org.mozilla.jss.crypto.NoSuchItemOnTokenException; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -2624,4 +2626,61 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + public void deleteAuthority() throws EBaseException { + if (isHostAuthority()) + throw new CATypeException("Cannot delete the host CA"); + + caMap.remove(authorityID); + shutdown(); + + // delete ldap entry + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + try { + conn.delete(dn); + } catch (LDAPException e) { + throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + CryptoManager cryptoManager; + try { + cryptoManager = CryptoManager.getInstance(); + } catch (CryptoManager.NotInitializedException e) { + // can't happen + throw new ECAException("CryptoManager not initialized"); + } + + // delete cert + CryptoStore cryptoStore = + cryptoManager.getInternalKeyStorageToken().getCryptoStore(); + try { + cryptoStore.deleteCert(mCaX509Cert); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteCA: cert is not on token: " + e); + // if the cert isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteCA: TokenExcepetion while deleting cert: " + e); + throw new ECAException("TokenException while deleting cert: " + e); + } + + // delete key + try { + cryptoStore.deletePrivateKey(mSigningUnit.getPrivateKey()); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteCA: private key is not on token: " + e); + // if the key isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteCA: TokenExcepetion while deleting private key: " + e); + // TODO don't know what causes this yet, or how to + // prevent it. + //throw new ECAException("TokenException while deleting private key: " + e); + } + } + } diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java index 820f8ab6499eed9fdb8e3d8d782df64c71ad1fc3..1449a1e6a58946f1ce1fd6a2d5b5cb7b6b15f7dd 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -99,7 +99,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ca = hostCA.getCA(aid); @@ -116,7 +116,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -143,7 +143,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -198,7 +198,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -232,6 +232,30 @@ public class AuthorityService extends PKIService implements AuthorityResource { new AuthorityData(null, null, null, null, false, null)); } + @Override + public Response deleteCA(String aidString) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad AuthorityID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.deleteAuthority(); + return createNoContentResponse(); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + private static AuthorityData readAuthorityData(ICertificateAuthority ca) throws PKIException { String dn; diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java index 86de3352e2424211125c146edf759481448a2694..5a80877ca4479058ba88005421c46d092b2df6a6 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -59,4 +59,9 @@ public class AuthorityClient extends Client { return client.getEntity(response, AuthorityData.class); } + public void deleteCA(String aidString) { + Response response = proxy.deleteCA(aidString); + client.getEntity(response, Void.class); + } + } diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java index eaef903db444512dbea6c87b11800130d94a944d..c6dc696247122b5f07802696c38c2f3517341106 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -1,5 +1,6 @@ package com.netscape.certsrv.authority; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -93,4 +94,11 @@ public interface AuthorityResource { @ACLMapping("authorities.modify") public Response disableCA(@PathParam("id") String caIDString); + @DELETE + @Path("{id}") + @ClientResponseType(entityType=Void.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.delete") + public Response deleteCA(@PathParam("id") String caIDString); + } diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index 31d5c9277a63ca7c916f39651300b0c9a9061c1e..96bc392294f27d57d9795c2b1b793b8cbc001fda 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -583,4 +583,10 @@ public interface ICertificateAuthority extends ISubsystem { */ public void modifyAuthority(Boolean enabled, String desc) throws EBaseException; + + /** + * Delete this lightweight CA. + */ + public void deleteAuthority() + throws EBaseException; } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java index 99d38ad1b989e171079df78ddd8b2774817ccb33..4fbcfef760086928b2e0e75fe4fc56f1b249b5fd 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -17,6 +17,7 @@ public class AuthorityCLI extends CLI { addModule(new AuthorityCreateCLI(this)); addModule(new AuthorityDisableCLI(this)); addModule(new AuthorityEnableCLI(this)); + addModule(new AuthorityRemoveCLI(this)); } public String getFullName() { diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..8cfb2f84067b63652687feb2c51d29e91baf861f --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java @@ -0,0 +1,59 @@ +package com.netscape.cmstools.authority; + +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityRemoveCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityRemoveCLI(AuthorityCLI authorityCLI) { + super("del", "Delete Authority", authorityCLI); + this.authorityCLI = authorityCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No ID specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + String aidString = cmdArgs[0]; + authorityCLI.authorityClient.deleteCA(aidString); + MainCLI.printMessage("Deleted authority \"" + aidString + "\""); + } + +} -- 2.4.3 From edewata at redhat.com Tue Sep 29 19:13:30 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Tue, 29 Sep 2015 14:13:30 -0500 Subject: [Pki-devel] [PATCH] 644 Added support for directory-authenticated profiles in CLI. In-Reply-To: <5601960E.80003@redhat.com> References: <5601960E.80003@redhat.com> Message-ID: <560AE2DA.6010302@redhat.com> On 9/22/2015 12:55 PM, Endi Sukma Dewata wrote: > The pki client-cert-request CLI has been modified to support > directory-authenticated profiles by sending the username and > password as XML/JSON request attributes. The CertRequetService > will then put the credentials into an AuthCredentials object. > > The ProfileSubmitServlet has also been modified to create an > AuthCredentials object from the HTTP request object. > > The certificate processor classes have been modified to accept > an AuthCredentials object instead of retrieving it from HTTP > request object. > > https://fedorahosted.org/pki/ticket/1463 The patch has been revised and split into 3 patches. Please apply in the following order: #645, #646, #644-1. -- Endi S. Dewata -------------- next part -------------- >From b936584aa94affa4d477b0265caa79a7059ad4a7 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Mon, 28 Sep 2015 10:40:32 +0200 Subject: [PATCH] Relocated legacy cert enrollment methods. The EnrollmentProcessor.processEnrollment() and RenewalProcessor. processRenewal() methods that take CMSRequest object have been moved into ProfileSubmitServlet because they are only used by the legacy servlet. https://fedorahosted.org/pki/ticket/1463 --- .../cms/servlet/cert/EnrollmentProcessor.java | 23 +------- .../cms/servlet/cert/RenewalProcessor.java | 24 +------- .../cms/servlet/processors/CAProcessor.java | 12 +++- .../cms/servlet/profile/ProfileSubmitServlet.java | 66 +++++++++++++++++++--- 4 files changed, 73 insertions(+), 52 deletions(-) diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index e5b9a14df99f29da8ad5c4f76c088c98ff766540..c1faabf399043593425f3294de606674d2ecf422 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -29,8 +29,8 @@ import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; -import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; @@ -39,7 +39,6 @@ import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.request.IRequest; -import com.netscape.cms.servlet.common.CMSRequest; import com.netscape.cms.servlet.common.CMSTemplate; import com.netscape.cms.servlet.profile.SSLClientCertProvider; import com.netscape.cmsutil.ldap.LDAPUtil; @@ -84,26 +83,6 @@ public class EnrollmentProcessor extends CertProcessor { } /** - * Called by the legacy servlets to access the Processor function - * @param request - * @return - * @throws EBaseException - */ - public HashMap processEnrollment(CMSRequest cmsReq) throws EBaseException { - HttpServletRequest req = cmsReq.getHttpReq(); - String profileId = (this.profileID == null) ? req.getParameter("profileId") : this.profileID; - IProfile profile = ps.getProfile(profileId); - - if (profile == null) { - CMS.debug(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", CMSTemplate.escapeJavaScriptStringHTML(profileId))); - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND",CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - - CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq(), null); - } - - /** * Process the HTTP request *

* diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java index efd1d7b0cf799dc399257502cb3f4e3196174b50..5ebbbff8fb3fd70fe4e1ebecbdce7c978d37a7a4 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java @@ -26,9 +26,6 @@ import java.util.Locale; import javax.servlet.http.HttpServletRequest; -import netscape.security.x509.BasicConstraintsExtension; -import netscape.security.x509.X509CertImpl; - import org.apache.commons.lang.StringUtils; import com.netscape.certsrv.apps.CMS; @@ -45,33 +42,18 @@ import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.request.IRequest; -import com.netscape.cms.servlet.common.CMSRequest; import com.netscape.cms.servlet.common.CMSTemplate; import com.netscape.cms.servlet.profile.SSLClientCertProvider; +import netscape.security.x509.BasicConstraintsExtension; +import netscape.security.x509.X509CertImpl; + public class RenewalProcessor extends CertProcessor { public RenewalProcessor(String id, Locale locale) throws EPropertyNotFound, EBaseException { super(id, locale); } - public HashMap processRenewal(CMSRequest cmsReq) throws EBaseException { - HttpServletRequest req = cmsReq.getHttpReq(); - String profileId = (this.profileID == null) ? req.getParameter("profileId") : this.profileID; - IProfile profile = ps.getProfile(profileId); - if (profile == null) { - throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", - CMSTemplate.escapeJavaScriptStringHTML(profileId))); - } - - CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - - //only used in renewal - data.setSerialNum(req.getParameter("serial_num")); - - return processRenewal(data, req); - } - /* * Renewal - Renewal is retrofitted into the Profile Enrollment * Framework. The authentication and authorization are taken from diff --git a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java index b9af84bc9b5b878f895707c266b1df1fa5b1e26f..5f6f45cb8a2dc4ada2f61fdd808a30fad9358cc2 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java @@ -34,8 +34,6 @@ import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; -import netscape.security.x509.X509CertImpl; - import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.AuthToken; import com.netscape.certsrv.authentication.IAuthToken; @@ -69,6 +67,8 @@ import com.netscape.cms.servlet.common.CMSGateway; import com.netscape.cms.servlet.common.ServletUtils; import com.netscape.cmsutil.util.Utils; +import netscape.security.x509.X509CertImpl; + public class CAProcessor extends Processor { public final static String ARG_AUTH_TOKEN = "auth_token"; @@ -196,6 +196,14 @@ public class CAProcessor extends Processor { } } + public String getProfileID() { + return profileID; + } + + public IProfileSubsystem getProfileSubsystem() { + return ps; + } + /****************************************** * Stats - to be moved to Stats module ******************************************/ diff --git a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java index 3f8d4c4791ed3fa49b1e0f3af68b62eba207de0c..c26853db5a40b6c69bc0ede23d8b6b848fd019cf 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java +++ b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java @@ -26,9 +26,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509CertInfo; - import org.w3c.dom.Node; import com.netscape.certsrv.apps.CMS; @@ -36,21 +33,28 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.profile.EProfileException; import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileOutput; +import com.netscape.certsrv.profile.IProfileSubsystem; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.template.ArgList; import com.netscape.certsrv.template.ArgSet; +import com.netscape.cms.servlet.cert.CertEnrollmentRequestFactory; import com.netscape.cms.servlet.cert.EnrollmentProcessor; import com.netscape.cms.servlet.cert.RenewalProcessor; import com.netscape.cms.servlet.common.CMSRequest; +import com.netscape.cms.servlet.common.CMSTemplate; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cmsutil.util.Cert; import com.netscape.cmsutil.xml.XMLObject; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509CertInfo; + /** * This servlet submits end-user request into the profile framework. * @@ -114,12 +118,10 @@ public class ProfileSubmitServlet extends ProfileServlet { try { if ((renewal != null) && (renewal.equalsIgnoreCase("true"))) { CMS.debug("ProfileSubmitServlet: isRenewal true"); - RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); - results = processor.processRenewal(cmsReq); + results = processRenewal(cmsReq); } else { CMS.debug("ProfileSubmitServlet: isRenewal false"); - EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(cmsReq); + results = processEnrollment(cmsReq); } } catch (BadRequestDataException e) { CMS.debug("ProfileSubmitServlet: bad data provided in processing request: " + e.toString()); @@ -199,6 +201,56 @@ public class ProfileSubmitServlet extends ProfileServlet { } } + public HashMap processEnrollment(CMSRequest cmsReq) throws EBaseException { + + HttpServletRequest request = cmsReq.getHttpReq(); + Locale locale = getLocale(request); + + EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); + + String profileId = processor.getProfileID() == null ? request.getParameter("profileId") : processor.getProfileID(); + CMS.debug("ProfileSubmitServlet: profile: " + profileId); + + IProfileSubsystem ps = processor.getProfileSubsystem(); + IProfile profile = ps.getProfile(profileId); + + if (profile == null) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not found"); + throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", + CMSTemplate.escapeJavaScriptStringHTML(profileId))); + } + + CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); + return processor.processEnrollment(data, request, null); + } + + public HashMap processRenewal(CMSRequest cmsReq) throws EBaseException { + + HttpServletRequest request = cmsReq.getHttpReq(); + Locale locale = getLocale(request); + + RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); + + String profileId = processor.getProfileID() == null ? request.getParameter("profileId") : processor.getProfileID(); + CMS.debug("ProfileSubmitServlet: profile: " + profileId); + + IProfileSubsystem ps = processor.getProfileSubsystem(); + IProfile profile = ps.getProfile(profileId); + + if (profile == null) { + CMS.debug("ProfileSubmitServlet: Profile " + profileId + " not found"); + throw new BadRequestDataException(CMS.getUserMessage(locale, "CMS_PROFILE_NOT_FOUND", + CMSTemplate.escapeJavaScriptStringHTML(profileId))); + } + + CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); + + //only used in renewal + data.setSerialNum(request.getParameter("serial_num")); + + return processor.processRenewal(data, request); + } + private void setOutputIntoArgs(IProfile profile, ArgList outputlist, Locale locale, IRequest req) { Enumeration outputIds = profile.getProfileOutputIds(); -- 2.4.3 -------------- next part -------------- >From f560636a1ff8c26acaa725a5d62be31410257d8c Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Mon, 28 Sep 2015 22:37:02 +0200 Subject: [PATCH] Refactored certificate processors. The CertProcessor.setCredentialsIntoContext() and CAProcessor. authenticate() methods have been modified such that they can accept credentials provided via the AuthCredentials (for REST services) or via the HttpServletRequest (for legacy servlets). The CertEnrollmentRequest has been modified to inherit from ResourceMessage such that REST clients can provide the credentials via request attributes. https://fedorahosted.org/pki/ticket/1463 --- .../server/ca/rest/CertRequestService.java | 2 + .../certsrv/cert/CertEnrollmentRequest.java | 12 ++-- .../netscape/cms/servlet/cert/CertProcessor.java | 38 +++++++------ .../netscape/cms/servlet/cert/CertRequestDAO.java | 15 ++++- .../cms/servlet/cert/EnrollmentProcessor.java | 8 ++- .../cms/servlet/cert/RenewalProcessor.java | 12 ++-- .../cms/servlet/common/AuthCredentials.java | 2 +- .../cms/servlet/processors/CAProcessor.java | 64 ++++++++++++++++------ .../cms/servlet/profile/ProfileSubmitServlet.java | 4 +- .../cmscore/authentication/AuthSubsystem.java | 4 ++ 10 files changed, 111 insertions(+), 50 deletions(-) diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 7cb4ff71e18b6e29bf55c11dc99bbfb9b83dd60f..cddbeb1ba47741673ab5eb3d22e2bf7c53c4c33d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -67,6 +67,7 @@ import com.netscape.certsrv.request.RequestNotFoundException; import com.netscape.cms.servlet.base.PKIService; import com.netscape.cms.servlet.cert.CertRequestDAO; import com.netscape.cmsutil.ldap.LDAPUtil; + import netscape.security.x509.X500Name; /** @@ -175,6 +176,7 @@ public class CertRequestService extends PKIService implements CertRequestResourc CMS.debug("enrollCert: bad request data: " + e); throw new BadRequestException(e.toString()); } catch (EBaseException e) { + CMS.debug(e); throw new PKIException(e); } catch (Exception e) { CMS.debug(e); diff --git a/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java b/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java index d55b5b4e1007516fef8fa6f9820c44d522f4bde4..2b914e85667dc525947f7357ceaf6bbe464a2480 100644 --- a/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java +++ b/base/common/src/com/netscape/certsrv/cert/CertEnrollmentRequest.java @@ -37,6 +37,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import com.netscape.certsrv.base.ResourceMessage; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.profile.ProfileOutput; @@ -48,7 +49,7 @@ import com.netscape.certsrv.profile.ProfileOutput; @XmlRootElement(name = "CertEnrollmentRequest") @XmlAccessorType(XmlAccessType.FIELD) -public class CertEnrollmentRequest { +public class CertEnrollmentRequest extends ResourceMessage { private static final String PROFILE_ID = "profileId"; private static final String RENEWAL = "renewal"; @@ -286,7 +287,7 @@ public class CertEnrollmentRequest { @Override public int hashCode() { final int prime = 31; - int result = 1; + int result = super.hashCode(); result = prime * result + ((inputs == null) ? 0 : inputs.hashCode()); result = prime * result + ((outputs == null) ? 0 : outputs.hashCode()); result = prime * result + ((profileId == null) ? 0 : profileId.hashCode()); @@ -301,7 +302,7 @@ public class CertEnrollmentRequest { public boolean equals(Object obj) { if (this == obj) return true; - if (obj == null) + if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; @@ -346,8 +347,6 @@ public class CertEnrollmentRequest { before.setProfileId("caUserCert"); before.setRenewal(false); - //Simulate a "caUserCert" Profile enrollment - ProfileInput certReq = before.createInput("KeyGenInput"); certReq.addAttribute(new ProfileAttribute("cert_request_type", "crmf", null)); certReq.addAttribute(new ProfileAttribute( @@ -371,6 +370,9 @@ public class CertEnrollmentRequest { submitter.addAttribute(new ProfileAttribute("requestor_email", "admin at redhat.com", null)); submitter.addAttribute(new ProfileAttribute("requestor_phone", "650-555-5555", null)); + before.setAttribute("uid", "testuser"); + before.setAttribute("pwd", "password"); + String xml = before.toXML(); System.out.println(xml); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java index f1a147eb475a8a1378cac829dcaee765ab2c3e70..e5daf78fd6e006c6f559a6fc3bf9cad6485b64e9 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertProcessor.java @@ -42,6 +42,7 @@ import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.request.INotify; import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.request.RequestStatus; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cmsutil.ldap.LDAPUtil; @@ -51,26 +52,31 @@ public class CertProcessor extends CAProcessor { super(id, locale); } - protected void setCredentialsIntoContext(HttpServletRequest request, IProfileAuthenticator authenticator, + protected void setCredentialsIntoContext( + HttpServletRequest request, + AuthCredentials creds, + IProfileAuthenticator authenticator, IProfileContext ctx) { - Enumeration authIds = authenticator.getValueNames(); - if (authIds != null) { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authNames not null"); - while (authIds.hasMoreElements()) { - String authName = authIds.nextElement(); + Enumeration names = authenticator.getValueNames(); + if (names == null) { + CMS.debug("CertProcessor: No authenticator credentials required"); + return; + } - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName:" + - authName); - if (request.getParameter(authName) != null) { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName found in request"); - ctx.set(authName, request.getParameter(authName)); - } else { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authName not found in request"); - } + CMS.debug("CertProcessor: Authentication credentials:"); + while (names.hasMoreElements()) { + String name = names.nextElement(); + + Object value; + if (creds == null) { + value = request.getParameter(name); + } else { + value = creds.get(name); } - } else { - CMS.debug("CertRequestSubmitter:setCredentialsIntoContext() authIds` null"); + + if (value == null) continue; + ctx.set(name, value.toString()); } } diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index a2e4b583d318ac8412361850d91233b77a447e13..6fbcd3c37ae46dd8ea71673d3c862890cbc9f3e4 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -44,6 +44,7 @@ import com.netscape.certsrv.request.IRequest; import com.netscape.certsrv.request.IRequestQueue; import com.netscape.certsrv.request.RequestId; import com.netscape.certsrv.request.RequestNotFoundException; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.processors.CAProcessor; import com.netscape.cms.servlet.request.CMSRequestDAO; @@ -175,13 +176,23 @@ public class CertRequestDAO extends CMSRequestDAO { CertRequestInfos ret = new CertRequestInfos(); + AuthCredentials credentials = new AuthCredentials(); + String uid = data.getAttribute("uid"); + if (uid != null) { + credentials.set("uid", uid); + } + String password = data.getAttribute("pwd"); + if (password != null) { + credentials.set("pwd", password); + } + HashMap results = null; if (data.isRenewal()) { RenewalProcessor processor = new RenewalProcessor("caProfileSubmit", locale); - results = processor.processRenewal(data, request); + results = processor.processRenewal(data, request, credentials); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request, aid); + results = processor.processEnrollment(data, request, aid, credentials); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index c1faabf399043593425f3294de606674d2ecf422..dadd34cfe8b74ebbefa1af2d2141d5baee04755e 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -39,6 +39,7 @@ import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; import com.netscape.certsrv.request.IRequest; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.common.CMSTemplate; import com.netscape.cms.servlet.profile.SSLClientCertProvider; import com.netscape.cmsutil.ldap.LDAPUtil; @@ -102,7 +103,8 @@ public class EnrollmentProcessor extends CertProcessor { public HashMap processEnrollment( CertEnrollmentRequest data, HttpServletRequest request, - AuthorityID aid) + AuthorityID aid, + AuthCredentials credentials) throws EBaseException { try { @@ -140,7 +142,7 @@ public class EnrollmentProcessor extends CertProcessor { IProfileAuthenticator authenticator = profile.getAuthenticator(); if (authenticator != null) { CMS.debug("EnrollmentProcessor: authenticator " + authenticator.getName() + " found"); - setCredentialsIntoContext(request, authenticator, ctx); + setCredentialsIntoContext(request, credentials, authenticator, ctx); } // for ssl authentication; pass in servlet for retrieving ssl client certificates @@ -151,7 +153,7 @@ public class EnrollmentProcessor extends CertProcessor { CMS.debug("EnrollmentProcessor: set sslClientCertProvider"); // before creating the request, authenticate the request - IAuthToken authToken = authenticate(request, null, authenticator, context, false); + IAuthToken authToken = authenticate(request, null, authenticator, context, false, credentials); // authentication success, now authorize authorize(profileId, profile, authToken); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java index 5ebbbff8fb3fd70fe4e1ebecbdce7c978d37a7a4..7e34e4d5eb89b1287bf27ff410eb02bed4afdc1a 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java @@ -42,6 +42,7 @@ import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; import com.netscape.certsrv.profile.IProfileInput; import com.netscape.certsrv.request.IRequest; +import com.netscape.cms.servlet.common.AuthCredentials; import com.netscape.cms.servlet.common.CMSTemplate; import com.netscape.cms.servlet.profile.SSLClientCertProvider; @@ -63,7 +64,10 @@ public class RenewalProcessor extends CertProcessor { * Things to note: * * the renew request will contain the original profile instead of the new */ - public HashMap processRenewal(CertEnrollmentRequest data, HttpServletRequest request) + public HashMap processRenewal( + CertEnrollmentRequest data, + HttpServletRequest request, + AuthCredentials credentials) throws EBaseException { try { if (CMS.debugOn()) { @@ -170,14 +174,14 @@ public class RenewalProcessor extends CertProcessor { if (authenticator != null) { CMS.debug("RenewalSubmitter: authenticator " + authenticator.getName() + " found"); - setCredentialsIntoContext(request, authenticator, ctx); + setCredentialsIntoContext(request, credentials, authenticator, ctx); } // for renewal, this will override or add auth info to the profile context if (origAuthenticator != null) { CMS.debug("RenewalSubmitter: for renewal, original authenticator " + origAuthenticator.getName() + " found"); - setCredentialsIntoContext(request, origAuthenticator, ctx); + setCredentialsIntoContext(request, credentials, origAuthenticator, ctx); } // for renewal, input needs to be retrieved from the orig req record @@ -197,7 +201,7 @@ public class RenewalProcessor extends CertProcessor { context.put("origSubjectDN", origSubjectDN); // before creating the request, authenticate the request - IAuthToken authToken = authenticate(request, origReq, authenticator, context, true); + IAuthToken authToken = authenticate(request, origReq, authenticator, context, true, credentials); // authentication success, now authorize authorize(profileId, renewProfile, authToken); diff --git a/base/server/cms/src/com/netscape/cms/servlet/common/AuthCredentials.java b/base/server/cms/src/com/netscape/cms/servlet/common/AuthCredentials.java index 32ae0fcc815bb2afc304726266bccc4c9fef6a6a..b4d5fa9c858a8326a55365395cca5384f69499df 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/common/AuthCredentials.java +++ b/base/server/cms/src/com/netscape/cms/servlet/common/AuthCredentials.java @@ -54,7 +54,7 @@ public class AuthCredentials implements IAuthCredentials { */ public void set(String name, Object cred) throws EAuthException { if (cred == null) { - throw new EAuthException("AuthCredentials.set()"); + throw new EAuthException("Missing credential: " + name); } authCreds.put(name, cred); diff --git a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java index 5f6f45cb8a2dc4ada2f61fdd808a30fad9358cc2..e3b3d3497fa63c3986fbb33af77f30aad1e7146d 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/processors/CAProcessor.java @@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletRequest; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.AuthToken; +import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authentication.IAuthToken; import com.netscape.certsrv.authorization.AuthzToken; import com.netscape.certsrv.authorization.IAuthzSubsystem; @@ -358,10 +359,14 @@ public class CAProcessor extends Processor { * authenticate for renewal - more to add necessary params/values * to the session context */ - public IAuthToken authenticate(IProfileAuthenticator authenticator, - HttpServletRequest request, IRequest origReq, SessionContext context) throws EBaseException + public IAuthToken authenticate( + IProfileAuthenticator authenticator, + HttpServletRequest request, + IRequest origReq, + SessionContext context, + AuthCredentials credentials) throws EBaseException { - IAuthToken authToken = authenticate(authenticator, request); + IAuthToken authToken = authenticate(authenticator, request, credentials); // For renewal, fill in necessary params if (authToken != null) { String ouid = origReq.getExtDataInString("auth_token.uid"); @@ -417,18 +422,23 @@ public class CAProcessor extends Processor { return authToken; } - public IAuthToken authenticate(IProfileAuthenticator authenticator, - HttpServletRequest request) throws EBaseException { - AuthCredentials credentials = new AuthCredentials(); + public IAuthToken authenticate( + IProfileAuthenticator authenticator, + HttpServletRequest request, + AuthCredentials credentials) throws EBaseException { - // build credential - Enumeration authNames = authenticator.getValueNames(); + if (credentials == null) { + credentials = new AuthCredentials(); - if (authNames != null) { - while (authNames.hasMoreElements()) { - String authName = authNames.nextElement(); + // build credential + Enumeration authNames = authenticator.getValueNames(); - credentials.set(authName, request.getParameter(authName)); + if (authNames != null) { + while (authNames.hasMoreElements()) { + String authName = authNames.nextElement(); + + credentials.set(authName, request.getParameter(authName)); + } } } @@ -447,8 +457,13 @@ public class CAProcessor extends Processor { return authToken; } - public IAuthToken authenticate(HttpServletRequest request, IRequest origReq, IProfileAuthenticator authenticator, - SessionContext context, boolean isRenewal) throws EBaseException { + public IAuthToken authenticate( + HttpServletRequest request, + IRequest origReq, + IProfileAuthenticator authenticator, + SessionContext context, + boolean isRenewal, + AuthCredentials credentials) throws EBaseException { startTiming("profile_authentication"); IAuthToken authToken = null; @@ -475,12 +490,27 @@ public class CAProcessor extends Processor { String auditMessage = null; try { if (isRenewal) { - authToken = authenticate(authenticator, request, origReq, context); + authToken = authenticate(authenticator, request, origReq, context, credentials); } else { - authToken = authenticate(authenticator, request); + authToken = authenticate(authenticator, request, credentials); } + + } catch (EAuthException e) { + CMS.debug("CAProcessor: authentication error: " + e); + + authSubjectID += " : " + uid_cred; + auditMessage = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_AUTH_FAIL, + authSubjectID, + ILogger.FAILURE, + authMgrID, + uid_attempted_cred); + audit(auditMessage); + + throw e; + } catch (EBaseException e) { - CMS.debug("CertProcessor: authentication error " + e.toString()); + CMS.debug(e); authSubjectID += " : " + uid_cred; auditMessage = CMS.getLogMessage( diff --git a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java index c26853db5a40b6c69bc0ede23d8b6b848fd019cf..f7b08ece99e11f1e1633e0d67fb4646a27417d80 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java +++ b/base/server/cms/src/com/netscape/cms/servlet/profile/ProfileSubmitServlet.java @@ -221,7 +221,7 @@ public class ProfileSubmitServlet extends ProfileServlet { } CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processor.processEnrollment(data, request, null); + return processor.processEnrollment(data, request, null, null); } public HashMap processRenewal(CMSRequest cmsReq) throws EBaseException { @@ -248,7 +248,7 @@ public class ProfileSubmitServlet extends ProfileServlet { //only used in renewal data.setSerialNum(request.getParameter("serial_num")); - return processor.processRenewal(data, request); + return processor.processRenewal(data, request, null); } private void setOutputIntoArgs(IProfile profile, ArgList outputlist, Locale locale, IRequest req) { diff --git a/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java index 137edb5c5a75916fb8a2b2fdf07ab0a6aa56f0fe..8e2c59c26a6b142c8d600c28e3facd6eef4e1913 100644 --- a/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java +++ b/base/server/cmscore/src/com/netscape/cmscore/authentication/AuthSubsystem.java @@ -195,6 +195,8 @@ public class AuthSubsystem implements IAuthSubsystem { while (instances.hasMoreElements()) { String insName = instances.nextElement(); + CMS.debug("AuthSubsystem: initializing authentication manager " + insName); + String implName = c.getString(insName + "." + PROP_PLUGIN); AuthMgrPlugin plugin = mAuthMgrPlugins.get(implName); @@ -233,6 +235,7 @@ public class AuthSubsystem implements IAuthSubsystem { throw new EAuthException(CMS.getUserMessage("CMS_ACL_CLASS_LOAD_FAIL", className), e); } catch (EBaseException e) { + CMS.debug(e); log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AUTH_INIT_ERROR", insName, e.toString())); // Skip the authenticaiton instance if // it is mis-configurated. This give @@ -240,6 +243,7 @@ public class AuthSubsystem implements IAuthSubsystem { // fix the problem via console } catch (Throwable e) { + CMS.debug(e); log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_AUTH_AUTH_INIT_ERROR", insName, e.toString())); // Skip the authenticaiton instance if // it is mis-configurated. This give -- 2.4.3 -------------- next part -------------- >From 5fc1eccd1e9b8c9503bbfe01bb7b6ef370d3474b Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Sun, 27 Sep 2015 17:23:48 +0200 Subject: [PATCH] Added support for directory-authenticated profiles in CLI. The pki cert-request-submit and client-cert-request CLIs have been modified to provide options to specify the username and password for directory-authenticated certificate enrollments. https://fedorahosted.org/pki/ticket/1463 --- .../cmstools/cert/CertRequestSubmitCLI.java | 47 ++++++++------ .../cmstools/client/ClientCertRequestCLI.java | 72 ++++++++++++++++++---- 2 files changed, 89 insertions(+), 30 deletions(-) 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 9611159681b65844c1fc32937ca0a65c2c31980d..cec1cff4f2c8167c7c16a3d095963039840b1486 100644 --- a/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRequestSubmitCLI.java @@ -1,5 +1,6 @@ package com.netscape.cmstools.cert; +import java.io.Console; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -17,6 +18,7 @@ import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; + import netscape.security.x509.X500Name; public class CertRequestSubmitCLI extends CLI { @@ -27,13 +29,20 @@ public class CertRequestSubmitCLI extends CLI { super("request-submit", "Submit certificate request", certCLI); this.certCLI = certCLI; - Option optAID = new Option(null, "issuer-id", true, "Authority ID (host authority if omitted)"); - optAID.setArgName("id"); - options.addOption(optAID); + Option option = new Option(null, "issuer-id", true, "Authority ID (host authority if omitted)"); + option.setArgName("id"); + options.addOption(option); - Option optADN = new Option(null, "issuer-dn", true, "Authority DN (host authority if omitted)"); - optADN.setArgName("dn"); - options.addOption(optADN); + 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); } public void printHelp() { @@ -41,7 +50,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 @@ -97,20 +106,22 @@ public class CertRequestSubmitCLI extends CLI { System.exit(-1); } - try { - CertEnrollmentRequest erd = getEnrollmentRequest(cmdArgs[0]); - CertRequestInfos cri = certCLI.certClient.enrollRequest(erd, aid, adn); - MainCLI.printMessage("Submitted certificate request"); - CertCLI.printCertRequestInfos(cri); + CertEnrollmentRequest request = getEnrollmentRequest(cmdArgs[0]); - } catch (FileNotFoundException e) { - System.err.println("Error: " + e.getMessage()); - System.exit(-1); + String certRequestUsername = cmd.getOptionValue("username"); + if (certRequestUsername != null) { + request.setAttribute("uid", certRequestUsername); + } - } catch (JAXBException e) { - System.err.println("Error: " + e.getMessage()); - System.exit(-1); + if (cmd.hasOption("password")) { + Console console = System.console(); + String certRequestPassword = new String(console.readPassword("Password: ")); + request.setAttribute("pwd", certRequestPassword); } + + CertRequestInfos cri = certCLI.certClient.enrollRequest(request, aid, adn); + MainCLI.printMessage("Submitted certificate request"); + CertCLI.printCertRequestInfos(cri); } private CertEnrollmentRequest getEnrollmentRequest(String fileName) throws JAXBException, FileNotFoundException { diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index db71c8a0f7db4644290efb766178b76668c22377..370a7be5b1d09b8b445a82fce3c2185607e9ccae 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -19,13 +19,13 @@ package com.netscape.cmstools.client; import java.io.ByteArrayOutputStream; +import java.io.Console; import java.io.File; import java.security.KeyPair; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; -import netscape.ldap.util.DN; -import netscape.ldap.util.RDN; - import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.io.FileUtils; @@ -50,6 +50,9 @@ import com.netscape.cmstools.cli.MainCLI; import com.netscape.cmsutil.util.Cert; import com.netscape.cmsutil.util.Utils; +import netscape.ldap.util.DN; +import netscape.ldap.util.RDN; + /** * @author Endi S. Dewata */ @@ -73,6 +76,13 @@ public class ClientCertRequestCLI extends CLI { option.setArgName("request type"); 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, "attribute-encoding", false, "Enable Attribute encoding"); options.addOption(option); @@ -265,20 +275,58 @@ public class ClientCertRequestCLI extends CLI { } } + // parse subject DN and put the values in a map + 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) { - DN dn = new DN(subjectDN); - Vector rdns = dn.getRDNs(); - - 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]; - ProfileAttribute uidAttr = sn.getAttribute("sn_" + type); - uidAttr.setValue(value); + 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); } } + String certRequestUsername = cmd.getOptionValue("username"); + if (certRequestUsername != null) { + request.setAttribute("uid", certRequestUsername); + } + + if (cmd.hasOption("password")) { + Console console = System.console(); + String certRequestPassword = new String(console.readPassword("Password: ")); + request.setAttribute("pwd", certRequestPassword); + } + if (verbose) { System.out.println("Sending certificate request."); } -- 2.4.3 From cfu at redhat.com Tue Sep 29 23:03:04 2015 From: cfu at redhat.com (Christina Fu) Date: Tue, 29 Sep 2015 16:03:04 -0700 Subject: [Pki-devel] [PATCH] 644 Added support for directory-authenticated profiles in CLI. In-Reply-To: <560AE2DA.6010302@redhat.com> References: <5601960E.80003@redhat.com> <560AE2DA.6010302@redhat.com> Message-ID: <560B18A8.60604@redhat.com> thanks for the split. I have looked at the patches and tried them out too. They are fine. ACK. Christina On 09/29/2015 12:13 PM, Endi Sukma Dewata wrote: > On 9/22/2015 12:55 PM, Endi Sukma Dewata wrote: >> The pki client-cert-request CLI has been modified to support >> directory-authenticated profiles by sending the username and >> password as XML/JSON request attributes. The CertRequetService >> will then put the credentials into an AuthCredentials object. >> >> The ProfileSubmitServlet has also been modified to create an >> AuthCredentials object from the HTTP request object. >> >> The certificate processor classes have been modified to accept >> an AuthCredentials object instead of retrieving it from HTTP >> request object. >> >> https://fedorahosted.org/pki/ticket/1463 > > The patch has been revised and split into 3 patches. Please apply in > the following order: #645, #646, #644-1. > > > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com > https://www.redhat.com/mailman/listinfo/pki-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From alee at redhat.com Wed Sep 30 04:17:23 2015 From: alee at redhat.com (Ade Lee) Date: Wed, 30 Sep 2015 00:17:23 -0400 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> Message-ID: <1443586643.5988.13.camel@redhat.com> ACK on synchronization patch. On the delete patch, a few comments. 1) It would be good to know what is going on with the exception. 2) The new acls and mappings reminded me that upgrade scripts are required to allow old 10.x servers to be able to create subcas. Please open a ticket if one does not yet exist. 3) It would be good to have a "Are you sure?" dialog on the CLI (with relevant override option). 4) Please open an auditing ticket if one is not already opened. We definitely need to be auditing everything here in detail. 5) I have been thinking about ways to restrict delete. We should discuss and decide on options. Some ideas: a) Add CS.cfg option to disable deletes (for production say). b) Add optional field (deletable) to the CA entry. This can be set by the creating admin to be True for test environments or cases where we know the environment will be short lived, or False for long lived CAs. Default could be configurable. CAs could still be deleted, but only by doing something out-of-band --like modifying the db entry using pki-server commands or similar. c) Requiring CAs to be disabled before deleting them. d) Setting a separate ACL for delete, so that it would be easier for admins to set special permissions for delete. ... others? Ade On Wed, 2015-09-30 at 01:25 +1000, Fraser Tweedale wrote: > The attached patches fix some incorrect synchronization of the > lightweight CAs index (patch 0048) and implement deletion of > lightweight CAs (patch 0049). > > These patches replace earlier patches 0048 and 0049 which I rescind. > > There is a commented out throw in > CertificateAuthority.deleteAuthority(); I don't yet understand what > causes this failure case but a) everything seems to work (at least > with the small numbers of lightweight CAs I've tested with) and b) > I'm seeking clarification from NSS experts on the matter, so stay > tuned. > > Cheers, > Fraser From ftweedal at redhat.com Wed Sep 30 08:35:57 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Wed, 30 Sep 2015 18:35:57 +1000 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <1443586643.5988.13.camel@redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> <1443586643.5988.13.camel@redhat.com> Message-ID: <20150930083557.GX16937@dhcp-40-8.bne.redhat.com> On Wed, Sep 30, 2015 at 12:17:23AM -0400, Ade Lee wrote: > ACK on synchronization patch. > Thanks, pushed to master (2cc4977). > On the delete patch, a few comments. > > 1) It would be good to know what is going on with the exception. > It would. Investigations will continue. As discussed on IRC, *if* the patch is merged with this wart, I will open a ticket to track. > 2) The new acls and mappings reminded me that upgrade scripts are > required to allow old 10.x servers to be able to create subcas. Please > open a ticket if one does not yet exist. > Ticket: https://fedorahosted.org/pki/ticket/1630 > 3) It would be good to have a "Are you sure?" dialog on the CLI (with > relevant override option). > Will do. > 4) Please open an auditing ticket if one is not already opened. We > definitely need to be auditing everything here in detail. > Ticket: https://fedorahosted.org/pki/ticket/1629 > 5) I have been thinking about ways to restrict delete. We should > discuss and decide on options. Some ideas: > > a) Add CS.cfg option to disable deletes (for production say). > Disagree; don't want more config in flat files. Having the knob in the database would be better but I prefer a combination of other options (see below). > b) Add optional field (deletable) to the CA entry. This can be > set by the creating admin to be True for test environments or > cases where we know the environment will be short lived, or > False for long lived CAs. Default could be configurable. > > CAs could still be deleted, but only by doing something > out-of-band --like modifying the db entry using pki-server > commands or similar. > > c) Requiring CAs to be disabled before deleting them. > I'm in favour of this. > d) Setting a separate ACL for delete, so that it would be easier > for admins to set special permissions for delete. > And in favour of this. > ... others? > I like (c) plus (d) plus perhaps a pkispawn knob that controls whether the admin-can-delete ACL gets added at the beginning. Let me know what you think and thanks for your feedback! Fraser > Ade > > On Wed, 2015-09-30 at 01:25 +1000, Fraser Tweedale wrote: > > The attached patches fix some incorrect synchronization of the > > lightweight CAs index (patch 0048) and implement deletion of > > lightweight CAs (patch 0049). > > > > These patches replace earlier patches 0048 and 0049 which I rescind. > > > > There is a commented out throw in > > CertificateAuthority.deleteAuthority(); I don't yet understand what > > causes this failure case but a) everything seems to work (at least > > with the small numbers of lightweight CAs I've tested with) and b) > > I'm seeking clarification from NSS experts on the matter, so stay > > tuned. > > > > Cheers, > > Fraser From cheimes at redhat.com Wed Sep 30 13:08:07 2015 From: cheimes at redhat.com (Christian Heimes) Date: Wed, 30 Sep 2015 15:08:07 +0200 Subject: [Pki-devel] [PATCH ] 041 Python packaging of PKI client library In-Reply-To: <560A9FB2.7060605@redhat.com> References: <560A9FB2.7060605@redhat.com> Message-ID: <560BDEB7.8030007@redhat.com> On 2015-09-29 16:26, Christian Heimes wrote: > Hi, > > Ade has asked me to modify setup.py so we can upload the client library > parts of Dogtag on the Python package index. > > I ran into trouble with setuptools and had to remove setuptools again. > The latest version of setuptools broke the data_files option. The new patch provides a separate setup.py just for the client libs. It's cleaner and less of a hack than a combined setup.py with two build flavors. Christian -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-cheimes-0041-2-Python-packaging-of-PKI-client-library.patch Type: text/x-patch Size: 5128 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 455 bytes Desc: OpenPGP digital signature URL: From edewata at redhat.com Wed Sep 30 13:30:08 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Wed, 30 Sep 2015 08:30:08 -0500 Subject: [Pki-devel] [PATCH] 644 Added support for directory-authenticated profiles in CLI. In-Reply-To: <560B18A8.60604@redhat.com> References: <5601960E.80003@redhat.com> <560AE2DA.6010302@redhat.com> <560B18A8.60604@redhat.com> Message-ID: <560BE3E0.6030301@redhat.com> On 9/29/2015 6:03 PM, Christina Fu wrote: > thanks for the split. I have looked at the patches and tried them out > too. They are fine. > ACK. Thanks! Pushed to master. -- Endi S. Dewata From ftweedal at redhat.com Wed Sep 30 14:00:03 2015 From: ftweedal at redhat.com (Fraser Tweedale) Date: Thu, 1 Oct 2015 00:00:03 +1000 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <20150930083557.GX16937@dhcp-40-8.bne.redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> <1443586643.5988.13.camel@redhat.com> <20150930083557.GX16937@dhcp-40-8.bne.redhat.com> Message-ID: <20150930140003.GY16937@dhcp-40-8.bne.redhat.com> Updated patch attached. Comments inline. On Wed, Sep 30, 2015 at 06:35:57PM +1000, Fraser Tweedale wrote: > > 3) It would be good to have a "Are you sure?" dialog on the CLI (with > > relevant override option). > > > Will do. > Done. > > 5) I have been thinking about ways to restrict delete. We should > > discuss and decide on options. Some ideas: > > > > a) Add CS.cfg option to disable deletes (for production say). > > > Disagree; don't want more config in flat files. Having the knob in > the database would be better but I prefer a combination of other > options (see below). > > > b) Add optional field (deletable) to the CA entry. This can be > > set by the creating admin to be True for test environments or > > cases where we know the environment will be short lived, or > > False for long lived CAs. Default could be configurable. > > > > CAs could still be deleted, but only by doing something > > out-of-band --like modifying the db entry using pki-server > > commands or similar. > > > > c) Requiring CAs to be disabled before deleting them. > > > I'm in favour of this. > > > d) Setting a separate ACL for delete, so that it would be easier > > for admins to set special permissions for delete. > > > And in favour of this. > > > ... others? > > > I like (c) plus (d) plus perhaps a pkispawn knob that controls > whether the admin-can-delete ACL gets added at the beginning. > > Let me know what you think and thanks for your feedback! > (c) and (d) are implemented in updated patch. If you agree with (c) plus (d) plus pkispawn knob (I guess we'll call that (e)), I'll file a ticket for (e). Cheers, Fraser -------------- next part -------------- From 285dd4c916f21f59484bec90336acac9170fb51e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Tue, 29 Sep 2015 11:17:21 -0400 Subject: [PATCH] Lightweight CAs: implement deletion API and CLI Fixes: https://fedorahosted.org/pki/ticket/1324 --- base/ca/shared/conf/acl.ldif | 1 + base/ca/shared/conf/acl.properties | 1 + .../src/com/netscape/ca/CertificateAuthority.java | 63 ++++++++++++++++++ .../dogtagpki/server/ca/rest/AuthorityService.java | 35 ++++++++-- .../certsrv/authority/AuthorityClient.java | 5 ++ .../certsrv/authority/AuthorityResource.java | 8 +++ .../netscape/certsrv/ca/CAEnabledException.java | 15 +++++ .../netscape/certsrv/ca/ICertificateAuthority.java | 6 ++ .../netscape/cmstools/authority/AuthorityCLI.java | 1 + .../cmstools/authority/AuthorityRemoveCLI.java | 74 ++++++++++++++++++++++ 10 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 base/common/src/com/netscape/certsrv/ca/CAEnabledException.java create mode 100644 base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 54c9f1d5c64b6578de83f1b7ffdff922a69975f4..27d89a3131e9220b4c03b17aad14b364fe97ff20 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -59,3 +59,4 @@ resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="A resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities +resourceACLS: certServer.ca.authorities:delete:allow (delete) group="Administrators":Administrators may delete lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index f0b5b9f650ad2fc4bde531ade94347a7280d3089..8b3e9d0eea09e5e3ab8271888ab0532d47b69348 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -25,3 +25,4 @@ authorities.create = certServer.ca.authorities,create authorities.list = certServer.ca.authorities,list authorities.modify = certServer.ca.authorities,modify authorities.read = certServer.ca.authorities,read +authorities.delete = certServer.ca.authorities,delete diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index b3663ed1d497d03651ad1fa753b4e23ae4aea6b0..af6a2af1109efbdbeeb122cf6e877fc68ee4fd3e 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -50,9 +50,11 @@ import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; +import org.mozilla.jss.crypto.CryptoStore; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.KeyPairAlgorithm; import org.mozilla.jss.crypto.KeyPairGenerator; +import org.mozilla.jss.crypto.NoSuchItemOnTokenException; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -68,6 +70,7 @@ import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CAEnabledException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; @@ -2624,4 +2627,64 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + public void deleteAuthority() throws EBaseException { + if (isHostAuthority()) + throw new CATypeException("Cannot delete the host CA"); + + if (authorityEnabled) + throw new CAEnabledException("Must disable CA before deletion"); + + caMap.remove(authorityID); + shutdown(); + + // delete ldap entry + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + try { + conn.delete(dn); + } catch (LDAPException e) { + throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + CryptoManager cryptoManager; + try { + cryptoManager = CryptoManager.getInstance(); + } catch (CryptoManager.NotInitializedException e) { + // can't happen + throw new ECAException("CryptoManager not initialized"); + } + + // delete cert + CryptoStore cryptoStore = + cryptoManager.getInternalKeyStorageToken().getCryptoStore(); + try { + cryptoStore.deleteCert(mCaX509Cert); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteCA: cert is not on token: " + e); + // if the cert isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteCA: TokenExcepetion while deleting cert: " + e); + throw new ECAException("TokenException while deleting cert: " + e); + } + + // delete key + try { + cryptoStore.deletePrivateKey(mSigningUnit.getPrivateKey()); + } catch (NoSuchItemOnTokenException e) { + CMS.debug("deleteCA: private key is not on token: " + e); + // if the key isn't there, never mind + } catch (TokenException e) { + CMS.debug("deleteCA: TokenExcepetion while deleting private key: " + e); + // TODO don't know what causes this yet, or how to + // prevent it. + //throw new ECAException("TokenException while deleting private key: " + e); + } + } + } diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java index 820f8ab6499eed9fdb8e3d8d782df64c71ad1fc3..07fa28f53fde37dae46c0e2279af27f88b2616c7 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -42,6 +42,7 @@ import com.netscape.certsrv.base.ForbiddenException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.ResourceNotFoundException; import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CAEnabledException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -99,7 +100,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ca = hostCA.getCA(aid); @@ -116,7 +117,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -143,7 +144,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -198,7 +199,7 @@ public class AuthorityService extends PKIService implements AuthorityResource { try { aid = new AuthorityID(aidString); } catch (IllegalArgumentException e) { - throw new BadRequestException("Bad CA ID: " + aidString); + throw new BadRequestException("Bad AuthorityID: " + aidString); } ICertificateAuthority ca = hostCA.getCA(aid); @@ -232,6 +233,32 @@ public class AuthorityService extends PKIService implements AuthorityResource { new AuthorityData(null, null, null, null, false, null)); } + @Override + public Response deleteCA(String aidString) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad AuthorityID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.deleteAuthority(); + return createNoContentResponse(); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (CAEnabledException e) { + throw new ConflictingOperationException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + private static AuthorityData readAuthorityData(ICertificateAuthority ca) throws PKIException { String dn; diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java index 86de3352e2424211125c146edf759481448a2694..5a80877ca4479058ba88005421c46d092b2df6a6 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityClient.java @@ -59,4 +59,9 @@ public class AuthorityClient extends Client { return client.getEntity(response, AuthorityData.class); } + public void deleteCA(String aidString) { + Response response = proxy.deleteCA(aidString); + client.getEntity(response, Void.class); + } + } diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java index eaef903db444512dbea6c87b11800130d94a944d..c6dc696247122b5f07802696c38c2f3517341106 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -1,5 +1,6 @@ package com.netscape.certsrv.authority; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -93,4 +94,11 @@ public interface AuthorityResource { @ACLMapping("authorities.modify") public Response disableCA(@PathParam("id") String caIDString); + @DELETE + @Path("{id}") + @ClientResponseType(entityType=Void.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.delete") + public Response deleteCA(@PathParam("id") String caIDString); + } diff --git a/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java b/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..4c85276f3bf71bb640a3468f4ed4be4a6aa0180c --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CAEnabledException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be performed because + * the CA to which the operation pertains is enabled. + */ +public class CAEnabledException extends ECAException { + + private static final long serialVersionUID = 1056602856006912665L; + + public CAEnabledException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index 31d5c9277a63ca7c916f39651300b0c9a9061c1e..96bc392294f27d57d9795c2b1b793b8cbc001fda 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -583,4 +583,10 @@ public interface ICertificateAuthority extends ISubsystem { */ public void modifyAuthority(Boolean enabled, String desc) throws EBaseException; + + /** + * Delete this lightweight CA. + */ + public void deleteAuthority() + throws EBaseException; } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java index 99d38ad1b989e171079df78ddd8b2774817ccb33..4fbcfef760086928b2e0e75fe4fc56f1b249b5fd 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -17,6 +17,7 @@ public class AuthorityCLI extends CLI { addModule(new AuthorityCreateCLI(this)); addModule(new AuthorityDisableCLI(this)); addModule(new AuthorityEnableCLI(this)); + addModule(new AuthorityRemoveCLI(this)); } public String getFullName() { diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..24918a34510e92ce8a22f4e85e4800a073b4c87c --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityRemoveCLI.java @@ -0,0 +1,74 @@ +package com.netscape.cmstools.authority; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +public class AuthorityRemoveCLI extends CLI { + + public AuthorityCLI authorityCLI; + + public AuthorityRemoveCLI(AuthorityCLI authorityCLI) { + super("del", "Delete Authority", authorityCLI); + this.authorityCLI = authorityCLI; + + options.addOption(null, "force", false, "Force delete"); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " ", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + if (cmdArgs.length != 1) { + if (cmdArgs.length < 1) + System.err.println("No ID specified."); + else + System.err.println("Too many arguments."); + printHelp(); + System.exit(-1); + } + + if (!cmd.hasOption("force")) { + System.out.print("Are you sure (Y/N)? "); + System.out.flush(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String line = reader.readLine(); + if (!line.equalsIgnoreCase("Y")) { + System.exit(-1); + } + } + + String aidString = cmdArgs[0]; + authorityCLI.authorityClient.deleteCA(aidString); + MainCLI.printMessage("Deleted authority \"" + aidString + "\""); + } + +} -- 2.4.3 From cheimes at redhat.com Wed Sep 30 16:33:27 2015 From: cheimes at redhat.com (Christian Heimes) Date: Wed, 30 Sep 2015 18:33:27 +0200 Subject: [Pki-devel] [PATCH ] 041 Python packaging of PKI client library In-Reply-To: <560BDEB7.8030007@redhat.com> References: <560A9FB2.7060605@redhat.com> <560BDEB7.8030007@redhat.com> Message-ID: <560C0ED7.50201@redhat.com> On 2015-09-30 15:08, Christian Heimes wrote: > On 2015-09-29 16:26, Christian Heimes wrote: >> Hi, >> >> Ade has asked me to modify setup.py so we can upload the client library >> parts of Dogtag on the Python package index. >> >> I ran into trouble with setuptools and had to remove setuptools again. >> The latest version of setuptools broke the data_files option. > > The new patch provides a separate setup.py just for the client libs. > It's cleaner and less of a hack than a combined setup.py with two build > flavors. Ade likes to have an alias to just build the packages. Christian -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-cheimes-0041-3-Python-packaging-of-PKI-client-library.patch Type: text/x-patch Size: 5761 bytes Desc: not available URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 455 bytes Desc: OpenPGP digital signature URL: From jmagne at redhat.com Wed Sep 30 17:36:13 2015 From: jmagne at redhat.com (John Magne) Date: Wed, 30 Sep 2015 13:36:13 -0400 (EDT) Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <1443586643.5988.13.camel@redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> <1443586643.5988.13.camel@redhat.com> Message-ID: <1493987207.60583803.1443634573580.JavaMail.zimbra@redhat.com> ----- Original Message ----- > From: "Ade Lee" > To: "Fraser Tweedale" , pki-devel at redhat.com > Sent: Tuesday, September 29, 2015 9:17:23 PM > Subject: Re: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion > > ACK on synchronization patch. > > On the delete patch, a few comments. > > 1) It would be good to know what is going on with the exception. > > 2) The new acls and mappings reminded me that upgrade scripts are > required to allow old 10.x servers to be able to create subcas. Please > open a ticket if one does not yet exist. > > 3) It would be good to have a "Are you sure?" dialog on the CLI (with > relevant override option). > > 4) Please open an auditing ticket if one is not already opened. We > definitely need to be auditing everything here in detail. > Couple more: - Would it make sense to allow all in progress requests to finish before detonation? - What about a reversible delete where everything gets archived like when one deletes a program off of a DVR which can be recovered later. > 5) I have been thinking about ways to restrict delete. We should > discuss and decide on options. Some ideas: > > a) Add CS.cfg option to disable deletes (for production say). > b) Add optional field (deletable) to the CA entry. This can be > set by the creating admin to be True for test environments or > cases where we know the environment will be short lived, or > False for long lived CAs. Default could be configurable. > > CAs could still be deleted, but only by doing something > out-of-band --like modifying the db entry using pki-server > commands or similar. > c) Requiring CAs to be disabled before deleting them. > d) Setting a separate ACL for delete, so that it would be easier > for admins to set special permissions for delete. > ... others? > > Ade > > On Wed, 2015-09-30 at 01:25 +1000, Fraser Tweedale wrote: > > The attached patches fix some incorrect synchronization of the > > lightweight CAs index (patch 0048) and implement deletion of > > lightweight CAs (patch 0049). > > > > These patches replace earlier patches 0048 and 0049 which I rescind. > > > > There is a commented out throw in > > CertificateAuthority.deleteAuthority(); I don't yet understand what > > causes this failure case but a) everything seems to work (at least > > with the small numbers of lightweight CAs I've tested with) and b) > > I'm seeking clarification from NSS experts on the matter, so stay > > tuned. > > > > Cheers, > > Fraser > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com > https://www.redhat.com/mailman/listinfo/pki-devel > From cfu at redhat.com Wed Sep 30 18:26:03 2015 From: cfu at redhat.com (Christina Fu) Date: Wed, 30 Sep 2015 11:26:03 -0700 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <20150930140003.GY16937@dhcp-40-8.bne.redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> <1443586643.5988.13.camel@redhat.com> <20150930083557.GX16937@dhcp-40-8.bne.redhat.com> <20150930140003.GY16937@dhcp-40-8.bne.redhat.com> Message-ID: <560C293B.2080801@redhat.com> Hi Fraser, Ade caught me on irc for some feedback. I have not had chance to look at your patches, but I did get the gist of the subca delete issues from him. Two key suggestions I have: 1. make sure the action is audited to its parent (audit log preserved) 2. make sure revocation is taken cared of of the subca's signing cert (and therefore invalidating all its signed certs) - and make sure the root CA is never deleted, so that the crls could be preserved and referenced to; - and note that ocsp will no longer work for the subca that is deleted, as the signing cert is gone for good Regarding keeping the root CA, we had discussion on possibly keeping it in a "mothballed state"...I'll let Ade add to this. thanks, Christina On 09/30/2015 07:00 AM, Fraser Tweedale wrote: > Updated patch attached. Comments inline. > > On Wed, Sep 30, 2015 at 06:35:57PM +1000, Fraser Tweedale wrote: >>> 3) It would be good to have a "Are you sure?" dialog on the CLI (with >>> relevant override option). >>> >> Will do. >> > Done. > >>> 5) I have been thinking about ways to restrict delete. We should >>> discuss and decide on options. Some ideas: >>> >>> a) Add CS.cfg option to disable deletes (for production say). >>> >> Disagree; don't want more config in flat files. Having the knob in >> the database would be better but I prefer a combination of other >> options (see below). >> >>> b) Add optional field (deletable) to the CA entry. This can be >>> set by the creating admin to be True for test environments or >>> cases where we know the environment will be short lived, or >>> False for long lived CAs. Default could be configurable. >>> >>> CAs could still be deleted, but only by doing something >>> out-of-band --like modifying the db entry using pki-server >>> commands or similar. >>> >>> c) Requiring CAs to be disabled before deleting them. >>> >> I'm in favour of this. >> >>> d) Setting a separate ACL for delete, so that it would be easier >>> for admins to set special permissions for delete. >>> >> And in favour of this. >> >>> ... others? >>> >> I like (c) plus (d) plus perhaps a pkispawn knob that controls >> whether the admin-can-delete ACL gets added at the beginning. >> >> Let me know what you think and thanks for your feedback! >> > (c) and (d) are implemented in updated patch. If you agree with (c) > plus (d) plus pkispawn knob (I guess we'll call that (e)), I'll file > a ticket for (e). > > Cheers, > Fraser > > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com > https://www.redhat.com/mailman/listinfo/pki-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From edewata at redhat.com Wed Sep 30 19:40:05 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Wed, 30 Sep 2015 14:40:05 -0500 Subject: [Pki-devel] [PATCH] 647 Added default subject DN for pki client-cert-request. Message-ID: <560C3A95.2000501@redhat.com> The pki client-cert-request CLI has been modified to generate a default subject DN if it's not specified. The man page has been updated accordingly. https://fedorahosted.org/pki/ticket/1463 -- Endi S. Dewata -------------- next part -------------- From 249b85ce6fcbc772acc54ce76103793b44ad303a Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Sun, 27 Sep 2015 17:23:48 +0200 Subject: [PATCH] Added default subject DN for pki client-cert-request. The pki client-cert-request CLI has been modified to generate a default subject DN if it's not specified. The man page has been updated accordingly. https://fedorahosted.org/pki/ticket/1463 --- base/java-tools/man/man1/pki-client.1 | 10 ++++++--- .../cmstools/client/ClientCertRequestCLI.java | 24 ++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/base/java-tools/man/man1/pki-client.1 b/base/java-tools/man/man1/pki-client.1 index 65e61855574e0801bdbf936b6299e54ee3857beb..e659397a7fba4b034fae579a350f9285d9c3bde2 100644 --- a/base/java-tools/man/man1/pki-client.1 +++ b/base/java-tools/man/man1/pki-client.1 @@ -21,7 +21,7 @@ pki-client \- Command-Line Interface for managing the security database on Certi \fBpki\fR [CLI options] \fBclient\fR \fBpki\fR [CLI options] \fBclient-init\fR [command options] \fBpki\fR [CLI options] \fBclient-cert-find\fR [command options] -\fBpki\fR [CLI options] \fBclient-cert-request\fR [command options] +\fBpki\fR [CLI options] \fBclient-cert-request\fR [subject DN] [command options] \fBpki\fR [CLI options] \fBclient-cert-import\fR [nickname] [command options] \fBpki\fR [CLI options] \fBclient-cert-mod\fR [command options] \fBpki\fR [CLI options] \fBclient-cert-show\fR [command options] @@ -47,7 +47,7 @@ This command is to create a new security database for the client. This command is to list certificates in the client security database. .RE .PP -\fBpki\fR [CLI options] \fBclient-cert-request\fR [command options] +\fBpki\fR [CLI options] \fBclient-cert-request\fR [subject DN] [command options] .RS 4 This command is to generate and submit a certificate request. .RE @@ -88,7 +88,11 @@ To view certificates in the security database: To request a certificate: -.B pki -d -c client-cert-request +.B pki -d -c client-cert-request [subject DN] + +Some certificate profiles may require authentication using username and password. They can be specified +using --username and --password options. If the subject DN is not specififed the CLI will generate a +default subject DN "UID=". To import a certificate from a file into the security database: diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index 370a7be5b1d09b8b445a82fce3c2185607e9ccae..3ec4745e6a38058d7bb697df5f367c8831bfa216 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -68,7 +68,7 @@ public class ClientCertRequestCLI extends CLI { } public void printHelp() { - formatter.printHelp(getFullName() + " [OPTIONS...]", options); + formatter.printHelp(getFullName() + " [Subject DN] [OPTIONS...]", options); } public void createOptions() { @@ -151,14 +151,23 @@ public class ClientCertRequestCLI extends CLI { System.exit(-1); } - if (cmdArgs.length < 1) { - System.err.println("Error: Missing subject DN."); - printHelp(); - System.exit(-1); + String certRequestUsername = cmd.getOptionValue("username"); + + String subjectDN; + + if (cmdArgs.length == 0) { + if (certRequestUsername == null) { + System.err.println("Error: Missing subject DN or request username."); + printHelp(); + System.exit(-1); + } + + subjectDN = "UID=" + certRequestUsername; + + } else { + subjectDN = cmdArgs[0]; } - String subjectDN = cmdArgs[0]; - // pkcs10, crmf String requestType = cmd.getOptionValue("type", "pkcs10"); @@ -316,7 +325,6 @@ public class ClientCertRequestCLI extends CLI { } } - String certRequestUsername = cmd.getOptionValue("username"); if (certRequestUsername != null) { request.setAttribute("uid", certRequestUsername); } -- 2.4.3 From alee at redhat.com Wed Sep 30 19:44:30 2015 From: alee at redhat.com (Ade Lee) Date: Wed, 30 Sep 2015 15:44:30 -0400 Subject: [Pki-devel] [PATCH ] 041 Python packaging of PKI client library In-Reply-To: <560C0ED7.50201@redhat.com> References: <560A9FB2.7060605@redhat.com> <560BDEB7.8030007@redhat.com> <560C0ED7.50201@redhat.com> Message-ID: <1443642270.5988.15.camel@redhat.com> For some reason, I still see a whitespace error -- new blank line at EOF. Other than that, ACK. On Wed, 2015-09-30 at 18:33 +0200, Christian Heimes wrote: > On 2015-09-30 15:08, Christian Heimes wrote: > > On 2015-09-29 16:26, Christian Heimes wrote: > > > Hi, > > > > > > Ade has asked me to modify setup.py so we can upload the client > > > library > > > parts of Dogtag on the Python package index. > > > > > > I ran into trouble with setuptools and had to remove setuptools > > > again. > > > The latest version of setuptools broke the data_files option. > > > > The new patch provides a separate setup.py just for the client > > libs. > > It's cleaner and less of a hack than a combined setup.py with two > > build > > flavors. > > Ade likes to have an alias to just build the packages. > > Christian > From alee at redhat.com Wed Sep 30 19:59:55 2015 From: alee at redhat.com (Ade Lee) Date: Wed, 30 Sep 2015 15:59:55 -0400 Subject: [Pki-devel] [PATCH] 0048-0049 Lightweight CAs: implement deletion In-Reply-To: <560C293B.2080801@redhat.com> References: <20150929152533.GV16937@dhcp-40-8.bne.redhat.com> <1443586643.5988.13.camel@redhat.com> <20150930083557.GX16937@dhcp-40-8.bne.redhat.com> <20150930140003.GY16937@dhcp-40-8.bne.redhat.com> <560C293B.2080801@redhat.com> Message-ID: <1443643195.5988.24.camel@redhat.com> On Wed, 2015-09-30 at 11:26 -0700, Christina Fu wrote: See other comments below, but the gist of Christina's comments are: 1. We need to look up the parent and revoke the subca signing cert. 2. We need to restrict Delete to Leaf CAs. So if you have Host CA -> sub ca1 -> sub ca2 -> subca 3, you cannot delete sub CA 1 without deleting subca3 and subca2 (in that order). 3. The mothballed state Christina refers to is what we now call the disabled state. In this case, we should not be able to issue new certs (including subca certs). We should however still be able to revoke certs and publish CRLs. > Hi Fraser, > > Ade caught me on irc for some feedback. I have not had chance to > look at your patches, but I did get the gist of the subca delete > issues from him. > Two key suggestions I have: > 1. make sure the action is audited to its parent (audit log > preserved) > 2. make sure revocation is taken cared of of the subca's signing cert > (and therefore invalidating all its signed certs) > - and make sure the root CA is never deleted, so that the crls > could be preserved and referenced to; > - and note that ocsp will no longer work for the subca that is > deleted, as the signing cert is gone for good > > Regarding keeping the root CA, we had discussion on possibly keeping > it in a "mothballed state"...I'll let Ade add to this. > > thanks, > Christina > > On 09/30/2015 07:00 AM, Fraser Tweedale wrote: > > Updated patch attached. Comments inline. > > > > On Wed, Sep 30, 2015 at 06:35:57PM +1000, Fraser Tweedale wrote: > > > > 3) It would be good to have a "Are you sure?" dialog on the CLI > > > > (with > > > > relevant override option). > > > > > > > Will do. > > > > > Done. > > > > > > 5) I have been thinking about ways to restrict delete. We > > > > should > > > > discuss and decide on options. Some ideas: > > > > > > > > a) Add CS.cfg option to disable deletes (for production > > > > say). > > > > > > > Disagree; don't want more config in flat files. Having the knob > > > in > > > the database would be better but I prefer a combination of other > > > options (see below). > > > > > > > b) Add optional field (deletable) to the CA entry. This can > > > > be > > > > set by the creating admin to be True for test > > > > environments or > > > > cases where we know the environment will be short lived, > > > > or > > > > False for long lived CAs. Default could be configurable. > > > > > > > > CAs could still be deleted, but only by doing something > > > > out-of-band --like modifying the db entry using pki > > > > -server > > > > commands or similar. > > > > > > > > c) Requiring CAs to be disabled before deleting them. > > > > > > > I'm in favour of this. > > > > > > > d) Setting a separate ACL for delete, so that it would be > > > > easier > > > > for admins to set special permissions for delete. > > > > > > > And in favour of this. > > > > > > > ... others? > > > > > > > I like (c) plus (d) plus perhaps a pkispawn knob that controls > > > whether the admin-can-delete ACL gets added at the beginning. > > > > > > Let me know what you think and thanks for your feedback! > > > > > (c) and (d) are implemented in updated patch. If you agree with > > (c) > > plus (d) plus pkispawn knob (I guess we'll call that (e)), I'll > > file > > a ticket for (e). > > I'm OK with (c) and (d) and others appear to be too. Archiving is difficult because it basically won't work with an HSM. I suppose this is just equivalent to the controls we have in revoking the host CA signing cert. I don't think (e) is needed. > > Cheers, > > Fraser > > > > > > > > > > > > > > > > > > > > _______________________________________________ > > Pki-devel mailing list > > Pki-devel at redhat.com> > https://www.redhat.com/mailman/listinfo/pki-devel > > > > > > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com> https://www.redhat.com/mailman/listinfo/pki-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From alee at redhat.com Wed Sep 30 20:20:52 2015 From: alee at redhat.com (Ade Lee) Date: Wed, 30 Sep 2015 16:20:52 -0400 Subject: [Pki-devel] [PATCH] add delete_ca to the python client Message-ID: <1443644452.5988.26.camel@redhat.com> This is the python client part to Fraser's delete_ca patch. Ade -------------- next part -------------- A non-text attachment was scrubbed... Name: pki-vakwetu-0272-Add-delete_ca-functionality-to-the-Python-API.patch Type: text/x-patch Size: 3861 bytes Desc: not available URL: From cfu at redhat.com Wed Sep 30 21:17:15 2015 From: cfu at redhat.com (Christina Fu) Date: Wed, 30 Sep 2015 14:17:15 -0700 Subject: [Pki-devel] [PATCH] 647 Added default subject DN for pki client-cert-request. In-Reply-To: <560C3A95.2000501@redhat.com> References: <560C3A95.2000501@redhat.com> Message-ID: <560C515B.9000108@redhat.com> Hi Endi, Thanks for adding this, and sorry for adding the comment after the fact... Just one suggestion in the man pages where subjectDN is described. You might want to add a note that it is up to the profile to accept or ignore the user specified subjetDN. This way, users won't be surprised if what they specified is not what they see in the end. conditional ACK on that note in man pages. thanks, Christina On 09/30/2015 12:40 PM, Endi Sukma Dewata wrote: > The pki client-cert-request CLI has been modified to generate a > default subject DN if it's not specified. The man page has been > updated accordingly. > > https://fedorahosted.org/pki/ticket/1463 > > > > _______________________________________________ > Pki-devel mailing list > Pki-devel at redhat.com > https://www.redhat.com/mailman/listinfo/pki-devel -------------- next part -------------- An HTML attachment was scrubbed... URL: From edewata at redhat.com Wed Sep 30 23:33:34 2015 From: edewata at redhat.com (Endi Sukma Dewata) Date: Wed, 30 Sep 2015 18:33:34 -0500 Subject: [Pki-devel] [PATCH] 647 Added default subject DN for pki client-cert-request. In-Reply-To: <560C515B.9000108@redhat.com> References: <560C3A95.2000501@redhat.com> <560C515B.9000108@redhat.com> Message-ID: <560C714E.6070802@redhat.com> On 9/30/2015 4:17 PM, Christina Fu wrote: > Hi Endi, > Thanks for adding this, and sorry for adding the comment after the fact... > > Just one suggestion in the man pages where subjectDN is described. You > might want to add a note that it is up to the profile to accept or > ignore the user specified subjetDN. This way, users won't be surprised > if what they specified is not what they see in the end. > > conditional ACK on that note in man pages. > > thanks, > Christina Thanks! I updated the man page and pushed it to master. -- Endi S. Dewata -------------- next part -------------- >From db403e74eb8a21ccd6192c57768ece6211aa2c79 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Sun, 27 Sep 2015 17:23:48 +0200 Subject: [PATCH] Added default subject DN for pki client-cert-request. The pki client-cert-request CLI has been modified to generate a default subject DN if it's not specified. The man page has been updated accordingly. https://fedorahosted.org/pki/ticket/1463 --- base/java-tools/man/man1/pki-client.1 | 17 +++++++++++---- .../cmstools/client/ClientCertRequestCLI.java | 24 ++++++++++++++-------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/base/java-tools/man/man1/pki-client.1 b/base/java-tools/man/man1/pki-client.1 index 65e61855574e0801bdbf936b6299e54ee3857beb..da5de7cbf91ca6d14f81599c821a4e430093e8c4 100644 --- a/base/java-tools/man/man1/pki-client.1 +++ b/base/java-tools/man/man1/pki-client.1 @@ -21,7 +21,7 @@ pki-client \- Command-Line Interface for managing the security database on Certi \fBpki\fR [CLI options] \fBclient\fR \fBpki\fR [CLI options] \fBclient-init\fR [command options] \fBpki\fR [CLI options] \fBclient-cert-find\fR [command options] -\fBpki\fR [CLI options] \fBclient-cert-request\fR [command options] +\fBpki\fR [CLI options] \fBclient-cert-request\fR [subject DN] [command options] \fBpki\fR [CLI options] \fBclient-cert-import\fR [nickname] [command options] \fBpki\fR [CLI options] \fBclient-cert-mod\fR [command options] \fBpki\fR [CLI options] \fBclient-cert-show\fR [command options] @@ -47,7 +47,7 @@ This command is to create a new security database for the client. This command is to list certificates in the client security database. .RE .PP -\fBpki\fR [CLI options] \fBclient-cert-request\fR [command options] +\fBpki\fR [CLI options] \fBclient-cert-request\fR [subject DN] [command options] .RS 4 This command is to generate and submit a certificate request. .RE @@ -82,13 +82,22 @@ To create a new database execute the following command: .B pki -d -c client-init -To view certificates in the security database: +To list certificates in the security database: .B pki -d -c client-cert-find To request a certificate: -.B pki -d -c client-cert-request +.B pki -d -c client-cert-request [subject DN] + +The subject DN requirement depends on the certificate profile being requested. +Some profiles may require the user to provide a subject DN in a certain +format. Some other profiles may generate their own subject DN. + +Certain profiles may also require additional authentication. To authenticate, +a username and a password can be specified using the --username and --password +options, respectively. If the subject DN is not specififed the CLI may use the +username to generate a default subject DN "UID=". To import a certificate from a file into the security database: diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index 370a7be5b1d09b8b445a82fce3c2185607e9ccae..3ec4745e6a38058d7bb697df5f367c8831bfa216 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -68,7 +68,7 @@ public class ClientCertRequestCLI extends CLI { } public void printHelp() { - formatter.printHelp(getFullName() + " [OPTIONS...]", options); + formatter.printHelp(getFullName() + " [Subject DN] [OPTIONS...]", options); } public void createOptions() { @@ -151,14 +151,23 @@ public class ClientCertRequestCLI extends CLI { System.exit(-1); } - if (cmdArgs.length < 1) { - System.err.println("Error: Missing subject DN."); - printHelp(); - System.exit(-1); + String certRequestUsername = cmd.getOptionValue("username"); + + String subjectDN; + + if (cmdArgs.length == 0) { + if (certRequestUsername == null) { + System.err.println("Error: Missing subject DN or request username."); + printHelp(); + System.exit(-1); + } + + subjectDN = "UID=" + certRequestUsername; + + } else { + subjectDN = cmdArgs[0]; } - String subjectDN = cmdArgs[0]; - // pkcs10, crmf String requestType = cmd.getOptionValue("type", "pkcs10"); @@ -316,7 +325,6 @@ public class ClientCertRequestCLI extends CLI { } } - String certRequestUsername = cmd.getOptionValue("username"); if (certRequestUsername != null) { request.setAttribute("uid", certRequestUsername); } -- 2.4.3 From lachen at redhat.com Wed Sep 2 17:21:28 2015 From: lachen at redhat.com (Lan Chen) Date: Wed, 02 Sep 2015 17:21:28 -0000 Subject: [Pki-devel] Dogtag 10.1 API In-Reply-To: Message-ID: <625987450.12482137.1441214487694.JavaMail.zimbra@redhat.com> Hi All, Is there good documentation on Dogtag 10.1 API somewhere? Lan ----- Original Message ----- From: "Ryan Murray" To: "Lan Chen" Sent: Wednesday, September 2, 2015 9:53:09 AM Subject: Re: Dogtag 10.1 There is a massive difference between the API's, I checked before raising the concern. By massive I mean they are all different. Example: 10.1 Noun=certs 10.2 Noun=certificates None of the other nouns are working as documented by 10.2. On Sep 2, 2015 9:50 AM, "Lan Chen" < lachen at redhat.com > wrote: Ryan, I just checked with Paul, we need it installed on RHEL. Could you see if the APIs documented for 10.2 also works for 10.1, there shouldn't be too big of a difference between the versions. From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 9:38:25 AM Subject: Re: Dogtag 10.1 The packages are not avaliable to the RHEL 7 channels or EPEL channels. From a technical standpoint I would need to spend time getting the exact limitations, but as it stands I would have to install tons of fedora packages or compile from source to get 10.2 running on RHEL. On Wednesday, September 2, 2015, Lan Chen < lachen at redhat.com > wrote:

I meant why can't 10.2 be on RHEL, and need to be on Fedora? From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 9:33:08 AM Subject: Re: Dogtag 10.1 There is no issue with the install, that was done Monday night. The issue is with the API not being documented. The client had to use fedora in all of their tests due to the newer 10.2 version and better API. On Wednesday, September 2, 2015, Lan Chen < lachen at redhat.com > wrote:
What's the issue on installing Dogtag 10.2 on RHEL? From: "Ryan Murray" < rmurray at stonedoorgroup.com > To: "Lan Chen" < lachen at redhat.com > Sent: Wednesday, September 2, 2015 8:02:27 AM Subject: Dogtag 10.1 Hi Lan, Could you please see if there is anyone at Red Hat technical that would have any documentation on the dog tag 10.1 API? It is not documented online that I can find. Hunting through source code to find undocumented API calls is a stretch on the SOW, and installing Fedora is a change to needs Red Hat's OK. Thanks
-------------- next part -------------- An HTML attachment was scrubbed... URL: