def verify_kdc_cert_validity(kdc_cert, ca_certs, realm): with NamedTemporaryFile() as kdc_file, NamedTemporaryFile() as ca_file: kdc_file.write(kdc_cert.public_bytes(x509.Encoding.PEM)) kdc_file.flush() x509.write_certificate_list(ca_certs, ca_file.name) ca_file.flush() try: ipautil.run([ paths.OPENSSL, 'verify', '-CAfile', ca_file.name, kdc_file.name ], capture_output=True) except ipautil.CalledProcessError as e: raise ValueError(e.output) try: eku = kdc_cert.extensions.get_extension_for_class( cryptography.x509.ExtendedKeyUsage) list(eku.value).index( cryptography.x509.ObjectIdentifier(x509.EKU_PKINIT_KDC)) except (cryptography.x509.ExtensionNotFound, ValueError): raise ValueError("invalid for a KDC") principal = str(Principal(['krbtgt', realm], realm)) gns = x509.process_othernames(kdc_cert.san_general_names) for gn in gns: if isinstance(gn, x509.KRB5PrincipalName) and gn.name == principal: break else: raise ValueError("invalid for realm %s" % realm)
def verify_kdc_cert_validity(kdc_cert, ca_certs, realm): with NamedTemporaryFile() as kdc_file, NamedTemporaryFile() as ca_file: kdc_file.write(kdc_cert.public_bytes(x509.Encoding.PEM)) kdc_file.flush() x509.write_certificate_list(ca_certs, ca_file.name) ca_file.flush() try: ipautil.run( [paths.OPENSSL, 'verify', '-CAfile', ca_file.name, kdc_file.name], capture_output=True) except ipautil.CalledProcessError as e: raise ValueError(e.output) try: eku = kdc_cert.extensions.get_extension_for_class( cryptography.x509.ExtendedKeyUsage) list(eku.value).index( cryptography.x509.ObjectIdentifier(x509.EKU_PKINIT_KDC)) except (cryptography.x509.ExtensionNotFound, ValueError): raise ValueError("invalid for a KDC") principal = str(Principal(['krbtgt', realm], realm)) gns = x509.process_othernames(kdc_cert.san_general_names) for gn in gns: if isinstance(gn, x509.KRB5PrincipalName) and gn.name == principal: break else: raise ValueError("invalid for realm %s" % realm)
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. """ if 'certificate' in obj: cert = x509.load_certificate(obj['certificate']) obj['subject'] = DN(cert.subject) obj['issuer'] = DN(cert.issuer) obj['serial_number'] = cert.serial obj['valid_not_before'] = x509.format_datetime( cert.not_valid_before) obj['valid_not_after'] = x509.format_datetime(cert.not_valid_after) if full: obj['md5_fingerprint'] = x509.to_hex_with_colons( cert.fingerprint(hashes.MD5())) obj['sha1_fingerprint'] = x509.to_hex_with_colons( cert.fingerprint(hashes.SHA1())) general_names = x509.process_othernames( x509.get_san_general_names(cert)) for gn in general_names: try: self._add_san_attribute(obj, full, gn) except Exception: # 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 execute(self, csr, all=False, raw=False, **kw): ca_enabled_check() ldap = self.api.Backend.ldap2 add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) # Check that requested authority exists (done before CA ACL # enforcement so that user gets better error message if # referencing nonexistant CA) and look up authority ID. # ca = kw['cacn'] ca_obj = api.Command.ca_show(ca)['result'] ca_id = ca_obj['ipacaid'][0] """ Access control is partially handled by the ACI titled 'Hosts can modify service userCertificate'. This is for the case where a machine binds using a host/ prinicpal. It can only do the request if the target hostname is in the managedBy attribute which is managed using the add/del member commands. Binding with a user principal one needs to be in the request_certs taskgroup (directly or indirectly via role membership). """ principal = kw.get('principal') principal_string = unicode(principal) if principal.is_user: principal_type = USER elif principal.is_host: principal_type = HOST else: principal_type = SERVICE bind_principal = kerberos.Principal(getattr(context, 'principal')) bind_principal_string = unicode(bind_principal) if bind_principal.is_user: bind_principal_type = USER elif bind_principal.is_host: bind_principal_type = HOST else: bind_principal_type = SERVICE if (bind_principal_string != principal_string and bind_principal_type != HOST): # Can the bound principal request certs for another principal? self.check_access() try: self.check_access("request certificate ignore caacl") bypass_caacl = True except errors.ACIError: bypass_caacl = False if not bypass_caacl: caacl_check(principal_type, principal, ca, profile_id) try: csr_obj = pkcs10.load_certificate_request(csr) except ValueError as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) try: ext_san = csr_obj.extensions.get_extension_for_oid( cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME) except cryptography.x509.extensions.ExtensionNotFound: ext_san = None # self-service and host principals may bypass SAN permission check if (bind_principal_string != principal_string and bind_principal_type != HOST): if ext_san is not None: self.check_access('request certificate with subjectaltname') dn = None principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) elif principal_type == HOST: principal_obj = api.Command['host_show'](principal.hostname, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal.username, all=True) except errors.NotFound as e: if add: if principal_type == SERVICE: principal_obj = api.Command['service_add']( principal_string, force=True) else: princtype_str = PRINCIPAL_TYPE_STRING_MAP[principal_type] raise errors.OperationNotSupportedForPrincipalType( operation=_("'add' option"), principal_type=princtype_str) else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal # # We only look at the "most specific" CN value cns = csr_obj.subject.get_attributes_for_oid( cryptography.x509.oid.NameOID.COMMON_NAME) if len(cns) == 0: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) cn = cns[-1].value # "most specific" is end of list if principal_type in (SERVICE, HOST): if cn.lower() != principal.hostname.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal.hostname)) elif principal_type == USER: # check user name if cn != principal.username: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address # # fail if any email addr from DN does not appear in ldap entry email_addrs = csr_obj.subject.get_attributes_for_oid( cryptography.x509.oid.NameOID.EMAIL_ADDRESS) if len(set(email_addrs) - set(principal_obj.get('mail', []))) > 0: raise errors.ValidationError( name='csr', error=_("DN emailAddress does not match " "any of user's email addresses")) # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError( info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any generalnames = [] if ext_san is not None: generalnames = x509.process_othernames(ext_san.value) for gn in generalnames: if isinstance(gn, cryptography.x509.general_name.DNSName): name = gn.value alt_principal = None alt_principal_obj = None try: if principal_type == HOST: alt_principal = kerberos.Principal((u'host', name), principal.realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal = kerberos.Principal( (principal.service_name, name), principal.realm) alt_principal_obj = api.Command['service_show']( alt_principal, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % "DNSName") except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this # host record related to this certificate. raise errors.NotFound(reason=_( 'The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) if alt_principal_obj is not None: altdn = alt_principal_obj['result']['dn'] if not ldap.can_write(altdn, "usercertificate"): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if alt_principal is not None and not bypass_caacl: caacl_check(principal_type, alt_principal, ca, profile_id) elif isinstance(gn, (x509.KRB5PrincipalName, x509.UPN)): if gn.name != principal_string: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % gn.name) elif isinstance(gn, cryptography.x509.general_name.RFC822Name): if principal_type == USER: if gn.value not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("RFC822Name does not match " "any of user's email addresses")) else: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for non-user principals") % "RFC822Name") else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % type(gn).__name__) # Request the certificate try: # re-serialise to PEM, in case the user-supplied data has # extraneous material that will cause Dogtag to freak out csr_pem = csr_obj.public_bytes(serialization.Encoding.PEM) result = self.Backend.ra.request_certificate( csr_pem, profile_id, ca_id, request_type=request_type) except errors.HTTPRequestError as e: if e.status == 409: # pylint: disable=no-member raise errors.CertificateOperationError( error=_("CA '%s' is disabled") % ca) else: raise e if not raw: self.obj._parse(result, all) result['request_id'] = int(result['request_id']) result['cacn'] = ca_obj['cn'][0] # Success? Then add it to the principal's entry # (unless the profile tells us not to) profile = api.Command['certprofile_show'](profile_id) store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE' if store and 'certificate' in result: cert = str(result.get('certificate')) kwargs = dict(addattr=u'usercertificate={}'.format(cert)) if principal_type == SERVICE: api.Command['service_mod'](principal_string, **kwargs) elif principal_type == HOST: api.Command['host_mod'](principal.hostname, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal.username, **kwargs) return dict( result=result, value=pkey_to_value(int(result['request_id']), kw), )