Thanks for review; rebased and updated patch attached. Only 0090 has substantive changes.
Cheers, Fraser On Mon, Aug 22, 2016 at 09:22:08AM +0200, Jan Cholasta wrote: > On 19.8.2016 13:11, Fraser Tweedale wrote: > > 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 > > Please fix this PEP8 issue before pushing: > > ./ipaserver/plugins/cert.py:597:17: W503 line break before binary operator > > > Patch 0098: 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. > > OK. > > > > > > > > 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. > > OK. > > > > > > > > 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. > > Works for me, but see Lukáš's reply, I think he has a point. Maybe we can > make a compromise and show only supported name types by default and > everything with --all? > Now only showing DNSName, RFC822Name, DirectoryName, UPN and KRBPrincipalName unless --all is given. > > > > > > > 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) > > OK. > > > 5) san_directoryname should be a DNParam rather than Str. > Fixed, thanks. > > 6) Could we use "Subject <name type>" instead of "Subject Alternative Name > (<name type>)" for labels? Or something else which is shorter and has the > name type more "visible" than the current form. > No worries. > > 7) The patch needs a rebase. > > > -- > Jan Cholasta
From 9dbe4c3cebb1279aefefe3dae9f3da2232d9c12f Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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 6dd9f6ffcdcd9d051d50d912996fea2104d71dff..c25965080e40dcbcaa21ab140509eaf2e29d7c61 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -560,7 +560,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) @@ -591,13 +591,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 4c72105829ffa02bde5e11669440ca2e095c35e9 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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 80172d2ebc4d5b1f40206bf527eb3e3f13a0c4ab Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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 c25965080e40dcbcaa21ab140509eaf2e29d7c61..3e9eda5047b839171e6a0bc96f92927fbb191824 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -559,8 +559,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) @@ -574,7 +574,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 @@ -591,13 +591,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( @@ -610,12 +612,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 ffde58d7e6fad175049e5ef5c9760978769c49fe Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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 3e9eda5047b839171e6a0bc96f92927fbb191824..9ee0b38c0aeadf2041b7d322417bbeab417b23a6 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -559,7 +559,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 d81c6c4cd7432783aaee511f2426f72a9cb1bddf Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@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 | 130 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py index 9ee0b38c0aeadf2041b7d322417bbeab417b23a6..d4c27a4dd80b2d047cf520524ced6724e5ec329e 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -39,7 +39,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 @@ -50,6 +50,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: @@ -312,9 +313,77 @@ class BaseCertObject(Object): label=_('Serial number (hex)'), flags={'no_create', 'no_update', 'no_search'}, ), + Str( + 'san_rfc822name*', + label=_('Subject email address'), + flags={'no_create', 'no_update', 'no_search'}, + ), + DNSNameParam( + 'san_dnsname*', + label=_('Subject DNS name'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_x400address*', + label=_('Subject X.400 address'), + flags={'no_create', 'no_update', 'no_search'}, + ), + DNParam( + 'san_directoryname*', + label=_('Subject directory name'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_edipartyname*', + label=_('Subject EDI Party name'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_uri*', + label=_('Subject URI'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_ipaddress*', + label=_('Subject IP Address'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_oid*', + label=_('Subject OID'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Principal( + 'san_other_upn*', + label=_('Subject UPN'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Principal( + 'san_other_kpn*', + label=_('Subject Kerberos principal name'), + flags={'no_create', 'no_update', 'no_search'}, + ), + Str( + 'san_other*', + label=_('Subject Other Name'), + flags={'no_create', 'no_update', 'no_search'}, + ), ) def _parse(self, obj, full=True): + """Extract certificate-specific data into a result object. + + ``obj`` + Result object containing certificate, into which extracted + data will be inserted. + ``full`` + Whether to include all fields, or only the ones we guess + people want to see most of the time. Also add + recognised otherNames to the generic ``san_other`` + attribute when ``True`` in addition to the specialised + attribute. + + """ cert = obj.get('certificate') if cert is not None: cert = x509.load_certificate(cert) @@ -329,11 +398,66 @@ class BaseCertObject(Object): obj['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) + 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, full, 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) + serial_number = obj.get('serial_number') if serial_number is not None: obj['serial_number_hex'] = u'0x%X' % serial_number + def _add_san_attribute( + self, obj, full, 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', + } + default_attrs = { + 'san_rfc822name', 'san_dnsname', 'san_directoryname', + 'san_other_upn', 'san_other_kpn', + } + + attr_name = name_type_map.get(name_type, 'san_other') + + if full or attr_name in default_attrs: + 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 full and attr_name.startswith('san_other_'): + # also include known otherName in generic otherName attribute + name_formatted = u'{}:{}'.format( + name_type[1], base64.b64encode(der_name)) + attr_value = self.params['san_other'].type(name_formatted) + obj.setdefault('san_other', []).append(attr_value) + + class BaseCertMethod(Method): def get_options(self): yield Str('cacn?', @@ -622,7 +746,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 @@ -800,7 +924,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) self.obj._fill_owners(result) -- 2.5.5
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code