[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