From 0245d2aadf8b38ba68aeacf70761bd09ad927951 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Aug 26 2016 07:09:45 +0000 Subject: 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 Reviewed-By: Jan Cholasta --- diff --git a/ipalib/pkcs10.py b/ipalib/pkcs10.py index e340c1a..158ebb3 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 8219492..15168de 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 6dd9f6f..c259650 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(