[Freeipa-devel] [PATCH] 0090, 0092..0094 cert-show: show subject alternative names

Fraser Tweedale ftweedal at redhat.com
Fri Aug 19 11:11:22 UTC 2016


Bump for review.

On Mon, Aug 15, 2016 at 05:15:16PM +1000, Fraser Tweedale wrote:
> Thanks for reviews.  Rebased and updated patches attached (and one
> new patch).  No substantive changes to 92..94.  Patch order is:
> 
> 92-2, 93-2, 94-2, 98, 90-3
> 
> Other comments inline.
> 
> Thanks,
> Fraser
> 
> On Fri, Aug 12, 2016 at 11:33:28AM +0200, Jan Cholasta wrote:
> > Patch 0092: ACK
> > 
> > Patch 0093: ACK
> > 
> > Patch 0094: ACK
> > 
> > Patch 0090:
> > 
> > 1) Generic otherNames (san_other) do not work correctly. The OID is not
> > included in the value and names with complex type other than
> > KerberosPrincipal are not parsed correctly. The value should include the OID
> > and DER blob of the name.
> > 
> Updated to use "OID:b64(DER)" as the attribute value.
> 
> > 2) With --all, san_other should be included in the result for all
> > otherNames, even the known ones, to provide (limited) forward compatibility.
> > 
> Done; when --all given, known otherName kinds are included in
> 'san_other' attribute in addition to their own attribute.
> 
> > 3) Do we have to support *all* the name types? I mean we could, for the sake
> > of completeness, but it might be easier to just keep the few ones we
> > actually care about (email, DNS name, principal name, UPN and directory name
> > in your patch 0095).
> > 
> Yeah, why not support them all?  See also Petr's comments in other
> branch of thread.
> 
> > 4)
> > 
> > +            obj.setdefault(attr_name, []).append(unicode(name))
> > 
> > The value should not (always) be unicode, but of the type declared by the
> > param (unicode or ipapython.kerberos.Principal or
> > ipapython.dnsutil.DNSName).
> > 
> I now pass the value to the constructor of whatever type the
> parameter uses:
> 
>         attr_value = self.params[attr_name].type(name_formatted)
>         obj.setdefault(attr_name, []).append(attr_value)

> From 17e5515ab0eeb92d87091eb00a26dcf358060dba Mon Sep 17 00:00:00 2001
> From: Fraser Tweedale <ftweedal at redhat.com>
> Date: Thu, 21 Jul 2016 16:27:49 +1000
> Subject: [PATCH 92/94] Move GeneralName parsing code to ipalib.x509
> 
> GeneralName parsing code is primarily relevant to X.509.  An
> upcoming change will add SAN parsing to the cert-show command, so
> first move the GeneralName parsing code from ipalib.pkcs10 to
> ipalib.x509.
> 
> Part of: https://fedorahosted.org/freeipa/ticket/6022
> ---
>  ipalib/pkcs10.py          |  93 ++-----------------------------------
>  ipalib/x509.py            | 114 +++++++++++++++++++++++++++++++++++++++++++++-
>  ipaserver/plugins/cert.py |   8 ++--
>  3 files changed, 120 insertions(+), 95 deletions(-)
> 
> diff --git a/ipalib/pkcs10.py b/ipalib/pkcs10.py
> index e340c1a2005ad781542a32a0a76753e80364dacf..158ebb3a25be2bd292f3883545fe8afe49b7be8c 100644
> --- a/ipalib/pkcs10.py
> +++ b/ipalib/pkcs10.py
> @@ -22,9 +22,10 @@ from __future__ import print_function
>  import sys
>  import base64
>  import nss.nss as nss
> -from pyasn1.type import univ, char, namedtype, tag
> +from pyasn1.type import univ, namedtype, tag
>  from pyasn1.codec.der import decoder
>  import six
> +from ipalib import x509
>  
>  if six.PY3:
>      unicode = str
> @@ -32,11 +33,6 @@ if six.PY3:
>  PEM = 0
>  DER = 1
>  
> -SAN_DNSNAME = 'DNS name'
> -SAN_RFC822NAME = 'RFC822 Name'
> -SAN_OTHERNAME_UPN = 'Other Name (OID.1.3.6.1.4.1.311.20.2.3)'
> -SAN_OTHERNAME_KRB5PRINCIPALNAME = 'Other Name (OID.1.3.6.1.5.2.2)'
> -
>  def get_subject(csr, datatype=PEM):
>      """
>      Given a CSR return the subject value.
> @@ -72,78 +68,6 @@ def get_extensions(csr, datatype=PEM):
>      return tuple(get_prefixed_oid_str(ext)[4:]
>                   for ext in request.extensions)
>  
> -class _PrincipalName(univ.Sequence):
> -    componentType = namedtype.NamedTypes(
> -        namedtype.NamedType('name-type', univ.Integer().subtype(
> -            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> -        ),
> -        namedtype.NamedType('name-string', univ.SequenceOf(char.GeneralString()).subtype(
> -            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> -        ),
> -    )
> -
> -class _KRB5PrincipalName(univ.Sequence):
> -    componentType = namedtype.NamedTypes(
> -        namedtype.NamedType('realm', char.GeneralString().subtype(
> -            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> -        ),
> -        namedtype.NamedType('principalName', _PrincipalName().subtype(
> -            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> -        ),
> -    )
> -
> -def _decode_krb5principalname(data):
> -    principal = decoder.decode(data, asn1Spec=_KRB5PrincipalName())[0]
> -    realm = (str(principal['realm']).replace('\\', '\\\\')
> -                                    .replace('@', '\\@'))
> -    name = principal['principalName']['name-string']
> -    name = '/'.join(str(n).replace('\\', '\\\\')
> -                          .replace('/', '\\/')
> -                          .replace('@', '\\@') for n in name)
> -    name = '%s@%s' % (name, realm)
> -    return name
> -
> -class _AnotherName(univ.Sequence):
> -    componentType = namedtype.NamedTypes(
> -        namedtype.NamedType('type-id', univ.ObjectIdentifier()),
> -        namedtype.NamedType('value', univ.Any().subtype(
> -            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> -        ),
> -    )
> -
> -class _GeneralName(univ.Choice):
> -    componentType = namedtype.NamedTypes(
> -        namedtype.NamedType('otherName', _AnotherName().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> -        ),
> -        namedtype.NamedType('rfc822Name', char.IA5String().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> -        ),
> -        namedtype.NamedType('dNSName', char.IA5String().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
> -        ),
> -        namedtype.NamedType('x400Address', univ.Sequence().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))
> -        ),
> -        namedtype.NamedType('directoryName', univ.Choice().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
> -        ),
> -        namedtype.NamedType('ediPartyName', univ.Sequence().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))
> -        ),
> -        namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))
> -        ),
> -        namedtype.NamedType('iPAddress', univ.OctetString().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))
> -        ),
> -        namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(
> -            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))
> -        ),
> -    )
> -
> -class _SubjectAltName(univ.SequenceOf):
> -    componentType = _GeneralName()
>  
>  def get_subjectaltname(csr, datatype=PEM):
>      """
> @@ -159,19 +83,8 @@ def get_subjectaltname(csr, datatype=PEM):
>          return None
>      del request
>  
> -    nss_names = nss.x509_alt_name(extension.value, nss.AsObject)
> -    asn1_names = decoder.decode(extension.value.data,
> -                                asn1Spec=_SubjectAltName())[0]
> -    names = []
> -    for nss_name, asn1_name in zip(nss_names, asn1_names):
> -        name_type = nss_name.type_string
> -        if name_type == SAN_OTHERNAME_KRB5PRINCIPALNAME:
> -            name = _decode_krb5principalname(asn1_name['otherName']['value'])
> -        else:
> -            name = nss_name.name
> -        names.append((name_type, name))
> +    return x509.decode_generalnames(extension.value)
>  
> -    return tuple(names)
>  
>  # Unfortunately, NSS can only parse the extension request attribute, so
>  # we have to parse friendly name ourselves (see RFC 2986)
> diff --git a/ipalib/x509.py b/ipalib/x509.py
> index 82194922d151a1b0f2df03df3578ad45b43b71c9..15168de08240a84794efef409d022eaa983291c9 100644
> --- a/ipalib/x509.py
> +++ b/ipalib/x509.py
> @@ -40,7 +40,7 @@ import re
>  
>  import nss.nss as nss
>  from nss.error import NSPRError
> -from pyasn1.type import univ, namedtype, tag
> +from pyasn1.type import univ, char, namedtype, tag
>  from pyasn1.codec.der import decoder, encoder
>  import six
>  
> @@ -63,6 +63,11 @@ EKU_EMAIL_PROTECTION = '1.3.6.1.5.5.7.3.4'
>  EKU_ANY = '2.5.29.37.0'
>  EKU_PLACEHOLDER = '1.3.6.1.4.1.3319.6.10.16'
>  
> +SAN_DNSNAME = 'DNS name'
> +SAN_RFC822NAME = 'RFC822 Name'
> +SAN_OTHERNAME_UPN = 'Other Name (OID.1.3.6.1.4.1.311.20.2.3)'
> +SAN_OTHERNAME_KRB5PRINCIPALNAME = 'Other Name (OID.1.3.6.1.5.2.2)'
> +
>  _subject_base = None
>  
>  def subject_base():
> @@ -374,6 +379,113 @@ def encode_ext_key_usage(ext_key_usage):
>      eku = encoder.encode(eku)
>      return _encode_extension('2.5.29.37', EKU_ANY not in ext_key_usage, eku)
>  
> +
> +class _AnotherName(univ.Sequence):
> +    componentType = namedtype.NamedTypes(
> +        namedtype.NamedType('type-id', univ.ObjectIdentifier()),
> +        namedtype.NamedType('value', univ.Any().subtype(
> +            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> +        ),
> +    )
> +
> +
> +class _GeneralName(univ.Choice):
> +    componentType = namedtype.NamedTypes(
> +        namedtype.NamedType('otherName', _AnotherName().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> +        ),
> +        namedtype.NamedType('rfc822Name', char.IA5String().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> +        ),
> +        namedtype.NamedType('dNSName', char.IA5String().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
> +        ),
> +        namedtype.NamedType('x400Address', univ.Sequence().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))
> +        ),
> +        namedtype.NamedType('directoryName', univ.Choice().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
> +        ),
> +        namedtype.NamedType('ediPartyName', univ.Sequence().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))
> +        ),
> +        namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))
> +        ),
> +        namedtype.NamedType('iPAddress', univ.OctetString().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))
> +        ),
> +        namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(
> +            implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8))
> +        ),
> +    )
> +
> +
> +class _SubjectAltName(univ.SequenceOf):
> +    componentType = _GeneralName()
> +
> +
> +class _PrincipalName(univ.Sequence):
> +    componentType = namedtype.NamedTypes(
> +        namedtype.NamedType('name-type', univ.Integer().subtype(
> +            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> +        ),
> +        namedtype.NamedType('name-string', univ.SequenceOf(char.GeneralString()).subtype(
> +            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> +        ),
> +    )
> +
> +
> +class _KRB5PrincipalName(univ.Sequence):
> +    componentType = namedtype.NamedTypes(
> +        namedtype.NamedType('realm', char.GeneralString().subtype(
> +            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
> +        ),
> +        namedtype.NamedType('principalName', _PrincipalName().subtype(
> +            explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
> +        ),
> +    )
> +
> +
> +def _decode_krb5principalname(data):
> +    principal = decoder.decode(data, asn1Spec=_KRB5PrincipalName())[0]
> +    realm = (str(principal['realm']).replace('\\', '\\\\')
> +                                    .replace('@', '\\@'))
> +    name = principal['principalName']['name-string']
> +    name = '/'.join(str(n).replace('\\', '\\\\')
> +                          .replace('/', '\\/')
> +                          .replace('@', '\\@') for n in name)
> +    name = '%s@%s' % (name, realm)
> +    return name
> +
> +
> +def decode_generalnames(secitem):
> +    """
> +    Decode a GeneralNames object (this the data for the Subject
> +    Alt Name and Issuer Alt Name extensions, among others).
> +
> +    ``secitem``
> +      The input is the DER-encoded extension data, without the
> +      OCTET STRING header, as an nss SecItem object.
> +
> +    Return a list of tuples of name types (as string, suitable for
> +    presentation) and names (as string, suitable for presentation).
> +
> +    """
> +    nss_names = nss.x509_alt_name(secitem, repr_kind=nss.AsObject)
> +    asn1_names = decoder.decode(secitem.data, asn1Spec=_SubjectAltName())[0]
> +    names = []
> +    for nss_name, asn1_name in zip(nss_names, asn1_names):
> +        name_type = nss_name.type_string
> +        if name_type == SAN_OTHERNAME_KRB5PRINCIPALNAME:
> +            name = _decode_krb5principalname(asn1_name['otherName']['value'])
> +        else:
> +            name = nss_name.name
> +        names.append((name_type, name))
> +
> +    return names
> +
> +
>  if __name__ == '__main__':
>      # this can be run with:
>      # python ipalib/x509.py < /etc/ipa/ca.crt
> diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
> index 06041d3083565e8d093b610473d6083111d406d2..85be2cf4daeb282b2c2ba866017c8e5745abda6d 100644
> --- a/ipaserver/plugins/cert.py
> +++ b/ipaserver/plugins/cert.py
> @@ -535,7 +535,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>  
>          # Validate the subject alt name, if any
>          for name_type, name in subjectaltname:
> -            if name_type == pkcs10.SAN_DNSNAME:
> +            if name_type == x509.SAN_DNSNAME:
>                  name = unicode(name)
>                  alt_principal_obj = None
>                  alt_principal_string = unicode(principal)
> @@ -566,13 +566,13 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                              "with subject alt name '%s'.") % name)
>                  if alt_principal_string is not None and not bypass_caacl:
>                      caacl_check(principal_type, principal, ca, profile_id)
> -            elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME,
> -                               pkcs10.SAN_OTHERNAME_UPN):
> +            elif name_type in (x509.SAN_OTHERNAME_KRB5PRINCIPALNAME,
> +                               x509.SAN_OTHERNAME_UPN):
>                  if name != principal_string:
>                      raise errors.ACIError(
>                          info=_("Principal '%s' in subject alt name does not "
>                                 "match requested principal") % name)
> -            elif name_type == pkcs10.SAN_RFC822NAME:
> +            elif name_type == x509.SAN_RFC822NAME:
>                  if principal_type == USER:
>                      if name not in principal_obj.get('mail', []):
>                          raise errors.ValidationError(
> -- 
> 2.5.5
> 

> From 27a31d28a0af4a84545678f72ae86946dc9ebeaf Mon Sep 17 00:00:00 2001
> From: Fraser Tweedale <ftweedal at redhat.com>
> Date: Fri, 22 Jul 2016 12:05:13 +1000
> Subject: [PATCH 93/94] x509: fix SAN directoryName parsing
> 
> The subjectAltName extension parsing code in ipalib.x509 fails on
> directoryName values because the Choice structure is not endowed
> with an inner type.  Implement the Name structure, whose inner type
> is a CHOICE { SEQUENCE OF RelativeDistinguishedName }, to resolve.
> 
> Note that the structure still does not get fully parsed; only enough
> to recognise the SequenceOf tag and not fail.
> 
> Part of: https://fedorahosted.org/freeipa/ticket/6022
> ---
>  ipalib/x509.py | 12 +++++++++---
>  1 file changed, 9 insertions(+), 3 deletions(-)
> 
> diff --git a/ipalib/x509.py b/ipalib/x509.py
> index 15168de08240a84794efef409d022eaa983291c9..2dc67441c92686826dd24f00a5ad30566cd032da 100644
> --- a/ipalib/x509.py
> +++ b/ipalib/x509.py
> @@ -196,6 +196,12 @@ def is_self_signed(certificate, datatype=PEM, dbdir=None):
>      del nsscert
>      return self_signed
>  
> +class _Name(univ.Choice):
> +    componentType = namedtype.NamedTypes(
> +        namedtype.NamedType('rdnSequence',
> +            univ.SequenceOf()),
> +    )
> +
>  class _TBSCertificate(univ.Sequence):
>      componentType = namedtype.NamedTypes(
>          namedtype.NamedType(
> @@ -204,9 +210,9 @@ class _TBSCertificate(univ.Sequence):
>                  tag.tagClassContext, tag.tagFormatSimple, 0))),
>          namedtype.NamedType('serialNumber', univ.Integer()),
>          namedtype.NamedType('signature', univ.Sequence()),
> -        namedtype.NamedType('issuer', univ.Sequence()),
> +        namedtype.NamedType('issuer', _Name()),
>          namedtype.NamedType('validity', univ.Sequence()),
> -        namedtype.NamedType('subject', univ.Sequence()),
> +        namedtype.NamedType('subject', _Name()),
>          namedtype.NamedType('subjectPublicKeyInfo', univ.Sequence()),
>          namedtype.OptionalNamedType(
>              'issuerUniquedID',
> @@ -403,7 +409,7 @@ class _GeneralName(univ.Choice):
>          namedtype.NamedType('x400Address', univ.Sequence().subtype(
>              implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))
>          ),
> -        namedtype.NamedType('directoryName', univ.Choice().subtype(
> +        namedtype.NamedType('directoryName', _Name().subtype(
>              implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
>          ),
>          namedtype.NamedType('ediPartyName', univ.Sequence().subtype(
> -- 
> 2.5.5
> 

> From 6e8dac22d4a985ce344c0d8583260cf5ceccbc1b Mon Sep 17 00:00:00 2001
> From: Fraser Tweedale <ftweedal at redhat.com>
> Date: Fri, 22 Jul 2016 12:11:59 +1000
> Subject: [PATCH 94/94] x509: use NSS enums and OIDs to identify SAN types
> 
> GeneralName parsing currently relies heavily on strings from NSS.
> Make the code hopefully less brittle by identifying GeneralName
> types by NSS enums and, for otherName, the name-type OID also.
> 
> Part of: https://fedorahosted.org/freeipa/ticket/6022
> ---
>  ipalib/x509.py            | 30 +++++++++++++++++++++++-------
>  ipaserver/plugins/cert.py | 19 ++++++++++---------
>  2 files changed, 33 insertions(+), 16 deletions(-)
> 
> diff --git a/ipalib/x509.py b/ipalib/x509.py
> index 2dc67441c92686826dd24f00a5ad30566cd032da..541609fbc1a53a73eafcff2327e53a292c2d9a3c 100644
> --- a/ipalib/x509.py
> +++ b/ipalib/x509.py
> @@ -33,6 +33,7 @@
>  
>  from __future__ import print_function
>  
> +import collections
>  import os
>  import sys
>  import base64
> @@ -63,10 +64,8 @@ EKU_EMAIL_PROTECTION = '1.3.6.1.5.5.7.3.4'
>  EKU_ANY = '2.5.29.37.0'
>  EKU_PLACEHOLDER = '1.3.6.1.4.1.3319.6.10.16'
>  
> -SAN_DNSNAME = 'DNS name'
> -SAN_RFC822NAME = 'RFC822 Name'
> -SAN_OTHERNAME_UPN = 'Other Name (OID.1.3.6.1.4.1.311.20.2.3)'
> -SAN_OTHERNAME_KRB5PRINCIPALNAME = 'Other Name (OID.1.3.6.1.5.2.2)'
> +SAN_UPN = '1.3.6.1.4.1.311.20.2.3'
> +SAN_KRB5PRINCIPALNAME = '1.3.6.1.5.2.2'
>  
>  _subject_base = None
>  
> @@ -465,6 +464,10 @@ def _decode_krb5principalname(data):
>      return name
>  
>  
> +GeneralNameInfo = collections.namedtuple(
> +        'GeneralNameInfo', ('type', 'desc', 'value'))
> +
> +
>  def decode_generalnames(secitem):
>      """
>      Decode a GeneralNames object (this the data for the Subject
> @@ -482,12 +485,25 @@ def decode_generalnames(secitem):
>      asn1_names = decoder.decode(secitem.data, asn1Spec=_SubjectAltName())[0]
>      names = []
>      for nss_name, asn1_name in zip(nss_names, asn1_names):
> -        name_type = nss_name.type_string
> -        if name_type == SAN_OTHERNAME_KRB5PRINCIPALNAME:
> +        # NOTE: we use the NSS enum to identify the name type.
> +        # (For otherName we also tuple it up with the type-id OID).
> +        # The enum does not correspond exactly to the ASN.1 tags.
> +        # If we ever want to switch to using the true tag numbers,
> +        # the expression to get the tag is:
> +        #
> +        #   asn1_name.getComponent().getTagSet()[0].asTuple()[2]
> +        #
> +        if nss_name.type_enum == nss.certOtherName:
> +            oid = str(asn1_name['otherName']['type-id'])
> +            nametype = (nss_name.type_enum, oid)
> +        else:
> +            nametype = nss_name.type_enum
> +
> +        if nametype == (nss.certOtherName, SAN_KRB5PRINCIPALNAME):
>              name = _decode_krb5principalname(asn1_name['otherName']['value'])
>          else:
>              name = nss_name.name
> -        names.append((name_type, name))
> +        names.append(GeneralNameInfo(nametype, nss_name.type_string, name))
>  
>      return names
>  
> diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
> index 85be2cf4daeb282b2c2ba866017c8e5745abda6d..207f6964645254ebc417cab80634a68911ae0a08 100644
> --- a/ipaserver/plugins/cert.py
> +++ b/ipaserver/plugins/cert.py
> @@ -534,8 +534,8 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                  "to the 'userCertificate' attribute of entry '%s'.") % dn)
>  
>          # Validate the subject alt name, if any
> -        for name_type, name in subjectaltname:
> -            if name_type == x509.SAN_DNSNAME:
> +        for name_type, desc, name in subjectaltname:
> +            if name_type == nss.certDNSName:
>                  name = unicode(name)
>                  alt_principal_obj = None
>                  alt_principal_string = unicode(principal)
> @@ -549,7 +549,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                          raise errors.ValidationError(
>                              name='csr',
>                              error=_("subject alt name type %s is forbidden "
> -                                "for user principals") % name_type
> +                                "for user principals") % desc
>                          )
>                  except errors.NotFound:
>                      # We don't want to issue any certificates referencing
> @@ -566,13 +566,15 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                              "with subject alt name '%s'.") % name)
>                  if alt_principal_string is not None and not bypass_caacl:
>                      caacl_check(principal_type, principal, ca, profile_id)
> -            elif name_type in (x509.SAN_OTHERNAME_KRB5PRINCIPALNAME,
> -                               x509.SAN_OTHERNAME_UPN):
> +            elif name_type in [
> +                (nss.certOtherName, x509.SAN_UPN),
> +                (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME),
> +            ]:
>                  if name != principal_string:
>                      raise errors.ACIError(
>                          info=_("Principal '%s' in subject alt name does not "
>                                 "match requested principal") % name)
> -            elif name_type == x509.SAN_RFC822NAME:
> +            elif name_type == nss.certRFC822Name:
>                  if principal_type == USER:
>                      if name not in principal_obj.get('mail', []):
>                          raise errors.ValidationError(
> @@ -585,12 +587,11 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                      raise errors.ValidationError(
>                          name='csr',
>                          error=_("subject alt name type %s is forbidden "
> -                            "for non-user principals") % name_type
> +                            "for non-user principals") % desc
>                      )
>              else:
>                  raise errors.ACIError(
> -                    info=_("Subject alt name type %s is forbidden") %
> -                         name_type)
> +                    info=_("Subject alt name type %s is forbidden") % desc)
>  
>          # Request the certificate
>          result = self.Backend.ra.request_certificate(
> -- 
> 2.5.5
> 

> From 9481e0436dc46b4668f1a45bd21f97c2096da142 Mon Sep 17 00:00:00 2001
> From: Fraser Tweedale <ftweedal at redhat.com>
> Date: Mon, 15 Aug 2016 15:39:49 +1000
> Subject: [PATCH] x509: include otherName DER value in GeneralNameInfo
> 
> We want to include the whole DER value when we pretty-print
> unrecognised otherNames, so add a field to the GeneralNameInfo
> namedtuple and populate it for otherNames.
> 
> Part of: https://fedorahosted.org/freeipa/ticket/6022
> ---
>  ipalib/x509.py            | 13 +++++++++----
>  ipaserver/plugins/cert.py |  2 +-
>  2 files changed, 10 insertions(+), 5 deletions(-)
> 
> diff --git a/ipalib/x509.py b/ipalib/x509.py
> index 541609fbc1a53a73eafcff2327e53a292c2d9a3c..e986a97a58aafd3aeab08765a397edbf67c7841a 100644
> --- a/ipalib/x509.py
> +++ b/ipalib/x509.py
> @@ -465,7 +465,7 @@ def _decode_krb5principalname(data):
>  
>  
>  GeneralNameInfo = collections.namedtuple(
> -        'GeneralNameInfo', ('type', 'desc', 'value'))
> +        'GeneralNameInfo', ('type', 'desc', 'value', 'der_value'))
>  
>  
>  def decode_generalnames(secitem):
> @@ -477,8 +477,9 @@ def decode_generalnames(secitem):
>        The input is the DER-encoded extension data, without the
>        OCTET STRING header, as an nss SecItem object.
>  
> -    Return a list of tuples of name types (as string, suitable for
> -    presentation) and names (as string, suitable for presentation).
> +    Return a list of ``GeneralNameInfo`` namedtuples.  The
> +    ``der_value`` field is set for otherNames, otherwise it is
> +    ``None``.
>  
>      """
>      nss_names = nss.x509_alt_name(secitem, repr_kind=nss.AsObject)
> @@ -496,14 +497,18 @@ def decode_generalnames(secitem):
>          if nss_name.type_enum == nss.certOtherName:
>              oid = str(asn1_name['otherName']['type-id'])
>              nametype = (nss_name.type_enum, oid)
> +            der_value = asn1_name['otherName']['value'].asOctets()
>          else:
>              nametype = nss_name.type_enum
> +            der_value = None
>  
>          if nametype == (nss.certOtherName, SAN_KRB5PRINCIPALNAME):
>              name = _decode_krb5principalname(asn1_name['otherName']['value'])
>          else:
>              name = nss_name.name
> -        names.append(GeneralNameInfo(nametype, nss_name.type_string, name))
> +
> +        gni = GeneralNameInfo(nametype, nss_name.type_string, name, der_value)
> +        names.append(gni)
>  
>      return names
>  
> diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
> index 207f6964645254ebc417cab80634a68911ae0a08..30c708113942fca4d1f11aa1219367110e518309 100644
> --- a/ipaserver/plugins/cert.py
> +++ b/ipaserver/plugins/cert.py
> @@ -534,7 +534,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>                  "to the 'userCertificate' attribute of entry '%s'.") % dn)
>  
>          # Validate the subject alt name, if any
> -        for name_type, desc, name in subjectaltname:
> +        for name_type, desc, name, der_name in subjectaltname:
>              if name_type == nss.certDNSName:
>                  name = unicode(name)
>                  alt_principal_obj = None
> -- 
> 2.5.5
> 

> From 0706141a0457a90e58c94527c65d7f1ead87c719 Mon Sep 17 00:00:00 2001
> From: Fraser Tweedale <ftweedal at redhat.com>
> Date: Thu, 14 Jul 2016 21:36:33 +1000
> Subject: [PATCH] cert-show: show subject alternative names
> 
> Enhance the cert-show command to return subject alternative name
> values.
> 
> Fixes: https://fedorahosted.org/freeipa/ticket/6022
> ---
>  ipaserver/plugins/cert.py | 125 ++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 120 insertions(+), 5 deletions(-)
> 
> diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
> index 30c708113942fca4d1f11aa1219367110e518309..c42ba2de06df5d6204275fb9d31694268814a269 100644
> --- a/ipaserver/plugins/cert.py
> +++ b/ipaserver/plugins/cert.py
> @@ -38,7 +38,7 @@ from ipalib import ngettext
>  from ipalib.constants import IPA_CA_CN
>  from ipalib.crud import Create, PKQuery, Retrieve, Search
>  from ipalib.frontend import Method, Object
> -from ipalib.parameters import Bytes, DateTime, DNParam, Principal
> +from ipalib.parameters import Bytes, DateTime, DNParam, DNSNameParam, Principal
>  from ipalib.plugable import Registry
>  from .virtual import VirtualCommand
>  from .baseldap import pkey_to_value
> @@ -49,6 +49,7 @@ from ipalib.request import context
>  from ipalib import output
>  from ipapython import kerberos
>  from ipapython.dn import DN
> +from ipapython.ipa_log_manager import root_logger
>  from ipaserver.plugins.service import normalize_principal, validate_realm
>  
>  if six.PY3:
> @@ -293,9 +294,74 @@ class BaseCertObject(Object):
>              label=_('Serial number (hex)'),
>              flags={'no_create', 'no_update', 'no_search'},
>          ),
> +        Str(
> +            'san_rfc822name*',
> +            label=_('Subject Alternative Name (Email)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        DNSNameParam(
> +            'san_dnsname*',
> +            label=_('Subject Alternative Name (DNS)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_x400address*',
> +            label=_('Subject Alternative Name (X.400 address)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_directoryname*',
> +            label=_('Subject Alternative Name (Directory name)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_edipartyname*',
> +            label=_('Subject Alternative Name (EDI Party name)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_uri*',
> +            label=_('Subject Alternative Name (URI)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_ipaddress*',
> +            label=_('Subject Alternative Name (IP Address)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_oid*',
> +            label=_('Subject Alternative Name (OID)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Principal(
> +            'san_other_upn*',
> +            label=_('Subject Alternative Name (UPN)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Principal(
> +            'san_other_kpn*',
> +            label=_('Subject Alternative Name (Kerberos Principal)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
> +        Str(
> +            'san_other*',
> +            label=_('Subject Alternative Name (Other Name)'),
> +            flags={'no_create', 'no_update', 'no_search'},
> +        ),
>      )
>  
> -    def _parse(self, obj):
> +    def _parse(self, obj, generic_othernames):
> +        """Extract certificate-specific data into a result object.
> +
> +        ``obj``
> +            Result object containing certificate, into which extracted
> +            data will be inserted.
> +        ``generic_othernames``
> +            If ``True`` add recognised otherNames to the generic
> +            ``san_other`` attribute as well as their own attribute.
> +
> +        """
>          cert = x509.load_certificate(obj['certificate'])
>          obj['subject'] = DN(unicode(cert.subject))
>          obj['issuer'] = DN(unicode(cert.issuer))
> @@ -308,6 +374,55 @@ class BaseCertObject(Object):
>          obj['serial_number'] = cert.serial_number
>          obj['serial_number_hex'] = u'0x%X' % cert.serial_number
>  
> +        try:
> +            ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME)
> +            general_names = x509.decode_generalnames(ext_san.value)
> +        except KeyError:
> +            general_names = []
> +
> +        for name_type, desc, name, der_name in general_names:
> +            try:
> +                self._add_san_attribute(
> +                    obj, generic_othernames, name_type, name, der_name)
> +            except Exception as e:
> +                # Invalid GeneralName (i.e. not a valid X.509 cert);
> +                # don't fail but log something about it
> +                root_logger.warning(
> +                    "Encountered bad GeneralName; skipping", exc_info=True)
> +
> +
> +    def _add_san_attribute(
> +            self, obj, generic_othernames, name_type, name, der_name):
> +        name_type_map = {
> +            nss.certRFC822Name: 'san_rfc822name',
> +            nss.certDNSName: 'san_dnsname',
> +            nss.certX400Address: 'san_x400address',
> +            nss.certDirectoryName: 'san_directoryname',
> +            nss.certEDIPartyName: 'san_edipartyname',
> +            nss.certURI: 'san_uri',
> +            nss.certIPAddress: 'san_ipaddress',
> +            nss.certRegisterID: 'san_oid',
> +            (nss.certOtherName, x509.SAN_UPN): 'san_other_upn',
> +            (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME): 'san_other_kpn',
> +        }
> +
> +        attr_name = name_type_map.get(name_type, 'san_other')
> +
> +        if attr_name != 'san_other':
> +            name_formatted = name
> +        else:
> +            # display as "OID : b64(DER)"
> +            name_formatted = u'{}:{}'.format(
> +                name_type[1], base64.b64encode(der_name))
> +        attr_value = self.params[attr_name].type(name_formatted)
> +        obj.setdefault(attr_name, []).append(attr_value)
> +
> +        if generic_othernames and attr_name.startswith('san_other_'):
> +            # recurse, faking the name type so that OID is still conveyed,
> +            # but it won't be found in name_type_map
> +            self._add_san_attribute(
> +                obj, False, (None, name_type[1]), name, der_name)
> +
>  
>  class BaseCertMethod(Method):
>      def get_options(self):
> @@ -597,7 +712,7 @@ class cert_request(Create, BaseCertMethod, VirtualCommand):
>          result = self.Backend.ra.request_certificate(
>              csr, profile_id, ca_id, request_type=request_type)
>          if not raw:
> -            self.obj._parse(result)
> +            self.obj._parse(result, all)
>              result['request_id'] = int(result['request_id'])
>  
>          # Success? Then add it to the principal's entry
> @@ -775,7 +890,7 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
>  
>          if not raw:
>              result['certificate'] = result['certificate'].replace('\r\n', '')
> -            self.obj._parse(result)
> +            self.obj._parse(result, all)
>              result['revoked'] = ('revocation_reason' in result)
>              if 'owner' in result:
>                  self.obj._fill_owners(result)
> @@ -1143,7 +1258,7 @@ class cert_find(Search, CertMethod):
>                      if 'certificate' in obj:
>                          obj['certificate'] = (
>                              obj['certificate'].replace('\r\n', ''))
> -                        self.obj._parse(obj)
> +                        self.obj._parse(obj, all)
>                          if not all:
>                              del obj['certificate']
>                              del obj['valid_not_before']
> -- 
> 2.5.5
> 




More information about the Freeipa-devel mailing list