summaryrefslogtreecommitdiffstats
path: root/ipaserver/plugins
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2009-01-04 18:44:16 -0700
committerJason Gerard DeRose <jderose@redhat.com>2009-01-04 18:44:16 -0700
commit7442ad2e27afa7719bfd7de16ac8b0b44cb418de (patch)
tree20c4ae7a83676464df3c17d6c10274a48fd5d9e6 /ipaserver/plugins
parent6fe78a4944f11d430b724103f7d8d49c92af9b63 (diff)
downloadfreeipa-7442ad2e27afa7719bfd7de16ac8b0b44cb418de.tar.gz
freeipa-7442ad2e27afa7719bfd7de16ac8b0b44cb418de.tar.xz
freeipa-7442ad2e27afa7719bfd7de16ac8b0b44cb418de.zip
Renamed ipa_server/ to ipaserver/ and tests/test_ipa_server/ to tests/test_ipaserver
Diffstat (limited to 'ipaserver/plugins')
-rw-r--r--ipaserver/plugins/__init__.py24
-rw-r--r--ipaserver/plugins/b_ldap.py327
-rw-r--r--ipaserver/plugins/b_ra.py406
3 files changed, 757 insertions, 0 deletions
diff --git a/ipaserver/plugins/__init__.py b/ipaserver/plugins/__init__.py
new file mode 100644
index 000000000..5737dcb79
--- /dev/null
+++ b/ipaserver/plugins/__init__.py
@@ -0,0 +1,24 @@
+# Authors: Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 only
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Sub-package containing all server plugins.
+
+By convention, modules with frontend plugins are named f_*.py and modules
+with backend plugins are named b_*.py.
+"""
diff --git a/ipaserver/plugins/b_ldap.py b/ipaserver/plugins/b_ldap.py
new file mode 100644
index 000000000..071bf52eb
--- /dev/null
+++ b/ipaserver/plugins/b_ldap.py
@@ -0,0 +1,327 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 only
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Backend plugin for LDAP.
+
+This wraps the python-ldap bindings.
+"""
+
+import ldap as _ldap
+from ipalib import api, Context
+from ipalib import errors
+from ipalib.crud import CrudBackend
+from ipaserver import servercore
+from ipaserver import ipaldap
+
+
+class conn(Context):
+ """
+ Thread-local LDAP connection.
+ """
+
+ def get_value(self):
+ return 'it worked'
+
+api.register(conn)
+
+
+class ldap(CrudBackend):
+ """
+ LDAP backend plugin.
+ """
+
+ def __init__(self):
+ self.dn = _ldap.dn
+
+ def make_user_dn(self, uid):
+ """
+ Construct user dn from uid.
+ """
+ return 'uid=%s,%s,%s' % (
+ self.dn.escape_dn_chars(uid),
+ self.api.env.container_user,
+ self.api.env.basedn,
+ )
+
+ def make_group_dn(self, cn):
+ """
+ Construct group dn from cn.
+ """
+ return 'cn=%s,%s,%s' % (
+ self.dn.escape_dn_chars(cn),
+ self.api.env.container_group,
+ self.api.env.basedn,
+ )
+
+ def make_hostgroup_dn(self, cn):
+ """
+ Construct group of hosts dn from cn.
+ """
+ return 'cn=%s,%s,%s' % (
+ self.dn.escape_dn_chars(cn),
+ self.api.env.container_hostgroup,
+ self.api.env.basedn,
+ )
+
+ def make_service_dn(self, principal):
+ """
+ Construct service principal dn from principal name
+ """
+ return 'krbprincipalname=%s,%s,%s' % (
+ self.dn.escape_dn_chars(principal),
+ self.api.env.container_service,
+ self.api.env.basedn,
+ )
+
+ def make_host_dn(self, hostname):
+ """
+ Construct host dn from hostname
+ """
+ return 'cn=%s,%s,%s' % (
+ self.dn.escape_dn_chars(hostname),
+ self.api.env.container_host,
+ self.api.env.basedn,
+ )
+
+ def get_object_type(self, attribute):
+ """
+ Based on attribute, make an educated guess as to the type of
+ object we're looking for.
+ """
+ attribute = attribute.lower()
+ object_type = None
+ if attribute == "uid": # User
+ object_type = "posixAccount"
+ elif attribute == "cn": # Group
+ object_type = "posixGroup"
+ elif attribute == "krbprincipalname": # Service
+ object_type = "krbPrincipal"
+
+ return object_type
+
+ def find_entry_dn(self, key_attribute, primary_key, object_type=None, base=None):
+ """
+ Find an existing entry's dn from an attribute
+ """
+ key_attribute = key_attribute.lower()
+ if not object_type:
+ object_type = self.get_object_type(key_attribute)
+ if not object_type:
+ return None
+
+ search_filter = "(&(objectclass=%s)(%s=%s))" % (
+ object_type,
+ key_attribute,
+ self.dn.escape_dn_chars(primary_key)
+ )
+
+ if not base:
+ base = self.api.env.container_accounts
+
+ search_base = "%s, %s" % (base, self.api.env.basedn)
+
+ entry = servercore.get_sub_entry(search_base, search_filter, ['dn', 'objectclass'])
+
+ return entry.get('dn')
+
+ def get_base_entry(self, searchbase, searchfilter, attrs):
+ return servercore.get_base_entry(searchbase, searchfilter, attrs)
+
+ def get_sub_entry(self, searchbase, searchfilter, attrs):
+ return servercore.get_sub_entry(searchbase, searchfilter, attrs)
+
+ def get_one_entry(self, searchbase, searchfilter, attrs):
+ return servercore.get_one_entry(searchbase, searchfilter, attrs)
+
+ def get_ipa_config(self):
+ """Return a dictionary of the IPA configuration"""
+ return servercore.get_ipa_config()
+
+ def mark_entry_active(self, dn):
+ return servercore.mark_entry_active(dn)
+
+ def mark_entry_inactive(self, dn):
+ return servercore.mark_entry_inactive(dn)
+
+ def _generate_search_filters(self, **kw):
+ """Generates a search filter based on a list of words and a list
+ of fields to search against.
+
+ Returns a tuple of two filters: (exact_match, partial_match)
+ """
+
+ # construct search pattern for a single word
+ # (|(f1=word)(f2=word)...)
+ exact_pattern = "(|"
+ for field in kw.keys():
+ exact_pattern += "(%s=%s)" % (field, kw[field])
+ exact_pattern += ")"
+
+ sub_pattern = "(|"
+ for field in kw.keys():
+ sub_pattern += "(%s=*%s*)" % (field, kw[field])
+ sub_pattern += ")"
+
+ # construct the giant match for all words
+ exact_match_filter = "(&" + exact_pattern + ")"
+ partial_match_filter = "(|" + sub_pattern + ")"
+
+ return (exact_match_filter, partial_match_filter)
+
+ def modify_password(self, dn, **kw):
+ return servercore.modify_password(dn, kw.get('oldpass'), kw.get('newpass'))
+
+ def add_member_to_group(self, memberdn, groupdn):
+ """
+ Add a new member to a group.
+
+ :param memberdn: the DN of the member to add
+ :param groupdn: the DN of the group to add a member to
+ """
+ return servercore.add_member_to_group(memberdn, groupdn)
+
+ def remove_member_from_group(self, memberdn, groupdn):
+ """
+ Remove a new member from a group.
+
+ :param memberdn: the DN of the member to remove
+ :param groupdn: the DN of the group to remove a member from
+ """
+ return servercore.remove_member_from_group(memberdn, groupdn)
+
+ # The CRUD operations
+
+ def strip_none(self, kw):
+ """
+ Remove any None values present in the LDAP attribute dict.
+ """
+ for (key, value) in kw.iteritems():
+ if value is None:
+ continue
+ if type(value) in (list, tuple):
+ value = filter(
+ lambda v: type(v) in (str, unicode, bool, int, float),
+ value
+ )
+ if len(value) > 0:
+ yield (key, value)
+ else:
+ assert type(value) in (str, unicode, bool, int, float)
+ yield (key, value)
+
+ def create(self, **kw):
+ if servercore.entry_exists(kw['dn']):
+ raise errors.DuplicateEntry("entry already exists")
+ kw = dict(self.strip_none(kw))
+
+
+ entry = ipaldap.Entry(kw['dn'])
+
+ # dn isn't allowed to be in the entry itself
+ del kw['dn']
+
+ # Fill in our new entry
+ for k in kw:
+ entry.setValues(k, kw[k])
+
+ servercore.add_entry(entry)
+ return self.retrieve(entry.dn)
+
+ def retrieve(self, dn, attributes=None):
+ return servercore.get_entry_by_dn(dn, attributes)
+
+ def update(self, dn, **kw):
+ result = self.retrieve(dn, ["*"])
+
+ entry = ipaldap.Entry((dn, servercore.convert_scalar_values(result)))
+ kw = dict(self.strip_none(kw))
+ for k in kw:
+ entry.setValues(k, kw[k])
+
+ servercore.update_entry(entry.toDict())
+
+ return self.retrieve(dn)
+
+ def delete(self, dn):
+ return servercore.delete_entry(dn)
+
+ def search(self, **kw):
+ objectclass = kw.get('objectclass')
+ sfilter = kw.get('filter')
+ attributes = kw.get('attributes')
+ base = kw.get('base')
+ if attributes:
+ del kw['attributes']
+ else:
+ attributes = ['*']
+ if objectclass:
+ del kw['objectclass']
+ if base:
+ del kw['base']
+ if sfilter:
+ del kw['filter']
+ (exact_match_filter, partial_match_filter) = self._generate_search_filters(**kw)
+ if objectclass:
+ exact_match_filter = "(&(objectClass=%s)%s)" % (objectclass, exact_match_filter)
+ partial_match_filter = "(&(objectClass=%s)%s)" % (objectclass, partial_match_filter)
+ if sfilter:
+ exact_match_filter = "(%s%s)" % (sfilter, exact_match_filter)
+ partial_match_filter = "(%s%s)" % (sfilter, partial_match_filter)
+
+ if not base:
+ base = self.api.env.container_accounts
+
+ search_base = "%s, %s" % (base, self.api.env.basedn)
+ try:
+ exact_results = servercore.search(search_base,
+ exact_match_filter, attributes)
+ except errors.NotFound:
+ exact_results = [0]
+
+ try:
+ partial_results = servercore.search(search_base,
+ partial_match_filter, attributes)
+ except errors.NotFound:
+ partial_results = [0]
+
+ exact_counter = exact_results[0]
+ partial_counter = partial_results[0]
+
+ exact_results = exact_results[1:]
+ partial_results = partial_results[1:]
+
+ # Remove exact matches from the partial_match list
+ exact_dns = set(map(lambda e: e.get('dn'), exact_results))
+ partial_results = filter(lambda e: e.get('dn') not in exact_dns,
+ partial_results)
+
+ if (exact_counter == -1) or (partial_counter == -1):
+ counter = -1
+ else:
+ counter = len(exact_results) + len(partial_results)
+
+ results = [counter]
+ for r in exact_results + partial_results:
+ results.append(r)
+
+ return results
+
+api.register(ldap)
diff --git a/ipaserver/plugins/b_ra.py b/ipaserver/plugins/b_ra.py
new file mode 100644
index 000000000..bf119b810
--- /dev/null
+++ b/ipaserver/plugins/b_ra.py
@@ -0,0 +1,406 @@
+# Authors:
+# Andrew Wnuk <awnuk@redhat.com>
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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 only
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Backend plugin for IPA-RA.
+
+IPA-RA provides an access to CA to issue, retrieve, and revoke certificates.
+IPA-RA plugin provides CA interface via the following methods:
+ check_request_status to check certificate request status
+ get_certificate to retrieve an existing certificate
+ request_certificate to request certificate
+ revoke_certificate to revoke certificate
+ take_certificate_off_hold to take certificate off hold
+"""
+
+import os, stat, subprocess
+import array
+import errno
+import binascii
+import httplib, urllib
+from socket import gethostname
+
+from ipalib import api, Backend
+from ipalib import errors
+from ipaserver import servercore
+from ipaserver import ipaldap
+
+
+class ra(Backend):
+
+
+ def __init__(self):
+ self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
+ self.pwd_file = self.sec_dir + os.sep + '.pwd'
+ self.noise_file = self.sec_dir + os.sep + '.noise'
+
+ self.ca_host = None
+ self.ca_port = None
+ self.ca_ssl_port = None
+
+ self.__get_ca_location()
+
+ self.ipa_key_size = "2048"
+ self.ipa_certificate_nickname = "ipaCert"
+ self.ca_certificate_nickname = "caCert"
+
+ if not os.path.isdir(self.sec_dir):
+ os.mkdir(self.sec_dir)
+ self.__create_pwd_file()
+ self.__create_nss_db()
+ self.__import_ca_chain()
+ self.__request_ipa_certificate(self.__generate_ipa_request())
+
+
+ def check_request_status(self, request_id=None):
+ """
+ Check certificate request status
+ :param request_id: request ID
+ """
+ self.log.debug("IPA-RA: check_request_status")
+ return_values = {}
+ if request_id is not None:
+ params = urllib.urlencode({'requestId': request_id, 'xmlOutput': 'true'})
+ headers = {"Content-type": "application/x-www-form-urlencoded"}
+ conn = httplib.HTTPConnection(self.ca_host, self.ca_port)
+ conn.request("POST", "/ca/ee/ca/checkRequest", params, headers)
+ response = conn.getresponse()
+ api.log.debug("IPA-RA: response.status: %d response.reason: %s" % (response.status, response.reason))
+ data = response.read()
+ conn.close()
+ self.log.debug(data)
+ if data is not None:
+ request_status = self.__find_substring(data, 'header.status = "', '"')
+ if request_status is not None:
+ return_values["status"] = "0"
+ return_values["request_status"] = request_status
+ self.log.debug("IPA-RA: request_status: '%s'" % request_status)
+ serial_number = self.__find_substring(data, 'record.serialNumber="', '"')
+ if serial_number is not None:
+ return_values["serial_number"] = "0x"+serial_number
+ request_id = self.__find_substring(data, 'header.requestId = "', '"')
+ if request_id is not None:
+ return_values["request_id"] = request_id
+ error = self.__find_substring(data, 'fixed.unexpectedError = "', '"')
+ if error is not None:
+ return_values["error"] = error
+ if return_values.has_key("status") is False:
+ return_values["status"] = "2"
+ else:
+ return_values["status"] = "1"
+ return return_values
+
+
+ def get_certificate(self, serial_number=None):
+ """
+ Retrieve an existing certificate
+ :param serial_number: certificate serial number
+ """
+ self.log.debug("IPA-RA: get_certificate")
+ issued_certificate = None
+ return_values = {}
+ if serial_number is not None:
+ request_info = ("serialNumber=%s" % serial_number)
+ self.log.debug("request_info: '%s'" % request_info)
+ returncode, stdout, stderr = self.__run_sslget(["-e", request_info, "-r", "/ca/agent/ca/displayBySerial", self.ca_host+":"+str(self.ca_ssl_port)])
+ self.log.debug("IPA-RA: returncode: %d" % returncode)
+ if (returncode == 0):
+ issued_certificate = self.__find_substring(stdout, 'header.certChainBase64 = "', '"')
+ if issued_certificate is not None:
+ return_values["status"] = "0"
+ issued_certificate = issued_certificate.replace("\\r", "")
+ issued_certificate = issued_certificate.replace("\\n", "")
+ self.log.debug("IPA-RA: issued_certificate: '%s'" % issued_certificate)
+ return_values["certificate"] = issued_certificate
+ else:
+ return_values["status"] = "1"
+ revocation_reason = self.__find_substring(stdout, 'header.revocationReason = ', ';')
+ if revocation_reason is not None:
+ return_values["revocation_reason"] = revocation_reason
+ else:
+ return_values["status"] = str(-returncode)
+ else:
+ return_values["status"] = "1"
+ return return_values
+
+
+ def request_certificate(self, certificate_request=None, request_type="pkcs10"):
+ """
+ Submit certificate request
+ :param certificate_request: certificate request
+ :param request_type: request type
+ """
+ self.log.debug("IPA-RA: request_certificate")
+ certificate = None
+ return_values = {}
+ if request_type is None:
+ request_type="pkcs10"
+ if certificate_request is not None:
+ request = urllib.quote(certificate_request)
+ request_info = "profileId=caRAserverCert&cert_request_type="+request_type+"&cert_request="+request+"&xmlOutput=true"
+ returncode, stdout, stderr = self.__run_sslget(["-e", request_info, "-r", "/ca/ee/ca/profileSubmit", self.ca_host+":"+str(self.ca_ssl_port)])
+ self.log.debug("IPA-RA: returncode: %d" % returncode)
+ if (returncode == 0):
+ status = self.__find_substring(stdout, "<Status>", "</Status>")
+ if status is not None:
+ self.log.debug ("status=%s" % status)
+ return_values["status"] = status
+ request_id = self.__find_substring(stdout, "<Id>", "</Id>")
+ if request_id is not None:
+ self.log.debug ("request_id=%s" % request_id)
+ return_values["request_id"] = request_id
+ serial_number = self.__find_substring(stdout, "<serialno>", "</serialno>")
+ if serial_number is not None:
+ self.log.debug ("serial_number=%s" % serial_number)
+ return_values["serial_number"] = ("0x%s" % serial_number)
+ subject = self.__find_substring(stdout, "<SubjectDN>", "</SubjectDN>")
+ if subject is not None:
+ self.log.debug ("subject=%s" % subject)
+ return_values["subject"] = subject
+ certificate = self.__find_substring(stdout, "<b64>", "</b64>")
+ if certificate is not None:
+ self.log.debug ("certificate=%s" % certificate)
+ return_values["certificate"] = certificate
+ if return_values.has_key("status") is False:
+ return_values["status"] = "2"
+ else:
+ return_values["status"] = str(-returncode)
+ else:
+ return_values["status"] = "1"
+ return return_values
+
+
+ def revoke_certificate(self, serial_number=None, revocation_reason=0):
+ """
+ Revoke a certificate
+ :param serial_number: certificate serial number
+ :param revocation_reason: revocation reason
+ revocationr reasons: 0 - unspecified
+ 1 - key compromise
+ 2 - ca compromise
+ 3 - affiliation changed
+ 4 - superseded
+ 5 - cessation of operation
+ 6 - certificate hold
+ 7 - value 7 is not used
+ 8 - remove from CRL
+ 9 - privilege withdrawn
+ 10 - aa compromise
+ see RFC 5280 for more details
+ """
+ return_values = {}
+ self.log.debug("IPA-RA: revoke_certificate")
+ if revocation_reason is None:
+ revocation_reason = 0
+ if serial_number is not None:
+ if isinstance(serial_number, int):
+ serial_number = str(serial_number)
+ if isinstance(revocation_reason, int):
+ revocation_reason = str(revocation_reason)
+ request_info = "op=revoke&revocationReason="+revocation_reason+"&revokeAll=(certRecordId%3D"+serial_number+")&totalRecordCount=1"
+ returncode, stdout, stderr = self.__run_sslget(["-e", request_info, "-r", "/ca/agent/ca/doRevoke", self.ca_host+":"+str(self.ca_ssl_port)])
+ api.log.debug("IPA-RA: returncode: %d" % returncode)
+ if (returncode == 0):
+ return_values["status"] = "0"
+ if (stdout.find('revoked = "yes"') > -1):
+ return_values["revoked"] = True
+ else:
+ return_values["revoked"] = False
+ else:
+ return_values["status"] = str(-returncode)
+ else:
+ return_values["status"] = "1"
+ return return_values
+
+
+ def take_certificate_off_hold(self, serial_number=None):
+ """
+ Take revoked certificate off hold
+ :param serial_number: certificate serial number
+ """
+ return_values = {}
+ self.log.debug("IPA-RA: revoke_certificate")
+ if serial_number is not None:
+ if isinstance(serial_number, int):
+ serial_number = str(serial_number)
+ request_info = "serialNumber="+serial_number
+ returncode, stdout, stderr = self.__run_sslget(["-e", request_info, "-r", "/ca/agent/ca/doUnrevoke", self.ca_host+":"+str(self.ca_ssl_port)])
+ api.log.debug("IPA-RA: returncode: %d" % returncode)
+ if (returncode == 0):
+ if (stdout.find('unrevoked = "yes"') > -1):
+ return_values["taken_off_hold"] = True
+ else:
+ return_values["taken_off_hold"] = False
+ else:
+ return_values["status"] = str(-returncode)
+ else:
+ return_values["status"] = "1"
+ return return_values
+
+
+ def __find_substring(self, str, str1, str2):
+ sub_str = None
+ k0 = len(str)
+ k1 = str.find(str1)
+ k2 = len(str1)
+ if (k0 > 0 and k1 > -1 and k2 > 0 and k0 > k1 + k2):
+ sub_str = str[k1+k2:]
+ k3 = len(sub_str)
+ k4 = sub_str.find(str2)
+ if (k3 > 0 and k4 > -1 and k3 > k4):
+ sub_str = sub_str[:k4]
+ return sub_str
+
+
+ def __get_ca_location(self):
+ if 'ca_host' in api.env:
+ api.log.debug("ca_host configuration found")
+ if api.env.ca_host is not None:
+ self.ca_host = api.env.ca_host
+ else:
+ api.log.debug("ca_host configuration not found")
+ # if CA is not hosted with IPA on the same system and there is no configuration support for 'api.env.ca_host', then set ca_host below
+ # self.ca_host = "example.com"
+ if self.ca_host is None:
+ self.ca_host = gethostname()
+ api.log.debug("ca_host: %s" % self.ca_host)
+
+ if 'ca_ssl_port' in api.env:
+ api.log.debug("ca_ssl_port configuration found")
+ if api.env.ca_ssl_port is not None:
+ self.ca_ssl_port = api.env.ca_ssl_port
+ else:
+ api.log.debug("ca_ssl_port configuration not found")
+ if self.ca_ssl_port is None:
+ self.ca_ssl_port = 9443
+ api.log.debug("ca_ssl_port: %d" % self.ca_ssl_port)
+
+ if 'ca_port' in api.env:
+ api.log.debug("ca_port configuration found")
+ if api.env.ca_port is not None:
+ self.ca_port = api.env.ca_port
+ else:
+ api.log.debug("ca_port configuration not found")
+ if self.ca_port is None:
+ self.ca_port = 9080
+ api.log.debug("ca_port: %d" % self.ca_port)
+
+
+ def __generate_ipa_request(self):
+ certificate_request = None
+ if not os.path.isfile(self.noise_file):
+ self.__create_noise_file()
+ returncode, stdout, stderr = self.__run_certutil(["-R", "-k", "rsa", "-g", self.ipa_key_size, "-s", "CN=IPA-Subsystem-Certificate,OU=pki-ipa,O=UsersysRedhat-Domain", "-z", self.noise_file, "-a"])
+ if os.path.isfile(self.noise_file):
+ os.unlink(self.noise_file)
+ if (returncode == 0):
+ api.log.info("IPA-RA: IPA certificate request generated")
+ certificate_request = self.__find_substring(stdout, "-----BEGIN NEW CERTIFICATE REQUEST-----", "-----END NEW CERTIFICATE REQUEST-----")
+ if certificate_request is not None:
+ api.log.debug("certificate_request=%s" % certificate_request)
+ else:
+ api.log.warn("IPA-RA: Error parsing certificate request." % returncode)
+ else:
+ api.log.warn("IPA-RA: Error (%d) generating IPA certificate request." % returncode)
+ return certificate_request
+
+ def __request_ipa_certificate(self, certificate_request=None):
+ ipa_certificate = None
+ if certificate_request is not None:
+ params = urllib.urlencode({'profileId': 'caServerCert', 'cert_request_type': 'pkcs10', 'requestor_name': 'freeIPA', 'cert_request': self.__generate_ipa_request(), 'xmlOutput': 'true'})
+ headers = {"Content-type": "application/x-www-form-urlencoded"}
+ conn = httplib.HTTPConnection(self.ca_host+":"+elf.ca_port)
+ conn.request("POST", "/ca/ee/ca/profileSubmit", params, headers)
+ response = conn.getresponse()
+ api.log.debug("IPA-RA: response.status: %d response.reason: '%s'" % (response.status, response.reason))
+ data = response.read()
+ conn.close()
+ api.log.info("IPA-RA: IPA certificate request submitted to CA: %s" % data)
+ return ipa_certificate
+
+ def __get_ca_chain(self):
+ headers = {"Content-type": "application/x-www-form-urlencoded"}
+ conn = httplib.HTTPConnection(self.ca_host+":"+elf.ca_port)
+ conn.request("POST", "/ca/ee/ca/getCertChain", None, headers)
+ response = conn.getresponse()
+ api.log.debug("IPA-RA: response.status: %d response.reason: '%s'" % (response.status, response.reason))
+ data = response.read()
+ conn.close()
+ certificate_chain = self.__find_substring(data, "<ChainBase64>", "</ChainBase64>")
+ if certificate_chain is not None:
+ api.log.info(("IPA-RA: CA chain obtained from CA: %s" % certificate_chain))
+ else:
+ api.log.warn("IPA-RA: Error parsing certificate chain.")
+ return certificate_chain
+
+ def __import_ca_chain(self):
+ returncode, stdout, stderr = self.__run_certutil(["-A", "-t", "CT,C,C", "-n", self.ca_certificate_nickname, "-a"], self.__get_ca_chain())
+ if (returncode == 0):
+ api.log.info("IPA-RA: CA chain imported to IPA's NSS DB")
+ else:
+ api.log.warn("IPA-RA: Error (%d) importing CA chain to IPA's NSS DB." % returncode)
+
+ def __create_noise_file(self):
+ noise = array.array('B', os.urandom(128))
+ f = open(self.noise_file, "wb")
+ noise.tofile(f)
+ f.close()
+
+ def __create_pwd_file(self):
+ hex_str = binascii.hexlify(os.urandom(10))
+ print "urandom: %s" % hex_str
+ f = os.open(self.pwd_file, os.O_CREAT | os.O_RDWR)
+ os.write(f, hex_str)
+ os.close(f)
+
+ def __create_nss_db(self):
+ returncode, stdout, stderr = self.__run_certutil(["-N"])
+ if (returncode == 0):
+ api.log.info("IPA-RA: NSS DB created")
+ else:
+ api.log.warn("IPA-RA: Error (%d) creating NSS DB." % returncode)
+
+ """
+ sslget and certutil utilities are used only till Python-NSS completion.
+ """
+ def __run_sslget(self, args, stdin=None):
+ new_args = ["/usr/bin/sslget", "-d", self.sec_dir, "-w", self.pwd_file, "-n", self.ipa_certificate_nickname]
+ new_args = new_args + args
+ return self.__run(new_args, stdin)
+
+ def __run_certutil(self, args, stdin=None):
+ new_args = ["/usr/bin/certutil", "-d", self.sec_dir, "-f", self.pwd_file]
+ new_args = new_args + args
+ return self.__run(new_args, stdin)
+
+ def __run(self, args, stdin=None):
+ if stdin:
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
+ stdout,stderr = p.communicate(stdin)
+ else:
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
+ stdout,stderr = p.communicate()
+
+ api.log.debug("IPA-RA: returncode: %d args: '%s'" % (p.returncode, ' '.join(args)))
+ # api.log.debug("IPA-RA: stdout: '%s'" % stdout)
+ # api.log.debug("IPA-RA: stderr: '%s'" % stderr)
+ return (p.returncode, stdout, stderr)
+
+api.register(ra)