def execute(self, serial_number, **kw): ca_enabled_check() # Make sure that the cert specified by issuer+serial exists. # Will raise NotFound if it does not. resp = api.Command.cert_show(unicode(serial_number), cacn=kw['cacn']) try: self.check_access() except errors.ACIError as acierr: self.debug( "Not granted by ACI to revoke certificate, looking at principal" ) try: cert = x509.load_certificate(resp['result']['certificate']) if not bind_principal_can_manage_cert(cert): raise acierr except errors.NotImplementedError: raise acierr revocation_reason = kw['revocation_reason'] if revocation_reason == 7: raise errors.CertificateOperationError( error=_('7 is not a valid revocation reason')) return dict( # Dogtag lightweight CAs have shared serial number domain, so # we don't tell Dogtag the issuer (but we already checked that # the given serial was issued by the named ca). result=self.Backend.ra.revoke_certificate( str(serial_number), revocation_reason=revocation_reason))
def verify_ca_cert_validity(self, nickname): cert = self.get_cert(nickname) cert = x509.load_certificate(cert, x509.DER) if not cert.subject: raise ValueError("has empty subject") try: bc = cert.extensions.get_extension_for_class( cryptography.x509.BasicConstraints) except cryptography.x509.ExtensionNotFound: raise ValueError("missing basic constraints") if not bc.value.ca: raise ValueError("not a CA certificate") try: cert.extensions.get_extension_for_class( cryptography.x509.SubjectKeyIdentifier) except cryptography.x509.ExtensionNotFound: raise ValueError("missing subject key identifier extension") try: self.run_certutil(['-V', '-n', nickname, '-u', 'L'], capture_output=True) except ipautil.CalledProcessError as e: # certutil output in case of error is # 'certutil: certificate is invalid: <ERROR_STRING>\n' raise ValueError(e.output)
def _get_cert_key(self, cert): try: cert_obj = x509.load_certificate(cert, x509.DER) except ValueError as e: message = messages.SearchResultTruncated( reason=_("failed to load certificate: %s") % e, ) self.add_message(message) raise return (DN(cert_obj.issuer), cert_obj.serial)
def execute(self, serial_number, all=False, raw=False, no_members=False, **options): ca_enabled_check() # Dogtag lightweight CAs have shared serial number domain, so # we don't tell Dogtag the issuer (but we check the cert after). # result = self.Backend.ra.get_certificate(str(serial_number)) cert = x509.load_certificate(result['certificate']) try: self.check_access() except errors.ACIError as acierr: self.debug( "Not granted by ACI to retrieve certificate, looking at principal" ) if not bind_principal_can_manage_cert(cert): raise acierr # pylint: disable=E0702 ca_obj = api.Command.ca_show(options['cacn'])['result'] if DN(cert.issuer) != DN(ca_obj['ipacasubjectdn'][0]): # DN of cert differs from what we requested raise errors.NotFound( reason=_("Certificate with serial number %(serial)s " "issued by CA '%(ca)s' not found") % dict(serial=serial_number, ca=options['cacn'])) if all or not no_members: ldap = self.api.Backend.ldap2 filter = ldap.make_filter_from_attr( 'usercertificate', base64.b64decode(result['certificate'])) try: entries = ldap.get_entries(base_dn=self.api.env.basedn, filter=filter, attrs_list=['']) except errors.EmptyResult: entries = [] for entry in entries: result.setdefault('owner', []).append(entry.dn) if not raw: result['certificate'] = result['certificate'].replace('\r\n', '') self.obj._parse(result, all) result['revoked'] = ('revocation_reason' in result) self.obj._fill_owners(result) result['cacn'] = ca_obj['cn'][0] return dict(result=result, value=pkey_to_value(serial_number, options))
def verify_server_cert_validity(self, nickname, hostname): """Verify a certificate is valid for a SSL server with given hostname Raises a ValueError if the certificate is invalid. """ cert = self.get_cert(nickname) cert = x509.load_certificate(cert, x509.DER) try: self.run_certutil(['-V', '-n', nickname, '-u', 'V']) except ipautil.CalledProcessError: raise ValueError('invalid for a SSL server') try: x509.match_hostname(cert, hostname) except ValueError: raise ValueError('invalid for server %s' % hostname)
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 verify_ca_cert_validity(self, nickname): cert = self.get_cert(nickname) cert = x509.load_certificate(cert, x509.DER) if not cert.subject: raise ValueError("has empty subject") try: bc = cert.extensions.get_extension_for_class( cryptography.x509.BasicConstraints) except cryptography.x509.ExtensionNotFound: raise ValueError("missing basic constraints") if not bc.value.ca: raise ValueError("not a CA certificate") try: self.run_certutil(['-V', '-n', nickname, '-u', 'L']) except ipautil.CalledProcessError: raise ValueError('invalid for a CA')
def verify_server_cert_validity(self, nickname, hostname): """Verify a certificate is valid for a SSL server with given hostname Raises a ValueError if the certificate is invalid. """ cert = self.get_cert(nickname) cert = x509.load_certificate(cert, x509.DER) try: self.run_certutil(['-V', '-n', nickname, '-u', 'V'], capture_output=True) except ipautil.CalledProcessError as e: # certutil output in case of error is # 'certutil: certificate is invalid: <ERROR_STRING>\n' raise ValueError(e.output) try: x509.match_hostname(cert, hostname) except ValueError: raise ValueError('invalid for server %s' % hostname)
def import_files(self, files, import_keys=False, key_password=None, key_nickname=None): """ Import certificates and a single private key from multiple files The files may be in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. :param files: Names of files to import :param import_keys: Whether to import private keys :param key_password: Password to decrypt private keys :param key_nickname: Nickname of the private key to import from PKCS#12 files """ key_file = None extracted_key = None extracted_certs = [] for filename in files: try: with open(filename, 'rb') as f: data = f.read() except IOError as e: raise RuntimeError( "Failed to open %s: %s" % (filename, e.strerror)) # Try to parse the file as PEM file matches = list(re.finditer( r'-----BEGIN (.+?)-----(.*?)-----END \1-----', data, re.DOTALL)) if matches: loaded = False for match in matches: body = match.group() label = match.group(1) line = len(data[:match.start() + 1].splitlines()) if label in ('CERTIFICATE', 'X509 CERTIFICATE', 'X.509 CERTIFICATE'): try: x509.load_certificate(match.group(2)) except ValueError as e: if label != 'CERTIFICATE': root_logger.warning( "Skipping certificate in %s at line %s: %s", filename, line, e) continue else: extracted_certs.append(body) loaded = True continue if label in ('PKCS7', 'PKCS #7 SIGNED DATA', 'CERTIFICATE'): try: certs = x509.pkcs7_to_pems(body) except ipautil.CalledProcessError as e: if label == 'CERTIFICATE': root_logger.warning( "Skipping certificate in %s at line %s: %s", filename, line, e) else: root_logger.warning( "Skipping PKCS#7 in %s at line %s: %s", filename, line, e) continue else: extracted_certs.extend(certs) loaded = True continue if label in ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY', 'RSA PRIVATE KEY', 'DSA PRIVATE KEY', 'EC PRIVATE KEY'): if not import_keys: continue if key_file: raise RuntimeError( "Can't load private key from both %s and %s" % (key_file, filename)) args = [ OPENSSL, 'pkcs8', '-topk8', '-passout', 'file:' + self.pwd_file, ] if ((label != 'PRIVATE KEY' and key_password) or label == 'ENCRYPTED PRIVATE KEY'): key_pwdfile = ipautil.write_tmp_file(key_password) args += [ '-passin', 'file:' + key_pwdfile.name, ] try: result = ipautil.run( args, stdin=body, capture_output=True) except ipautil.CalledProcessError as e: root_logger.warning( "Skipping private key in %s at line %s: %s", filename, line, e) continue else: extracted_key = result.output key_file = filename loaded = True continue if loaded: continue raise RuntimeError("Failed to load %s" % filename) # Try to load the file as DER certificate try: x509.load_certificate(data, x509.DER) except ValueError: pass else: data = x509.make_pem(base64.b64encode(data)) extracted_certs.append(data) continue # Try to import the file as PKCS#12 file if import_keys: try: self.import_pkcs12(filename, key_password) except RuntimeError: pass else: if key_file: raise RuntimeError( "Can't load private key from both %s and %s" % (key_file, filename)) key_file = filename server_certs = self.find_server_certs() if key_nickname: for nickname, _trust_flags in server_certs: if nickname == key_nickname: break else: raise RuntimeError( "Server certificate \"%s\" not found in %s" % (key_nickname, filename)) else: if len(server_certs) > 1: raise RuntimeError( "%s server certificates found in %s, " "expecting only one" % (len(server_certs), filename)) continue raise RuntimeError("Failed to load %s" % filename) if import_keys and not key_file: raise RuntimeError( "No server certificates found in %s" % (', '.join(files))) for cert_pem in extracted_certs: cert = x509.load_certificate(cert_pem) nickname = str(DN(cert.subject)) data = cert.public_bytes(serialization.Encoding.DER) self.add_cert(data, nickname, ',,') if extracted_key: in_file = ipautil.write_tmp_file( '\n'.join(extracted_certs) + '\n' + extracted_key) out_file = tempfile.NamedTemporaryFile() out_password = ipautil.ipa_generate_password() out_pwdfile = ipautil.write_tmp_file(out_password) args = [ OPENSSL, 'pkcs12', '-export', '-in', in_file.name, '-out', out_file.name, '-passin', 'file:' + self.pwd_file, '-passout', 'file:' + out_pwdfile.name, ] try: ipautil.run(args) except ipautil.CalledProcessError as e: raise RuntimeError( "No matching certificate found for private key from %s" % key_file) self.import_pkcs12(out_file.name, out_password)
def verify_kdc_cert_validity(self, nickname, realm): nicknames = self.get_trust_chain(nickname) certs = [self.get_cert(nickname) for nickname in nicknames] certs = [x509.load_certificate(cert, x509.DER) for cert in certs] verify_kdc_cert_validity(certs[-1], certs[:-1], realm)
def import_files(self, files, import_keys=False, key_password=None, key_nickname=None): """ Import certificates and a single private key from multiple files The files may be in PEM and DER certificate, PKCS#7 certificate chain, PKCS#8 and raw private key and PKCS#12 formats. :param files: Names of files to import :param import_keys: Whether to import private keys :param key_password: Password to decrypt private keys :param key_nickname: Nickname of the private key to import from PKCS#12 files """ key_file = None extracted_key = None extracted_certs = [] for filename in files: try: with open(filename, 'rb') as f: data = f.read() except IOError as e: raise RuntimeError( "Failed to open %s: %s" % (filename, e.strerror)) # Try to parse the file as PEM file matches = list(re.finditer( r'-----BEGIN (.+?)-----(.*?)-----END \1-----', data, re.DOTALL)) if matches: loaded = False for match in matches: body = match.group() label = match.group(1) line = len(data[:match.start() + 1].splitlines()) if label in ('CERTIFICATE', 'X509 CERTIFICATE', 'X.509 CERTIFICATE'): try: x509.load_certificate(match.group(2)) except ValueError as e: if label != 'CERTIFICATE': logger.warning( "Skipping certificate in %s at line %s: " "%s", filename, line, e) continue else: extracted_certs.append(body) loaded = True continue if label in ('PKCS7', 'PKCS #7 SIGNED DATA', 'CERTIFICATE'): try: certs = x509.pkcs7_to_pems(body) except ipautil.CalledProcessError as e: if label == 'CERTIFICATE': logger.warning( "Skipping certificate in %s at line %s: " "%s", filename, line, e) else: logger.warning( "Skipping PKCS#7 in %s at line %s: %s", filename, line, e) continue else: extracted_certs.extend(certs) loaded = True continue if label in ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY', 'RSA PRIVATE KEY', 'DSA PRIVATE KEY', 'EC PRIVATE KEY'): if not import_keys: continue if key_file: raise RuntimeError( "Can't load private key from both %s and %s" % (key_file, filename)) args = [ OPENSSL, 'pkcs8', '-topk8', '-passout', 'file:' + self.pwd_file, ] if ((label != 'PRIVATE KEY' and key_password) or label == 'ENCRYPTED PRIVATE KEY'): key_pwdfile = ipautil.write_tmp_file(key_password) args += [ '-passin', 'file:' + key_pwdfile.name, ] try: result = ipautil.run( args, stdin=body, capture_output=True) except ipautil.CalledProcessError as e: logger.warning( "Skipping private key in %s at line %s: %s", filename, line, e) continue else: extracted_key = result.output key_file = filename loaded = True continue if loaded: continue raise RuntimeError("Failed to load %s" % filename) # Try to load the file as DER certificate try: x509.load_certificate(data, x509.DER) except ValueError: pass else: data = x509.make_pem(base64.b64encode(data)) extracted_certs.append(data) continue # Try to import the file as PKCS#12 file if import_keys: try: self.import_pkcs12(filename, key_password) except RuntimeError: pass else: if key_file: raise RuntimeError( "Can't load private key from both %s and %s" % (key_file, filename)) key_file = filename server_certs = self.find_server_certs() if key_nickname: for nickname, _trust_flags in server_certs: if nickname == key_nickname: break else: raise RuntimeError( "Server certificate \"%s\" not found in %s" % (key_nickname, filename)) else: if len(server_certs) > 1: raise RuntimeError( "%s server certificates found in %s, " "expecting only one" % (len(server_certs), filename)) continue raise RuntimeError("Failed to load %s" % filename) if import_keys and not key_file: raise RuntimeError( "No server certificates found in %s" % (', '.join(files))) for cert_pem in extracted_certs: cert = x509.load_certificate(cert_pem) nickname = str(DN(cert.subject)) data = cert.public_bytes(serialization.Encoding.DER) self.add_cert(data, nickname, EMPTY_TRUST_FLAGS) if extracted_key: in_file = ipautil.write_tmp_file( '\n'.join(extracted_certs) + '\n' + extracted_key) out_file = tempfile.NamedTemporaryFile() out_password = ipautil.ipa_generate_password() out_pwdfile = ipautil.write_tmp_file(out_password) args = [ OPENSSL, 'pkcs12', '-export', '-in', in_file.name, '-out', out_file.name, '-passin', 'file:' + self.pwd_file, '-passout', 'file:' + out_pwdfile.name, ] try: ipautil.run(args) except ipautil.CalledProcessError as e: raise RuntimeError( "No matching certificate found for private key from %s" % key_file) self.import_pkcs12(out_file.name, out_password)