def execute(self, serial_number, **options): ca_enabled_check() hostname = None try: self.check_access() except errors.ACIError as acierr: self.debug("Not granted by ACI to retrieve certificate, looking at principal") bind_principal = getattr(context, 'principal') if not bind_principal.startswith('host/'): raise acierr hostname = get_host_from_principal(bind_principal) result=self.Backend.ra.get_certificate(serial_number) cert = x509.load_certificate(result['certificate']) result['subject'] = unicode(cert.subject) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) if hostname: # If we have a hostname we want to verify that the subject # of the certificate matches it, otherwise raise an error if hostname != cert.subject.common_name: #pylint: disable=E1101 raise acierr return dict(result=result)
def set_certificate_attrs(entry_attrs): """ Set individual attributes from some values from a certificate. entry_attrs is a dict of an entry returns nothing """ if not 'usercertificate' in entry_attrs: return if type(entry_attrs['usercertificate']) in (list, tuple): cert = entry_attrs['usercertificate'][0] else: cert = entry_attrs['usercertificate'] cert = x509.normalize_certificate(cert) cert = x509.load_certificate(cert, datatype=x509.DER) entry_attrs['subject'] = unicode(cert.subject) entry_attrs['serial_number'] = unicode(cert.serial_number) entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number entry_attrs['issuer'] = unicode(cert.issuer) entry_attrs['valid_not_before'] = unicode(cert.valid_not_before_str) entry_attrs['valid_not_after'] = unicode(cert.valid_not_after_str) entry_attrs['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) entry_attrs['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
def execute(self, serial_number, **options): ca_enabled_check() hostname = None try: self.check_access() except errors.ACIError as acierr: self.debug( "Not granted by ACI to retrieve certificate, looking at principal" ) bind_principal = getattr(context, 'principal') if not bind_principal.startswith('host/'): raise acierr hostname = get_host_from_principal(bind_principal) result = self.Backend.ra.get_certificate(serial_number) cert = x509.load_certificate(result['certificate']) result['subject'] = unicode(cert.subject) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) if hostname: # If we have a hostname we want to verify that the subject # of the certificate matches it, otherwise raise an error if hostname != cert.subject.common_name: #pylint: disable=E1101 raise acierr return dict(result=result)
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. """ cert = obj.get('certificate') if cert is not None: cert = x509.load_certificate(cert) obj['subject'] = DN(unicode(cert.subject)) obj['issuer'] = DN(unicode(cert.issuer)) obj['serial_number'] = cert.serial_number if full: obj['valid_not_before'] = unicode(cert.valid_not_before_str) obj['valid_not_after'] = unicode(cert.valid_not_after_str) obj['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) obj['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) try: ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME) general_names = x509.decode_generalnames(ext_san.value) except KeyError: general_names = [] for name_type, desc, name, der_name in general_names: try: self._add_san_attribute( obj, full, name_type, name, der_name) except Exception as e: # 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 _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. """ cert = obj.get('certificate') if cert is not None: cert = x509.load_certificate(cert) obj['subject'] = DN(unicode(cert.subject)) obj['issuer'] = DN(unicode(cert.issuer)) obj['serial_number'] = cert.serial_number if full: obj['valid_not_before'] = unicode(cert.valid_not_before_str) obj['valid_not_after'] = unicode(cert.valid_not_after_str) obj['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) obj['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) try: ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME) general_names = x509.decode_generalnames(ext_san.value) except KeyError: general_names = [] for name_type, _desc, name, der_name in general_names: try: self._add_san_attribute(obj, full, name_type, name, der_name) 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 set_certificate_attrs(entry_attrs): """ Set individual attributes from some values from a certificate. entry_attrs is a dict of an entry returns nothing """ if not 'usercertificate' in entry_attrs: return if type(entry_attrs['usercertificate']) in (list, tuple): cert = entry_attrs['usercertificate'][0] else: cert = entry_attrs['usercertificate'] cert = x509.normalize_certificate(cert) cert = x509.load_certificate(cert, datatype=x509.DER) entry_attrs['subject'] = unicode(cert.subject) entry_attrs['serial_number'] = unicode(cert.serial_number) entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number entry_attrs['issuer'] = unicode(cert.issuer) entry_attrs['valid_not_before'] = unicode(cert.valid_not_before_str) entry_attrs['valid_not_after'] = unicode(cert.valid_not_after_str) entry_attrs['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) entry_attrs['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
def execute(self, csr, **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) ca = '.' # top-level CA hardcoded until subca plugin implemented """ 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_string = kw.get('principal') principal = split_any_principal(principal_string) servicename, principal_name, realm = principal if servicename is None: principal_type = USER elif servicename == 'host': principal_type = HOST else: principal_type = SERVICE bind_principal = split_any_principal(getattr(context, 'principal')) bind_service, bind_name, bind_realm = bind_principal if bind_service is None: bind_principal_type = USER elif bind_service == 'host': bind_principal_type = HOST else: bind_principal_type = SERVICE if bind_principal != principal 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_string, ca, profile_id) try: subject = pkcs10.get_subject(csr) extensions = pkcs10.get_extensions(csr) subjectaltname = pkcs10.get_subjectaltname(csr) or () except (NSPRError, PyAsn1Error) as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) # self-service and host principals may bypass SAN permission check if bind_principal != principal and bind_principal_type != HOST: if '2.5.29.17' in extensions: 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_name, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal_name, all=True) except errors.NotFound as e: if principal_type == SERVICE and add: principal_obj = api.Command['service_add'](principal_string, force=True) 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 cn = subject.common_name #pylint: disable=E1101 if not cn: raise errors.ValidationError(name='csr', error=_("No Common Name was found in subject of request.")) if principal_type in (SERVICE, HOST): if cn.lower() != principal_name.lower(): raise errors.ACIError( info=_("hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal_name)) elif principal_type == USER: # check user name if cn != principal_name: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login") ) # check email address mail = subject.email_address #pylint: disable=E1101 if mail is not None and mail not in principal_obj.get('mail', []): 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 for name_type, name in subjectaltname: if name_type == pkcs10.SAN_DNSNAME: name = unicode(name) alt_principal_obj = None alt_principal_string = None try: if principal_type == HOST: alt_principal_string = 'host/%s@%s' % (name, realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal_string = '%s/%s@%s' % (servicename, name, realm) alt_principal_obj = api.Command['service_show']( alt_principal_string, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % name_type ) 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_string is not None and not bypass_caacl: caacl_check( principal_type, alt_principal_string, ca, profile_id) elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME, pkcs10.SAN_OTHERNAME_UPN): if split_any_principal(name) != principal: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % name) elif name_type == pkcs10.SAN_RFC822NAME: if principal_type == USER: if name 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") % name_type ) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % name_type) # Request the certificate result = self.Backend.ra.request_certificate( csr, profile_id, request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[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_name, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal_name, **kwargs) return dict( result=result )
(1, 'Data:')]) print nss.indented_format([(2, 'Version: %d (%#x)' % (cert.version+1, cert.version))]) print nss.indented_format([(2, 'Serial Number: %d (%#x)' % (cert.serial_number, cert.serial_number))]) print nss.indented_format([(2, 'Signature Algorithm:')]) print nss.indented_format(cert.signature_algorithm.format_lines(3)) print nss.indented_format([(2, 'Issuer: "%s"' % cert.issuer)]) print nss.indented_format([(2, 'Validity:'), (3, 'Not Before: %s' % cert.valid_not_before_str), (3, 'Not After: %s' % cert.valid_not_after_str)]) print nss.indented_format([(2, 'Subject: "%s"' % cert.subject)]) print nss.indented_format([(2, 'Subject Public Key Info:')]) print nss.indented_format(cert.subject_public_key_info.format_lines(3)) if len(extensions) > 0: print nss.indented_format([(1, 'Signed Extensions: (%d)' % len(extensions))]) for extension in extensions: print_extension(2, extension) print nss.indented_format(cert.signed_data.format_lines(1)) print nss.indented_format([(1, 'Fingerprint (MD5):')]) print nss.indented_format(nss.make_line_fmt_tuples(2, nss.data_to_hex(nss.md5_digest(cert.der_data), nss.OCTETS_PER_LINE_DEFAULT))) print nss.indented_format([(1, 'Fingerprint (SHA1):')]) print nss.indented_format(nss.make_line_fmt_tuples(2, nss.data_to_hex(nss.sha1_digest(cert.der_data), nss.OCTETS_PER_LINE_DEFAULT)))
# some CA's might not implement get pass if not principal.startswith('host/'): api.Command['service_mod'](principal, usercertificate=None) else: hostname = get_host_from_principal(principal) api.Command['host_mod'](hostname, usercertificate=None) # Request the certificate result = self.Backend.ra.request_certificate( csr, request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) # Success? Then add it to the service entry. if 'certificate' in result: if not principal.startswith('host/'): skw = {"usercertificate": str(result.get('certificate'))} api.Command['service_mod'](principal, **skw) else: hostname = get_host_from_principal(principal) skw = {"usercertificate": str(result.get('certificate'))} api.Command['host_mod'](hostname, **skw) return dict( result=result )
def execute(self, csr, **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) ca = '.' # top-level CA hardcoded until subca plugin implemented """ 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_string = kw.get('principal') principal = split_any_principal(principal_string) servicename, principal_name, realm = principal if servicename is None: principal_type = USER elif servicename == 'host': principal_type = HOST else: principal_type = SERVICE bind_principal = split_any_principal(getattr(context, 'principal')) bind_service, bind_name, bind_realm = bind_principal if bind_service is None: bind_principal_type = USER elif bind_service == 'host': bind_principal_type = HOST else: bind_principal_type = SERVICE if bind_principal != principal 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_string, ca, profile_id) try: subject = pkcs10.get_subject(csr) extensions = pkcs10.get_extensions(csr) subjectaltname = pkcs10.get_subjectaltname(csr) or () except (NSPRError, PyAsn1Error, ValueError) as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) # self-service and host principals may bypass SAN permission check if bind_principal != principal and bind_principal_type != HOST: if '2.5.29.17' in extensions: 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_name, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal_name, all=True) except errors.NotFound as e: if principal_type == SERVICE and add: principal_obj = api.Command['service_add'](principal_string, force=True) 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 cn = subject.common_name #pylint: disable=E1101 if not cn: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) if principal_type in (SERVICE, HOST): if cn.lower() != principal_name.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal_name)) elif principal_type == USER: # check user name if cn != principal_name: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address mail = subject.email_address #pylint: disable=E1101 if mail is not None and mail not in principal_obj.get('mail', []): 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 for name_type, name in subjectaltname: if name_type == pkcs10.SAN_DNSNAME: name = unicode(name) alt_principal_obj = None alt_principal_string = None try: if principal_type == HOST: alt_principal_string = 'host/%s@%s' % (name, realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal_string = '%s/%s@%s' % (servicename, name, realm) alt_principal_obj = api.Command['service_show']( alt_principal_string, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % name_type) 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_string is not None and not bypass_caacl: caacl_check(principal_type, alt_principal_string, ca, profile_id) elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME, pkcs10.SAN_OTHERNAME_UPN): if split_any_principal(name) != principal: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % name) elif name_type == pkcs10.SAN_RFC822NAME: if principal_type == USER: if name 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") % name_type) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % name_type) # Request the certificate result = self.Backend.ra.request_certificate(csr, profile_id, request_type=request_type) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[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_name, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal_name, **kwargs) return dict(result=result)
(3, 'Not Before: %s' % cert.valid_not_before_str), (3, 'Not After: %s' % cert.valid_not_after_str)])) print(nss.indented_format([(2, 'Subject: "%s"' % cert.subject)])) print(nss.indented_format([(2, 'Subject Public Key Info:')])) print(nss.indented_format(cert.subject_public_key_info.format_lines(3))) if len(extensions) > 0: print( nss.indented_format([(1, 'Signed Extensions: (%d)' % len(extensions)) ])) for extension in extensions: print_extension(2, extension) print(nss.indented_format(cert.signed_data.format_lines(1))) print(nss.indented_format([(1, 'Fingerprint (MD5):')])) print( nss.indented_format( nss.make_line_fmt_tuples( 2, nss.data_to_hex(nss.md5_digest(cert.der_data), nss.OCTETS_PER_LINE_DEFAULT)))) print(nss.indented_format([(1, 'Fingerprint (SHA1):')])) print( nss.indented_format( nss.make_line_fmt_tuples( 2, nss.data_to_hex(nss.sha1_digest(cert.der_data), nss.OCTETS_PER_LINE_DEFAULT))))
class cert_show(VirtualCommand): __doc__ = _('Retrieve an existing certificate.') takes_args = _serial_number has_output_params = ( Str( 'certificate', label=_('Certificate'), ), Str( 'subject', label=_('Subject'), ), Str( 'issuer', label=_('Issuer'), ), Str( 'valid_not_before', label=_('Not Before'), ), Str( 'valid_not_after', label=_('Not After'), ), Str( 'md5_fingerprint', label=_('Fingerprint (MD5)'), ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), ), Str( 'revocation_reason', label=_('Revocation reason'), ), Str( 'serial_number_hex', label=_('Serial number (hex)'), ), ) takes_options = (Str( 'out?', label=_('Output filename'), doc=_('File to store the certificate in.'), exclude='webui', ), ) operation = "retrieve certificate" def execute(self, serial_number, **options): hostname = None try: self.check_access() except errors.ACIError, acierr: self.debug( "Not granted by ACI to retrieve certificate, looking at principal" ) bind_principal = getattr(context, 'principal') if not bind_principal.startswith('host/'): raise acierr hostname = get_host_from_principal(bind_principal) result = self.Backend.ra.get_certificate(serial_number) cert = x509.load_certificate(result['certificate']) result['subject'] = unicode(cert.subject) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) if hostname: # If we have a hostname we want to verify that the subject # of the certificate matches it, otherwise raise an error if hostname != cert.subject.common_name: #pylint: disable=E1101 raise acierr return dict(result=result)
class cert_request(VirtualCommand): __doc__ = _('Submit a certificate signing request.') takes_args = (File( 'csr', validate_csr, label=_('CSR'), cli_name='csr_file', normalizer=normalize_csr, ), ) operation = "request certificate" takes_options = ( Str( 'principal', label=_('Principal'), doc= _('Service principal for this certificate (e.g. HTTP/test.example.com)' ), ), Str( 'request_type', default=u'pkcs10', autofill=True, ), Flag('add', doc=_("automatically add the principal if it doesn't exist"), default=False, autofill=True), ) has_output_params = ( Str( 'certificate', label=_('Certificate'), ), Str( 'subject', label=_('Subject'), ), Str( 'issuer', label=_('Issuer'), ), Str( 'valid_not_before', label=_('Not Before'), ), Str( 'valid_not_after', label=_('Not After'), ), Str( 'md5_fingerprint', label=_('Fingerprint (MD5)'), ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), ), Str( 'serial_number', label=_('Serial number'), ), Str( 'serial_number_hex', label=_('Serial number (hex)'), ), ) has_output = (Output( 'result', type=dict, doc=_('Dictionary mapping variable name to value'), ), ) def execute(self, csr, **kw): ldap = self.api.Backend.ldap2 principal = kw.get('principal') add = kw.get('add') del kw['principal'] del kw['add'] service = None """ 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). """ bind_principal = getattr(context, 'principal') # Can this user request certs? if not bind_principal.startswith('host/'): self.check_access() # FIXME: add support for subject alt name # Ensure that the hostname in the CSR matches the principal subject_host = get_csr_hostname(csr) (servicename, hostname, realm) = split_principal(principal) if subject_host.lower() != hostname.lower(): raise errors.ACIError( info=_("hostname in subject of request '%(subject_host)s' " "does not match principal hostname '%(hostname)s'") % dict(subject_host=subject_host, hostname=hostname)) dn = None service = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if not principal.startswith('host/'): service = api.Command['service_show'](principal, all=True, raw=True)['result'] dn = service['dn'] else: hostname = get_host_from_principal(principal) service = api.Command['host_show'](hostname, all=True, raw=True)['result'] dn = service['dn'] except errors.NotFound, e: if not add: raise errors.NotFound(reason=_("The service principal for " "this request doesn't exist.")) try: service = api.Command['service_add'](principal, **{ 'force': True })['result'] dn = service['dn'] except errors.ACIError: raise errors.ACIError( info=_('You need to be a member of ' 'the serviceadmin role to add services')) # We got this far so the service 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 request = pkcs10.load_certificate_request(csr) subjectaltname = pkcs10.get_subjectaltname(request) if subjectaltname is not None: for name in subjectaltname: name = unicode(name) try: hostentry = api.Command['host_show'](name, all=True, raw=True)['result'] hostdn = hostentry['dn'] 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=_( 'no host record for ' 'subject alt name %s in certificate request') % name) authprincipal = getattr(context, 'principal') if authprincipal.startswith("host/"): if not hostdn in service.get('managedby', []): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if 'usercertificate' in service: serial = x509.get_serial_number(service['usercertificate'][0], datatype=x509.DER) # revoke the certificate and remove it from the service # entry before proceeding. First we retrieve the certificate to # see if it is already revoked, if not then we revoke it. try: result = api.Command['cert_show'](unicode(serial))['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](unicode(serial), revocation_reason=4) except errors.NotImplementedError: # some CA's might not implement revoke pass except errors.NotImplementedError: # some CA's might not implement get pass if not principal.startswith('host/'): api.Command['service_mod'](principal, usercertificate=None) else: hostname = get_host_from_principal(principal) api.Command['host_mod'](hostname, usercertificate=None) # Request the certificate result = self.Backend.ra.request_certificate(csr, **kw) cert = x509.load_certificate(result['certificate']) result['issuer'] = unicode(cert.issuer) result['valid_not_before'] = unicode(cert.valid_not_before_str) result['valid_not_after'] = unicode(cert.valid_not_after_str) result['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) result['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) # Success? Then add it to the service entry. if 'certificate' in result: if not principal.startswith('host/'): skw = {"usercertificate": str(result.get('certificate'))} api.Command['service_mod'](principal, **skw) else: hostname = get_host_from_principal(principal) skw = {"usercertificate": str(result.get('certificate'))} api.Command['host_mod'](hostname, **skw) return dict(result=result)