Hi all, The attached patch includes SANs in cert-show output. If you have certs with esoteric altnames (especially any that are more than just ASN.1 string types), please test with those certs.
https://fedorahosted.org/freeipa/ticket/6022 Thanks, Fraser
From f56d698009f32a1b8760048848117148164fad33 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 Update the cert-show command to return subject alternative name values. Also move 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 | 28 ++++++++++-- 3 files changed, 140 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..2a7c007e237a75f8a441e9056cdeb55191a147f9 100644 --- a/ipaserver/plugins/cert.py +++ b/ipaserver/plugins/cert.py @@ -293,6 +293,11 @@ class BaseCertObject(Object): label=_('Serial number (hex)'), flags={'no_create', 'no_update', 'no_search'}, ), + Str( + 'subject_alt_name*', + label=_('Subject Alternative Name'), + flags={'no_create', 'no_update', 'no_search'}, + ), ) def _parse(self, obj): @@ -307,6 +312,21 @@ class BaseCertObject(Object): nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) obj['serial_number'] = cert.serial_number obj['serial_number_hex'] = u'0x%X' % cert.serial_number + try: + type_strings = { + x509.SAN_DNSNAME: 'DNS', + x509.SAN_RFC822NAME: 'Email', + x509.SAN_OTHERNAME_UPN: 'UPN', + x509.SAN_OTHERNAME_KRB5PRINCIPALNAME: 'Kerberos Principal', + } + ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME) + general_names = x509.decode_generalnames(ext_san.value) + obj['subject_alt_name'] = [ + '{}: {}'.format(type_strings.get(name_type, name_type), name) + for name_type, name in general_names + ] + except KeyError: + pass class BaseCertMethod(Method): @@ -535,7 +555,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 +586,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
-- 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