[Freeipa-devel] [PATCH] cert-show: show subject alternative names

Fraser Tweedale ftweedal at redhat.com
Thu Jul 14 11:44:36 UTC 2016


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
-------------- next part --------------
From f56d698009f32a1b8760048848117148164fad33 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

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



More information about the Freeipa-devel mailing list