Beispiel #1
0
 def _check_nameConstraints(extensions):
     current_nc_ext = _find_extension(extensions,
                                      cryptography.x509.NameConstraints)
     current_nc_perm = [
         str(altname)
         for altname in current_nc_ext.value.permitted_subtrees
     ] if current_nc_ext else []
     current_nc_excl = [
         str(altname)
         for altname in current_nc_ext.value.excluded_subtrees
     ] if current_nc_ext else []
     nc_perm = [
         str(
             cryptography_get_name(altname,
                                   'name constraints permitted'))
         for altname in self.name_constraints_permitted
     ]
     nc_excl = [
         str(cryptography_get_name(altname,
                                   'name constraints excluded'))
         for altname in self.name_constraints_excluded
     ]
     if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(
             current_nc_excl):
         return False
     if nc_perm or nc_excl:
         if current_nc_ext.critical != self.name_constraints_critical:
             return False
     return True
Beispiel #2
0
def parse_crl_distribution_points(module, crl_distribution_points):
    result = []
    for index, parse_crl_distribution_point in enumerate(
            crl_distribution_points):
        try:
            params = dict(
                full_name=None,
                relative_name=None,
                crl_issuer=None,
                reasons=None,
            )
            if parse_crl_distribution_point['full_name'] is not None:
                params['full_name'] = [
                    cryptography_get_name(name, 'full name')
                    for name in parse_crl_distribution_point['full_name']
                ]
            if parse_crl_distribution_point['relative_name'] is not None:
                try:
                    params[
                        'relative_name'] = cryptography_parse_relative_distinguished_name(
                            parse_crl_distribution_point['relative_name'])
                except Exception:
                    # If cryptography's version is < 1.6, the error is probably caused by that
                    if CRYPTOGRAPHY_VERSION < LooseVersion('1.6'):
                        raise OpenSSLObjectError(
                            'Cannot specify relative_name for cryptography < 1.6'
                        )
                    raise
            if parse_crl_distribution_point['crl_issuer'] is not None:
                params['crl_issuer'] = [
                    cryptography_get_name(name, 'CRL issuer')
                    for name in parse_crl_distribution_point['crl_issuer']
                ]
            if parse_crl_distribution_point['reasons'] is not None:
                reasons = []
                for reason in parse_crl_distribution_point['reasons']:
                    reasons.append(REVOCATION_REASON_MAP[reason])
                params['reasons'] = frozenset(reasons)
            result.append(cryptography.x509.DistributionPoint(**params))
        except OpenSSLObjectError as e:
            raise OpenSSLObjectError(
                'Error while parsing CRL distribution point #{index}: {error}'.
                format(index=index, error=e))
    return result
Beispiel #3
0
 def _check_subjectAltName(extensions):
     current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName)
     current_altnames = [str(altname) for altname in current_altnames_ext.value] if current_altnames_ext else []
     altnames = [str(cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
     if set(altnames) != set(current_altnames):
         return False
     if altnames:
         if current_altnames_ext.critical != self.subjectAltName_critical:
             return False
     return True
Beispiel #4
0
    def _generate_crl(self):
        backend = default_backend()
        crl = CertificateRevocationListBuilder()

        try:
            crl = crl.issuer_name(
                Name([
                    NameAttribute(cryptography_name_to_oid(entry[0]),
                                  to_text(entry[1])) for entry in self.issuer
                ]))
        except ValueError as e:
            raise CRLError(e)

        crl = crl.last_update(self.last_update)
        crl = crl.next_update(self.next_update)

        if self.update and self.crl:
            new_entries = set([
                self._compress_entry(entry)
                for entry in self.revoked_certificates
            ])
            for entry in self.crl:
                decoded_entry = self._compress_entry(
                    cryptography_decode_revoked_certificate(entry))
                if decoded_entry not in new_entries:
                    crl = crl.add_revoked_certificate(entry)
        for entry in self.revoked_certificates:
            revoked_cert = RevokedCertificateBuilder()
            revoked_cert = revoked_cert.serial_number(entry['serial_number'])
            revoked_cert = revoked_cert.revocation_date(
                entry['revocation_date'])
            if entry['issuer'] is not None:
                revoked_cert = revoked_cert.add_extension(
                    x509.CertificateIssuer([
                        cryptography_get_name(name, 'issuer')
                        for name in entry['issuer']
                    ]), entry['issuer_critical'])
            if entry['reason'] is not None:
                revoked_cert = revoked_cert.add_extension(
                    x509.CRLReason(entry['reason']), entry['reason_critical'])
            if entry['invalidity_date'] is not None:
                revoked_cert = revoked_cert.add_extension(
                    x509.InvalidityDate(entry['invalidity_date']),
                    entry['invalidity_date_critical'])
            crl = crl.add_revoked_certificate(revoked_cert.build(backend))

        self.crl = crl.sign(self.privatekey, self.digest, backend=backend)
        if self.format == 'pem':
            return self.crl.public_bytes(Encoding.PEM)
        else:
            return self.crl.public_bytes(Encoding.DER)
Beispiel #5
0
 def _validate_subject_alt_name(self):
     try:
         current_san = self.existing_certificate.extensions.get_extension_for_class(
             x509.SubjectAlternativeName).value
         expected_san = [
             cryptography_get_name(san) for san in self.subject_alt_name
         ]
         if not compare_sets(expected_san, current_san,
                             self.subject_alt_name_strict):
             return self.subject_alt_name, current_san
     except cryptography.x509.ExtensionNotFound:
         # This is only bad if the user specified a non-empty list
         if self.subject_alt_name:
             return NO_EXTENSION
Beispiel #6
0
 def _check_authority_key_identifier(extensions):
     ext = _find_extension(extensions, cryptography.x509.AuthorityKeyIdentifier)
     if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
         if not ext or ext.critical:
             return False
         aci = None
         csr_aci = None
         if self.authority_cert_issuer is not None:
             aci = [str(cryptography_get_name(n)) for n in self.authority_cert_issuer]
         if ext.value.authority_cert_issuer is not None:
             csr_aci = [str(n) for n in ext.value.authority_cert_issuer]
         return (ext.value.key_identifier == self.authority_key_identifier
                 and csr_aci == aci
                 and ext.value.authority_cert_serial_number == self.authority_cert_serial_number)
     else:
         return ext is None
Beispiel #7
0
    def __init__(self, module):
        super(CRL, self).__init__(
            module.params['path'],
            module.params['state'],
            module.params['force'],
            module.check_mode
        )

        self.format = module.params['format']

        self.update = module.params['mode'] == 'update'
        self.ignore_timestamps = module.params['ignore_timestamps']
        self.return_content = module.params['return_content']
        self.crl_content = None

        self.privatekey_path = module.params['privatekey_path']
        self.privatekey_content = module.params['privatekey_content']
        if self.privatekey_content is not None:
            self.privatekey_content = self.privatekey_content.encode('utf-8')
        self.privatekey_passphrase = module.params['privatekey_passphrase']

        self.issuer = parse_name_field(module.params['issuer'])
        self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]]

        self.last_update = get_relative_time_option(module.params['last_update'], 'last_update')
        self.next_update = get_relative_time_option(module.params['next_update'], 'next_update')

        self.digest = select_message_digest(module.params['digest'])
        if self.digest is None:
            raise CRLError('The digest "{0}" is not supported'.format(module.params['digest']))

        self.revoked_certificates = []
        for i, rc in enumerate(module.params['revoked_certificates']):
            result = {
                'serial_number': None,
                'revocation_date': None,
                'issuer': None,
                'issuer_critical': False,
                'reason': None,
                'reason_critical': False,
                'invalidity_date': None,
                'invalidity_date_critical': False,
            }
            path_prefix = 'revoked_certificates[{0}].'.format(i)
            if rc['path'] is not None or rc['content'] is not None:
                # Load certificate from file or content
                try:
                    if rc['content'] is not None:
                        rc['content'] = rc['content'].encode('utf-8')
                    cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography')
                    result['serial_number'] = cryptography_serial_number_of_cert(cert)
                except OpenSSLObjectError as e:
                    if rc['content'] is not None:
                        module.fail_json(
                            msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e))
                        )
                    else:
                        module.fail_json(
                            msg='Cannot read certificate "{1}" from {0}path: {2}'.format(path_prefix, rc['path'], to_native(e))
                        )
            else:
                # Specify serial_number (and potentially issuer) directly
                result['serial_number'] = rc['serial_number']
            # All other options
            if rc['issuer']:
                result['issuer'] = [cryptography_get_name(issuer) for issuer in rc['issuer']]
                result['issuer_critical'] = rc['issuer_critical']
            result['revocation_date'] = get_relative_time_option(
                rc['revocation_date'],
                path_prefix + 'revocation_date'
            )
            if rc['reason']:
                result['reason'] = REVOCATION_REASON_MAP[rc['reason']]
                result['reason_critical'] = rc['reason_critical']
            if rc['invalidity_date']:
                result['invalidity_date'] = get_relative_time_option(
                    rc['invalidity_date'],
                    path_prefix + 'invalidity_date'
                )
                result['invalidity_date_critical'] = rc['invalidity_date_critical']
            self.revoked_certificates.append(result)

        self.module = module

        self.backup = module.params['backup']
        self.backup_file = None

        try:
            self.privatekey = load_privatekey(
                path=self.privatekey_path,
                content=self.privatekey_content,
                passphrase=self.privatekey_passphrase,
                backend='cryptography'
            )
        except OpenSSLBadPassphraseError as exc:
            raise CRLError(exc)

        self.crl = None
        try:
            with open(self.path, 'rb') as f:
                data = f.read()
            self.actual_format = 'pem' if identify_pem_format(data) else 'der'
            if self.actual_format == 'pem':
                self.crl = x509.load_pem_x509_crl(data, default_backend())
                if self.return_content:
                    self.crl_content = data
            else:
                self.crl = x509.load_der_x509_crl(data, default_backend())
                if self.return_content:
                    self.crl_content = base64.b64encode(data)
        except Exception as dummy:
            self.crl_content = None
            self.actual_format = self.format
Beispiel #8
0
    def generate_csr(self):
        """(Re-)Generate CSR."""
        self._ensure_private_key_loaded()

        csr = cryptography.x509.CertificateSigningRequestBuilder()
        try:
            csr = csr.subject_name(
                cryptography.x509.Name([
                    cryptography.x509.NameAttribute(
                        cryptography_name_to_oid(entry[0]), to_text(entry[1]))
                    for entry in self.subject
                ]))
        except ValueError as e:
            raise CertificateSigningRequestError(e)

        if self.subjectAltName:
            csr = csr.add_extension(cryptography.x509.SubjectAlternativeName(
                [cryptography_get_name(name) for name in self.subjectAltName]),
                                    critical=self.subjectAltName_critical)

        if self.keyUsage:
            params = cryptography_parse_key_usage_params(self.keyUsage)
            csr = csr.add_extension(cryptography.x509.KeyUsage(**params),
                                    critical=self.keyUsage_critical)

        if self.extendedKeyUsage:
            usages = [
                cryptography_name_to_oid(usage)
                for usage in self.extendedKeyUsage
            ]
            csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages),
                                    critical=self.extendedKeyUsage_critical)

        if self.basicConstraints:
            params = {}
            ca, path_length = cryptography_get_basic_constraints(
                self.basicConstraints)
            csr = csr.add_extension(cryptography.x509.BasicConstraints(
                ca, path_length),
                                    critical=self.basicConstraints_critical)

        if self.ocspMustStaple:
            try:
                # This only works with cryptography >= 2.1
                csr = csr.add_extension(cryptography.x509.TLSFeature(
                    [cryptography.x509.TLSFeatureType.status_request]),
                                        critical=self.ocspMustStaple_critical)
            except AttributeError as dummy:
                csr = csr.add_extension(
                    cryptography.x509.UnrecognizedExtension(
                        CRYPTOGRAPHY_MUST_STAPLE_NAME,
                        CRYPTOGRAPHY_MUST_STAPLE_VALUE),
                    critical=self.ocspMustStaple_critical)

        if self.name_constraints_permitted or self.name_constraints_excluded:
            try:
                csr = csr.add_extension(
                    cryptography.x509.NameConstraints(
                        [
                            cryptography_get_name(
                                name, 'name constraints permitted')
                            for name in self.name_constraints_permitted
                        ],
                        [
                            cryptography_get_name(name,
                                                  'name constraints excluded')
                            for name in self.name_constraints_excluded
                        ],
                    ),
                    critical=self.name_constraints_critical)
            except TypeError as e:
                raise OpenSSLObjectError(
                    'Error while parsing name constraint: {0}'.format(e))

        if self.create_subject_key_identifier:
            csr = csr.add_extension(
                cryptography.x509.SubjectKeyIdentifier.from_public_key(
                    self.privatekey.public_key()),
                critical=False)
        elif self.subject_key_identifier is not None:
            csr = csr.add_extension(cryptography.x509.SubjectKeyIdentifier(
                self.subject_key_identifier),
                                    critical=False)

        if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
            issuers = None
            if self.authority_cert_issuer is not None:
                issuers = [
                    cryptography_get_name(n, 'authority cert issuer')
                    for n in self.authority_cert_issuer
                ]
            csr = csr.add_extension(cryptography.x509.AuthorityKeyIdentifier(
                self.authority_key_identifier, issuers,
                self.authority_cert_serial_number),
                                    critical=False)

        if self.crl_distribution_points:
            csr = csr.add_extension(cryptography.x509.CRLDistributionPoints(
                self.crl_distribution_points),
                                    critical=False)

        digest = None
        if cryptography_key_needs_digest_for_signing(self.privatekey):
            digest = select_message_digest(self.digest)
            if digest is None:
                raise CertificateSigningRequestError(
                    'Unsupported digest "{0}"'.format(self.digest))
        try:
            self.csr = csr.sign(self.privatekey, digest,
                                self.cryptography_backend)
        except TypeError as e:
            if str(
                    e
            ) == 'Algorithm must be a registered hash algorithm.' and digest is None:
                self.module.fail_json(
                    msg=
                    'Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.'
                )
            raise
        except UnicodeError as e:
            # This catches IDNAErrors, which happens when a bad name is passed as a SAN
            # (https://github.com/ansible-collections/community.crypto/issues/105).
            # For older cryptography versions, this is handled by idna, which raises
            # an idna.core.IDNAError. Later versions of cryptography deprecated and stopped
            # requiring idna, whence we cannot easily handle this error. Fortunately, in
            # most versions of idna, IDNAError extends UnicodeError. There is only version
            # 2.3 where it extends Exception instead (see
            # https://github.com/kjd/idna/commit/ebefacd3134d0f5da4745878620a6a1cba86d130
            # and then
            # https://github.com/kjd/idna/commit/ea03c7b5db7d2a99af082e0239da2b68aeea702a).
            msg = 'Error while creating CSR: {0}\n'.format(e)
            if self.using_common_name_for_san:
                self.module.fail_json(
                    msg=msg +
                    'This is probably caused because the Common Name is used as a SAN.'
                    ' Specifying use_common_name_for_san=false might fix this.'
                )
            self.module.fail_json(
                msg=msg +
                'This is probably caused by an invalid Subject Alternative DNS Name.'
            )
def test_cryptography_get_name_other_name_utfstring():
    actual = cryptography_get_name(
        'otherName:1.3.6.1.4.1.311.20.2.3;UTF8:Hello World')
    assert actual.type_id.dotted_string == '1.3.6.1.4.1.311.20.2.3'
    assert actual.value == b'\x0c\x0bHello World'
def test_cryptography_get_name_other_name_no_oid():
    with pytest.raises(
            OpenSSLObjectError,
            match="Cannot parse Subject Alternative Name otherName"):
        cryptography_get_name('otherName:value')
def test_cryptography_get_name_invalid_prefix():
    with pytest.raises(OpenSSLObjectError,
                       match="Cannot parse Subject Alternative Name"):
        cryptography_get_name('fake:value')