[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