summaryrefslogtreecommitdiffstats
path: root/ipaserver/plugins
diff options
context:
space:
mode:
authorFraser Tweedale <ftweedal@redhat.com>2016-10-26 09:48:19 +1000
committerMartin Babinsky <mbabinsk@redhat.com>2016-12-06 16:13:45 +0100
commitdfbdb5323863e6c3d681c1b33b1eb9d2efefd6c7 (patch)
tree50c13bc8659c692da061749ec35519ede5c4a1bb /ipaserver/plugins
parent0499ba5795cf483756ac980604fd2c26fda7ba39 (diff)
downloadfreeipa-dfbdb5323863e6c3d681c1b33b1eb9d2efefd6c7.tar.gz
freeipa-dfbdb5323863e6c3d681c1b33b1eb9d2efefd6c7.tar.xz
freeipa-dfbdb5323863e6c3d681c1b33b1eb9d2efefd6c7.zip
cert-request: match names against principal aliases
Currently we do not check Kerberos principal aliases when validating a CSR. Enhance cert-request to accept the following scenarios: - for hosts and services: CN and SAN dnsNames match a principal alias (realm and service name must be same as nominated principal) - for all principal types: UPN or KRB5PrincipalName othername match any principal alias. Fixes: https://fedorahosted.org/freeipa/ticket/6295 Reviewed-By: Martin Babinsky <mbabinsk@redhat.com> Reviewed-By: Milan Kubik <mkubik@redhat.com>
Diffstat (limited to 'ipaserver/plugins')
-rw-r--r--ipaserver/plugins/cert.py113
1 files changed, 85 insertions, 28 deletions
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
index 3571ef1fc..e4efa7d37 100644
--- a/ipaserver/plugins/cert.py
+++ b/ipaserver/plugins/cert.py
@@ -649,11 +649,13 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
cn = cns[-1].value # "most specific" is end of list
if principal_type in (SERVICE, HOST):
- if cn.lower() != principal.hostname.lower():
- raise errors.ACIError(
- info=_("hostname in subject of request '%(cn)s' "
- "does not match principal hostname '%(hostname)s'")
- % dict(cn=cn, hostname=principal.hostname))
+ if not _dns_name_matches_principal(cn, principal, principal_obj):
+ raise errors.ValidationError(
+ name='csr',
+ error=_(
+ "hostname in subject of request '%(cn)s' does not "
+ "match name or aliases of principal '%(principal)s'"
+ ) % dict(cn=cn, principal=principal))
elif principal_type == USER:
# check user name
if cn != principal.username:
@@ -686,26 +688,32 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
generalnames = x509.process_othernames(ext_san.value)
for gn in generalnames:
if isinstance(gn, cryptography.x509.general_name.DNSName):
+ if principal.is_user:
+ raise errors.ValidationError(
+ name='csr',
+ error=_(
+ "subject alt name type %s is forbidden "
+ "for user principals") % "DNSName"
+ )
+
name = gn.value
- alt_principal = None
+
+ if _dns_name_matches_principal(name, principal, principal_obj):
+ continue # nothing more to check for this alt name
+
+ # no match yet; check for an alternative principal with
+ # same realm and service type as subject principal.
+ components = list(principal.components)
+ components[-1] = name
+ alt_principal = kerberos.Principal(components, principal.realm)
alt_principal_obj = None
try:
if principal_type == HOST:
- alt_principal = kerberos.Principal(
- (u'host', name), principal.realm)
- alt_principal_obj = api.Command['host_show'](name, all=True)
+ alt_principal_obj = api.Command['host_show'](
+ name, all=True)
elif principal_type == SERVICE:
- alt_principal = kerberos.Principal(
- (principal.service_name, name), principal.realm)
alt_principal_obj = api.Command['service_show'](
alt_principal, all=True)
- elif principal_type == USER:
- raise errors.ValidationError(
- name='csr',
- error=_(
- "subject alt name type %s is forbidden "
- "for user principals") % "DNSName"
- )
except errors.NotFound:
# We don't want to issue any certificates referencing
# machines we don't know about. Nothing is stored in this
@@ -713,18 +721,23 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
raise errors.NotFound(reason=_('The service principal for '
'subject alt name %s in certificate request does not '
'exist') % name)
- if alt_principal_obj is not None:
- altdn = alt_principal_obj['result']['dn']
- if not ldap.can_write(altdn, "usercertificate"):
- raise errors.ACIError(info=_(
- "Insufficient privilege to create a certificate "
- "with subject alt name '%s'.") % name)
- if alt_principal is not None and not bypass_caacl:
+
+ # we found an alternative principal;
+ # now check write access and caacl
+ altdn = alt_principal_obj['result']['dn']
+ if not ldap.can_write(altdn, "usercertificate"):
+ raise errors.ACIError(info=_(
+ "Insufficient privilege to create a certificate "
+ "with subject alt name '%s'.") % name)
+ if not bypass_caacl:
caacl_check(principal_type, alt_principal, ca, profile_id)
+
elif isinstance(gn, (x509.KRB5PrincipalName, x509.UPN)):
- if gn.name != principal_string:
- raise errors.ACIError(
- info=_(
+ if not _principal_name_matches_principal(
+ gn.name, principal_obj):
+ raise errors.ValidationError(
+ name='csr',
+ error=_(
"Principal '%s' in subject alt name does not "
"match requested principal") % gn.name)
elif isinstance(gn, cryptography.x509.general_name.RFC822Name):
@@ -787,6 +800,50 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
)
+def _dns_name_matches_principal(name, principal, principal_obj):
+ """
+ Ensure that a DNS name matches the given principal.
+
+ :param name: The DNS name to match
+ :param principal: The subject ``Principal``
+ :param principal_obj: The subject principal's LDAP object
+ :return: True if name matches, otherwise False
+
+ """
+ for alias in principal_obj.get('krbprincipalname', []):
+ # we can only compare them if both subject principal and
+ # the alias are service or host principals
+ if not (alias.is_service and principal.is_service):
+ continue
+
+ # ignore aliases with different realm or service name from
+ # subject principal
+ if alias.realm != principal.realm:
+ continue
+ if alias.service_name != principal.service_name:
+ continue
+
+ # now compare DNS name to alias hostname
+ if name.lower() == alias.hostname.lower():
+ return True # we have a match
+
+ return False
+
+
+def _principal_name_matches_principal(name, principal_obj):
+ """
+ Ensure that a stringy principal name (e.g. from UPN
+ or KRB5PrincipalName OtherName) matches the given principal.
+
+ """
+ try:
+ principal = kerberos.Principal(name)
+ except ValueError:
+ return False
+
+ return principal in principal_obj.get('krbprincipalname', [])
+
+
@register()
class cert_status(Retrieve, BaseCertMethod, VirtualCommand):
__doc__ = _('Check the status of a certificate signing request.')