summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFraser Tweedale <ftweedal@redhat.com>2015-05-14 01:46:06 -0400
committerFraser Tweedale <ftweedal@redhat.com>2015-07-06 11:25:01 -0400
commitf5fa6e1a0d0d5dd66ba23937d16665b5793e20d3 (patch)
tree0ff160a47f52efe0af4d4e4536b1b122a78d09d6
parent2a976334c2160c91a61fb0c477777e7adbbd3150 (diff)
downloadfreeipa-feature/subca.zip
freeipa-feature/subca.tar.gz
freeipa-feature/subca.tar.xz
Add subca pluginfeature/subca
Part of: https://fedorahosted.org/freeipa/ticket/4559
-rw-r--r--ACI.txt8
-rw-r--r--API.txt52
-rw-r--r--TODO5
-rw-r--r--VERSION4
-rw-r--r--install/conf/ipa-pki-proxy.conf4
-rw-r--r--install/share/60certificate-profiles.ldif1
-rw-r--r--install/share/bootstrap-template.ldif6
-rw-r--r--install/updates/41-subca.update4
-rw-r--r--install/updates/Makefile.am1
-rw-r--r--ipalib/constants.py1
-rw-r--r--ipalib/plugins/subca.py184
-rw-r--r--ipaserver/install/cainstance.py8
-rw-r--r--ipaserver/install/server/upgrade.py16
-rw-r--r--ipaserver/plugins/dogtag.py23
14 files changed, 309 insertions, 8 deletions
diff --git a/ACI.txt b/ACI.txt
index 9206d76..b92d971 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -270,6 +270,14 @@ dn: cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example
aci: (targetattr = "krblastpwdchange || krbpasswordexpiration || krbprincipalkey || userpassword")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Reset Preserved User password";allow (read,search,write) groupdn = "ldap:///cn=System: Reset Preserved User password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (target_to = "ldap:///cn=users,cn=accounts,dc=ipa,dc=example")(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Undelete User";allow (moddn) groupdn = "ldap:///cn=System: Undelete User,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=subcas,cn=ca,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Add Sub-CA";allow (add) groupdn = "ldap:///cn=System: Add Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=subcas,cn=ca,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Delete Sub-CA";allow (delete) groupdn = "ldap:///cn=System: Delete Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=subcas,cn=ca,dc=ipa,dc=example
+aci: (targetattr = "cn || description")(targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Modify Sub-CA";allow (write) groupdn = "ldap:///cn=System: Modify Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=subcas,cn=ca,dc=ipa,dc=example
+aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Read Sub-CAs";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=ipasudocmd)")(version 3.0;acl "permission:System: Add Sudo Command";allow (add) groupdn = "ldap:///cn=System: Add Sudo Command,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index e226712..454fdfc 100644
--- a/API.txt
+++ b/API.txt
@@ -4393,6 +4393,58 @@ option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
+command: subca_add
+args: 1,6,3
+arg: Str('cn', attribute=True, cli_name='handle', multivalue=False, primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=True)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: subca_find
+args: 1,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='handle', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: subca_mod
+args: 1,9,3
+arg: Str('cn', attribute=True, cli_name='handle', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False)
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: subca_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='handle', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
command: sudocmd_add
args: 1,7,3
arg: Str('sudocmd', attribute=True, cli_name='command', multivalue=False, primary_key=True, required=True)
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..c3cc42f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+SubCAs TODO:
+
+- bump pki dep to version with sub-cas
+- key replication via custodia
+- see other TODOs in code
diff --git a/VERSION b/VERSION
index 266a04a..febc089 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=137
-# Last change: mbabinsk: Commands to manage user/host/service certificates
+IPA_API_VERSION_MINOR=138
+# Last change: ftweedal - add subca plugin
diff --git a/install/conf/ipa-pki-proxy.conf b/install/conf/ipa-pki-proxy.conf
index 4b5b6f7..4e31793 100644
--- a/install/conf/ipa-pki-proxy.conf
+++ b/install/conf/ipa-pki-proxy.conf
@@ -1,4 +1,4 @@
-# VERSION 8 - DO NOT REMOVE THIS LINE
+# VERSION 9 - DO NOT REMOVE THIS LINE
ProxyRequests Off
@@ -27,7 +27,7 @@ ProxyRequests Off
</LocationMatch>
# matches for CA REST API
-<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/admin/kraconnector/remove">
+<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/subca|^/ca/rest/admin/kraconnector/remove">
NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
NSSVerifyClient optional
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
diff --git a/install/share/60certificate-profiles.ldif b/install/share/60certificate-profiles.ldif
index 798c3a3..5235cd8 100644
--- a/install/share/60certificate-profiles.ldif
+++ b/install/share/60certificate-profiles.ldif
@@ -6,3 +6,4 @@ attributeTypes: (2.16.840.1.113730.3.8.21.1.4 NAME 'ipaCaCategory' DESC 'Additio
attributeTypes: (2.16.840.1.113730.3.8.21.1.5 NAME 'ipaCertProfileCategory' DESC 'Additional classification for certificate profiles' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.2' )
objectClasses: (2.16.840.1.113730.3.8.21.2.1 NAME 'ipaCertProfile' SUP top STRUCTURAL MUST ( cn $ description $ ipaCertProfileStoreIssued ) X-ORIGIN 'IPA v4.2' )
objectClasses: (2.16.840.1.113730.3.8.21.2.2 NAME 'ipaCaAcl' SUP ipaAssociation STRUCTURAL MUST cn MAY ( ipaCaCategory $ ipaCertProfileCategory $ userCategory $ hostCategory $ serviceCategory $ ipaMemberCa $ ipaMemberCertProfile $ memberService ) X-ORIGIN 'IPA v4.2' )
+objectClasses: (2.16.840.1.113730.3.8.21.2.3 NAME 'ipaSubCa' SUP top STRUCTURAL MUST cn MAY description X-ORIGIN 'IPA v4.2' )
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index 2387f22..710c134 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -447,3 +447,9 @@ changetype: add
objectClass: nsContainer
objectClass: top
cn: caacls
+
+dn: cn=subcas,cn=ca,$SUFFIX
+changetype: add
+objectClass: nsContainer
+objectClass: top
+cn: subcas
diff --git a/install/updates/41-subca.update b/install/updates/41-subca.update
new file mode 100644
index 0000000..dd094ff
--- /dev/null
+++ b/install/updates/41-subca.update
@@ -0,0 +1,4 @@
+dn: cn=subcas,cn=ca,$SUFFIX
+default: objectClass: nsContainer
+default: objectClass: top
+default: cn: subcas
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 2693e4f..3010a19 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -35,6 +35,7 @@ app_DATA = \
40-certprofile.update \
40-otp.update \
41-caacl.update \
+ 41-subca.update \
45-roles.update \
50-7_bit_check.update \
50-dogtag10-migration.update \
diff --git a/ipalib/constants.py b/ipalib/constants.py
index a062505..48ffc90 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -121,6 +121,7 @@ DEFAULT_CONFIG = (
('container_certprofile', DN(('cn', 'certprofiles'), ('cn', 'ca'))),
('container_topology', DN(('cn', 'topology'), ('cn', 'ipa'), ('cn', 'etc'))),
('container_caacl', DN(('cn', 'caacls'), ('cn', 'ca'))),
+ ('container_subca', DN(('cn', 'subcas'), ('cn', 'ca'))),
# Ports, hosts, and URIs:
('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipalib/plugins/subca.py b/ipalib/plugins/subca.py
new file mode 100644
index 0000000..0d0532a
--- /dev/null
+++ b/ipalib/plugins/subca.py
@@ -0,0 +1,184 @@
+#
+# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
+#
+
+import re
+
+from ipalib import api, Str
+from ipalib import output
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import (
+ LDAPObject, LDAPSearch, LDAPCreate,
+ LDAPUpdate, LDAPRetrieve)
+from ipalib.plugins.cert import ca_enabled_check
+from ipalib import ngettext
+from ipalib.text import _
+
+
+
+__doc__ = _("""
+Manage Subordiate Certificate Authorities
+
+Subordinate Certificate Authorities (Sub-CAs) can be added for scoped issuance
+of X.509 certificates.
+
+CA HANDLE SYNTAX:
+
+A CA handle is a string without spaces or punctuation starting with a letter
+and followed by a sequence of letters, digits or underscore ("_").
+
+EXAMPLES:
+
+ Create a new Sub-CA
+ ipa subca-add puppet --purpose "Puppet Certificates"
+
+ Delete a Sub-CA:
+ ipa subca-del puppet
+
+""")
+
+
+register = Registry()
+
+
+subca_handle_pattern = re.compile('^[a-zA-Z]\w*$')
+
+
+def validate_subca_handle(ugettext, value):
+ """Ensure Sub-CA handle matches form required by CA."""
+ if subca_handle_pattern.match(value) is None:
+ return _('invalid Sub-CA handle')
+ else:
+ return None
+
+
+@register()
+class subca(LDAPObject):
+ """
+ Sub-CA object.
+ """
+ container_dn = api.env.container_subca
+ object_name = _('Subordinate Certificate Authority')
+ object_name_plural = _('Subordinate Certificate Authorities')
+ object_class = ['ipasubca']
+ default_attributes = ['cn', 'description']
+ search_attributes = ['cn', 'description']
+ rdn_is_primary_key = True
+ label = _('Subordinate Certificate Authorities')
+ label_singular = _('Subordinate Certificate Authority')
+
+ takes_params = (
+ Str('cn', validate_subca_handle,
+ primary_key=True,
+ cli_name='handle',
+ label=_('Sub-CA handle'),
+ doc=_('Handle for referencing this Sub-CA'),
+ ),
+ Str('description',
+ required=True,
+ cli_name='desc',
+ label=_('Sub-CA description'),
+ doc=_('Brief description of this Sub-CA'),
+ ),
+ )
+
+ permission_filter_objectclasses = ['ipasubca']
+ managed_permissions = {
+ 'System: Read Sub-CAs': {
+ 'replaces_global_anonymous_aci': True,
+ 'ipapermbindruletype': 'all',
+ 'ipapermright': {'read', 'search', 'compare'},
+ 'ipapermdefaultattr': {
+ 'cn',
+ 'description',
+ 'objectclass',
+ },
+ },
+ 'System: Add Sub-CA': {
+ 'ipapermright': {'add'},
+ 'replaces': [
+ '(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add Sub-CA";allow (add) groupdn = "ldap:///cn=Add Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ 'System: Delete Sub-CA': {
+ 'ipapermright': {'delete'},
+ 'replaces': [
+ '(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete Sub-CA";allow (delete) groupdn = "ldap:///cn=Delete Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ 'System: Modify Sub-CA': {
+ 'ipapermright': {'write'},
+ 'ipapermdefaultattr': {
+ 'cn',
+ 'description',
+ },
+ 'replaces': [
+ '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify Sub-CA";allow (write) groupdn = "ldap:///cn=Modify Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ }
+
+
+@register()
+class subca_find(LDAPSearch):
+ __doc__ = _("Search for Sub-CAs.")
+ msg_summary = ngettext(
+ '%(count)d Sub-CA matched', '%(count)d sub-CAs matched', 0
+ )
+
+ def execute(self, *keys, **options):
+ ca_enabled_check()
+ return super(subca_find, self).execute(*keys, **options)
+
+
+@register()
+class subca_show(LDAPRetrieve):
+ __doc__ = _("Display the properties of a Sub-CA.")
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ return super(subca_show, self).execute(*args, **kwargs)
+
+
+@register()
+class subca_add(LDAPCreate):
+ __doc__ = _("Create a Sub-CA.")
+ msg_summary = _('Created Sub-CA "%(value)s"')
+
+ takes_options = (
+ Str('issuer_dn', # TODO DNParam ?
+ label=_('Issuer DN'),
+ ),
+ )
+
+ def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
+ ca_enabled_check()
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ """Create the sub-CA in Dogtag.
+
+ If the operation fails, remove the LDAP entry.
+ """
+ try:
+ with self.api.Backend.ra_subca as subca_api:
+ subca_api.create_subca(keys[0], options['issuer_dn'])
+ except:
+ ldap.delete_entry(dn)
+ raise
+
+ return dn
+
+
+@register()
+class subca_mod(LDAPUpdate):
+ __doc__ = _("Modify Sub-CA configuration.")
+ msg_summary = _('Modified Sub-CA "%(value)s"')
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ # TODO prevent rename (cn maps to Dogtag subca)
+ return super(subca_mod, self).execute(*args, **kwargs)
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 5fd3017..4cf5a26 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -1640,6 +1640,10 @@ def update_people_entry(dercert):
return True
def ensure_ldap_profiles_container():
+ ensure_container(('ou', 'certificateProfiles'))
+
+
+def ensure_container(rdn):
server_id = installutils.realm_to_serverid(api.env.realm)
dogtag_uri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % server_id
@@ -1647,7 +1651,7 @@ def ensure_ldap_profiles_container():
if not conn.isconnected():
conn.connect(autobind=True)
- dn = DN(('ou', 'certificateProfiles'), ('ou', 'ca'), ('o', 'ipaca'))
+ dn = DN(rdn, ('ou', 'ca'), ('o', 'ipaca'))
try:
conn.get_entry(dn)
except errors.NotFound:
@@ -1655,7 +1659,7 @@ def ensure_ldap_profiles_container():
entry = conn.make_entry(
dn,
objectclass=['top', 'organizationalUnit'],
- ou=['certificateProfiles'],
+ **{rdn[0]: [rdn[1]]}
)
conn.add_entry(entry)
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 740f046..a62cc53 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -349,6 +349,16 @@ def ca_import_included_profiles(ca):
return cainstance.import_included_profiles()
+def ca_ensure_subcas_container(ca):
+ root_logger.info('[Ensuring sub-CAs container exists in Dogtag database]')
+
+ if not ca.is_configured():
+ root_logger.info('CA is not configured')
+ return False
+
+ return cainstance.ensure_container(('ou', 'subCAs'))
+
+
def upgrade_ca_audit_cert_validity(ca):
"""
Update the Dogtag audit signing certificate.
@@ -1261,7 +1271,10 @@ def ca_upgrade_schema(ca):
root_logger.info('CA is not configured')
return False
- schema_files=['/usr/share/pki/server/conf/schema-certProfile.ldif']
+ schema_files=[
+ '/usr/share/pki/server/conf/schema-certProfile.ldif',
+ '/usr/share/pki/server/conf/schema-subCA.ldif',
+ ]
try:
modified = schemaupdate.update_schema(schema_files, ldapi=True)
except Exception as e:
@@ -1488,6 +1501,7 @@ def upgrade_configuration():
except ipautil.CalledProcessError as e:
root_logger.error("Failed to restart %s: %s", ca.service_name, e)
+ ca_ensure_subcas_container(ca)
ca_enable_ldap_profile_subsystem(ca)
# This step MUST be done after ca_enable_ldap_profile_subsystem and
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 3dc8f5c..f03f791 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -2045,7 +2045,10 @@ class RestClient(Backend):
headers = headers or {}
headers['Cookie'] = self.cookie
- resource = os.path.join('/ca/rest', self.path, path)
+ if path is not None:
+ resource = os.path.join('/ca/rest', self.path, path)
+ else:
+ resource = os.path.join('/ca/rest', self.path)
# perform main request
status, status_text, resp_headers, resp_body = dogtag.https_request(
@@ -2100,3 +2103,21 @@ class ra_certprofile(RestClient):
self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'})
api.register(ra_certprofile)
+
+
+class ra_subca(RestClient):
+ """
+ Sub-CA management backend plugin.
+ """
+ path = 'subca'
+
+ def create_subca(self, ca_ref, dn):
+ self._ssldo('POST', None,
+ headers={
+ 'Content-type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ body=json.dumps({"caRef": ca_ref, "dn": dn}),
+ )
+
+api.register(ra_subca)