Esempio n. 1
0
    def _deserialize(self, value, attr, data):
        general_names = []
        for name in value:
            if name['nameType'] == 'DNSName':
                general_names.append(x509.DNSName(name['value']))

            elif name['nameType'] == 'IPAddress':
                general_names.append(
                    x509.IPAddress(ipaddress.ip_address(name['value'])))

            elif name['nameType'] == 'IPNetwork':
                general_names.append(
                    x509.IPAddress(ipaddress.ip_network(name['value'])))

            elif name['nameType'] == 'uniformResourceIdentifier':
                general_names.append(
                    x509.UniformResourceIdentifier(name['value']))

            elif name['nameType'] == 'directoryName':
                # TODO: Need to parse a string in name['value'] like:
                # 'CN=Common Name, O=Org Name, OU=OrgUnit Name, C=US, ST=ST, L=City/[email protected]'
                # or
                # 'CN=Common Name/O=Org Name/OU=OrgUnit Name/C=US/ST=NH/L=City/[email protected]'
                # and turn it into something like:
                # x509.Name([
                #     x509.NameAttribute(x509.OID_COMMON_NAME, "Common Name"),
                #     x509.NameAttribute(x509.OID_ORGANIZATION_NAME, "Org Name"),
                #     x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, "OrgUnit Name"),
                #     x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"),
                #     x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, "NH"),
                #     x509.NameAttribute(x509.OID_LOCALITY_NAME, "City"),
                #     x509.NameAttribute(x509.OID_EMAIL_ADDRESS, "*****@*****.**")
                # ]
                # general_names.append(x509.DirectoryName(x509.Name(BLAH))))
                pass

            elif name['nameType'] == 'rfc822Name':
                general_names.append(x509.RFC822Name(name['value']))

            elif name['nameType'] == 'registeredID':
                general_names.append(
                    x509.RegisteredID(x509.ObjectIdentifier(name['value'])))

            elif name['nameType'] == 'otherName':
                # This has two inputs (type and value), so it doesn't fit the mold of the rest of these GeneralName entities.
                # general_names.append(x509.OtherName(name['type'], bytes(name['value']), 'utf-8'))
                pass

            elif name['nameType'] == 'x400Address':
                # The Python Cryptography library doesn't support x400Address types (yet?)
                pass

            elif name['nameType'] == 'EDIPartyName':
                # The Python Cryptography library doesn't support EDIPartyName types (yet?)
                pass

            else:
                current_app.logger.warning(
                    'Unable to deserialize SubAltName with type: {name_type}'.
                    format(name_type=name['nameType']))

        return x509.SubjectAlternativeName(general_names)
Esempio n. 2
0
    def update_cert(self, csr, lifetime):
        """Given a CSR, look it up in the database, update it and present the
        new certificate.

        Args:
            csr (cryptography.x509.CertificateSigningRequest): A CSR.
            lifetime (int): Lifetime in seconds.

        Raises:
            CertProcessorMismatchedPublicKeyError: The public key from the new
            CSR does not match the in database Certificate.

        Returns:
           cryptography.x509.Certificate: A Signed Certificate for a user.
        """
        common_name = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
        common_name = common_name[0].value
        bcert = bytes(str(self.storage.get_cert(common_name=common_name)[0]),
                      "UTF-8")
        old_cert = x509.load_pem_x509_certificate(bcert,
                                                  backend=default_backend())
        old_cert_pub = (old_cert.public_key().public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        ).decode("UTF-8"))
        csr_pub = (csr.public_key().public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        ).decode("UTF-8"))
        if old_cert_pub != csr_pub:
            raise CertProcessorMismatchedPublicKeyError
        ca_pkey = self.get_ca_key()
        ca_cert = self.get_ca_cert(ca_pkey)
        now = datetime.datetime.utcnow()
        lifetime_delta = now + datetime.timedelta(seconds=int(lifetime))
        alts = []
        for alt in self.config.get("ca", "alternate_name").split(","):
            alts.append(x509.DNSName("{}".format(alt)))

        cert = (x509.CertificateBuilder().subject_name(
            old_cert.subject).issuer_name(ca_cert.subject).public_key(
                csr.public_key()).serial_number(
                    old_cert.serial_number).not_valid_before(
                        old_cert.not_valid_before).not_valid_after(
                            lifetime_delta))
        if len(alts) > 0:
            cert = cert.add_extension(x509.SubjectAlternativeName(alts),
                                      critical=False)
        crl_dp = x509.DistributionPoint(
            [
                x509.UniformResourceIdentifier(
                    "{protocol}://{server_url}/crl".format(
                        protocol=self.PROTOCOL, server_url=self.SERVER_URL))
            ],
            relative_name=None,
            reasons=None,
            crl_issuer=None,
        )
        cert = cert.add_extension(x509.CRLDistributionPoints([crl_dp]),
                                  critical=False)

        cert = cert.sign(
            private_key=ca_pkey,
            algorithm=hashes.SHA256(),
            backend=default_backend(),
        )
        self.storage.update_cert(cert=cert, serial_number=cert.serial_number)
        return cert
Esempio n. 3
0
 def test_uri(self):
     url = 'https://example.com'
     self.assertEqual(parse_general_name(url),
                      x509.UniformResourceIdentifier(url))
     self.assertEqual(parse_general_name('uri:%s' % url),
                      x509.UniformResourceIdentifier(url))
class TestCertificateRevocationListBuilder(object):
    def test_issuer_name_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.issuer_name("notanx509name")

    def test_set_issuer_name_twice(self):
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
        )
        with pytest.raises(ValueError):
            builder.issuer_name(
                x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
            )

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_aware_last_update(self, backend):
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        tz = pytz.timezone("US/Pacific")
        last_time = tz.localize(last_time)
        utc_last = datetime.datetime(2012, 1, 17, 6, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        private_key = RSA_KEY_2048.private_key(backend)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(last_time).next_update(next_time)

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.last_update == utc_last

    def test_last_update_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.last_update("notadatetime")

    def test_last_update_before_1950(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(1940, 8, 10))

    def test_set_last_update_twice(self):
        builder = x509.CertificateRevocationListBuilder().last_update(
            datetime.datetime(2002, 1, 1, 12, 1)
        )
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(2002, 1, 1, 12, 1))

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_aware_next_update(self, backend):
        next_time = datetime.datetime(2022, 1, 16, 22, 43)
        tz = pytz.timezone("US/Pacific")
        next_time = tz.localize(next_time)
        utc_next = datetime.datetime(2022, 1, 17, 6, 43)
        last_time = datetime.datetime(2012, 1, 17, 6, 43)
        private_key = RSA_KEY_2048.private_key(backend)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(last_time).next_update(next_time)

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.next_update == utc_next

    def test_next_update_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.next_update("notadatetime")

    def test_next_update_before_1950(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(1940, 8, 10))

    def test_set_next_update_twice(self):
        builder = x509.CertificateRevocationListBuilder().next_update(
            datetime.datetime(2002, 1, 1, 12, 1)
        )
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(2002, 1, 1, 12, 1))

    def test_last_update_after_next_update(self):
        builder = x509.CertificateRevocationListBuilder()

        builder = builder.next_update(
            datetime.datetime(2002, 1, 1, 12, 1)
        )
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(2003, 1, 1, 12, 1))

    def test_next_update_after_last_update(self):
        builder = x509.CertificateRevocationListBuilder()

        builder = builder.last_update(
            datetime.datetime(2002, 1, 1, 12, 1)
        )
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(2001, 1, 1, 12, 1))

    def test_add_extension_checks_for_duplicates(self):
        builder = x509.CertificateRevocationListBuilder().add_extension(
            x509.CRLNumber(1), False
        )

        with pytest.raises(ValueError):
            builder.add_extension(x509.CRLNumber(2), False)

    def test_add_invalid_extension(self):
        builder = x509.CertificateRevocationListBuilder()

        with pytest.raises(TypeError):
            builder.add_extension(
                object(), False
            )

    def test_add_invalid_revoked_certificate(self):
        builder = x509.CertificateRevocationListBuilder()

        with pytest.raises(TypeError):
            builder.add_revoked_certificate(object())

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_issuer_name(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = x509.CertificateRevocationListBuilder().last_update(
            datetime.datetime(2002, 1, 1, 12, 1)
        ).next_update(
            datetime.datetime(2030, 1, 1, 12, 1)
        )

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_last_update(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
        ).next_update(
            datetime.datetime(2030, 1, 1, 12, 1)
        )

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_next_update(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')])
        ).last_update(
            datetime.datetime(2030, 1, 1, 12, 1)
        )

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_empty_list(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(last_update).next_update(next_update)

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert crl.last_update == last_update
        assert crl.next_update == next_update

    @pytest.mark.parametrize(
        "extension",
        [
            x509.CRLNumber(13),
            x509.DeltaCRLIndicator(12345678901234567890),
            x509.AuthorityKeyIdentifier(
                b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08"
                b"\xcbY",
                None,
                None
            ),
            x509.AuthorityInformationAccess([
                x509.AccessDescription(
                    AuthorityInformationAccessOID.CA_ISSUERS,
                    x509.DNSName(u"cryptography.io")
                )
            ]),
            x509.IssuerAlternativeName([
                x509.UniformResourceIdentifier(u"https://cryptography.io"),
            ])
        ]
    )
    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_extensions(self, backend, extension):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_extension(
            extension, False
        )

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert len(crl.extensions) == 1
        ext = crl.extensions.get_extension_for_class(type(extension))
        assert ext.critical is False
        assert ext.value == extension

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_multiple_extensions_critical(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        ian = x509.IssuerAlternativeName([
            x509.UniformResourceIdentifier(u"https://cryptography.io"),
        ])
        crl_number = x509.CRLNumber(13)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_extension(
            crl_number, False
        ).add_extension(
            ian, True
        )

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert len(crl.extensions) == 2
        ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber)
        assert ext1.critical is False
        assert ext1.value == crl_number
        ext2 = crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName
        )
        assert ext2.critical is True
        assert ext2.value == ian

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_add_unsupported_extension(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_extension(
            x509.OCSPNoCheck(), False
        )
        with pytest.raises(NotImplementedError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_rsa_key_too_small(self, backend):
        private_key = RSA_KEY_512.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        )

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA512(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_invalid_hash(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        )

        with pytest.raises(TypeError):
            builder.sign(private_key, object(), backend)

    @pytest.mark.requires_backend_interface(interface=DSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_dsa_key(self, backend):
        private_key = DSA_KEY_2048.private_key(backend)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0)
        )
        ian = x509.IssuerAlternativeName([
            x509.UniformResourceIdentifier(u"https://cryptography.io"),
        ])
        revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
            2
        ).revocation_date(
            datetime.datetime(2012, 1, 1, 1, 1)
        ).add_extension(
            invalidity_date, False
        ).build(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_revoked_certificate(
            revoked_cert0
        ).add_extension(
            ian, False
        )

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName
        ).value == ian
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_ec_key(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())
        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0)
        )
        ian = x509.IssuerAlternativeName([
            x509.UniformResourceIdentifier(u"https://cryptography.io"),
        ])
        revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
            2
        ).revocation_date(
            datetime.datetime(2012, 1, 1, 1, 1)
        ).add_extension(
            invalidity_date, False
        ).build(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_revoked_certificate(
            revoked_cert0
        ).add_extension(
            ian, False
        )

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName
        ).value == ian
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.requires_backend_interface(interface=DSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_dsa_key_sign_md5(self, backend):
        private_key = DSA_KEY_2048.private_key(backend)
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(last_time).next_update(next_time)

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.MD5(), backend)

    @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_ec_key_sign_md5(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())
        private_key = EC_KEY_SECP256R1.private_key(backend)
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(last_time).next_update(next_time)

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.MD5(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_revoked_certificates(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0)
        )
        revoked_cert0 = x509.RevokedCertificateBuilder().serial_number(
            38
        ).revocation_date(
            datetime.datetime(2011, 1, 1, 1, 1)
        ).build(backend)
        revoked_cert1 = x509.RevokedCertificateBuilder().serial_number(
            2
        ).revocation_date(
            datetime.datetime(2012, 1, 1, 1, 1)
        ).add_extension(
            invalidity_date, False
        ).build(backend)
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA")
            ])
        ).last_update(
            last_update
        ).next_update(
            next_update
        ).add_revoked_certificate(
            revoked_cert0
        ).add_revoked_certificate(
            revoked_cert1
        )

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 2
        assert crl.last_update == last_update
        assert crl.next_update == next_update
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 0
        assert crl[1].serial_number == revoked_cert1.serial_number
        assert crl[1].revocation_date == revoked_cert1.revocation_date
        assert len(crl[1].extensions) == 1
        ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date
Esempio n. 5
0
def _decode_general_name(backend, gn):
    if gn.type == backend._lib.GEN_DNS:
        data = _asn1_string_to_bytes(backend, gn.d.dNSName)
        if not data:
            decoded = u""
        elif data.startswith(b"*."):
            # This is a wildcard name. We need to remove the leading wildcard,
            # IDNA decode, then re-add the wildcard. Wildcard characters should
            # always be left-most (RFC 2595 section 2.4).
            decoded = u"*." + idna.decode(data[2:])
        else:
            # Not a wildcard, decode away. If the string has a * in it anywhere
            # invalid this will raise an InvalidCodePoint
            decoded = idna.decode(data)
            if data.startswith(b"."):
                # idna strips leading periods. Name constraints can have that
                # so we need to re-add it. Sigh.
                decoded = u"." + decoded

        return x509.DNSName(decoded)
    elif gn.type == backend._lib.GEN_URI:
        data = _asn1_string_to_ascii(backend, gn.d.uniformResourceIdentifier)
        parsed = urllib_parse.urlparse(data)
        if parsed.hostname:
            hostname = idna.decode(parsed.hostname)
        else:
            hostname = ""
        if parsed.port:
            netloc = hostname + u":" + six.text_type(parsed.port)
        else:
            netloc = hostname

        # Note that building a URL in this fashion means it should be
        # semantically indistinguishable from the original but is not
        # guaranteed to be exactly the same.
        uri = urllib_parse.urlunparse(
            (parsed.scheme, netloc, parsed.path, parsed.params, parsed.query,
             parsed.fragment))
        return x509.UniformResourceIdentifier(uri)
    elif gn.type == backend._lib.GEN_RID:
        oid = _obj2txt(backend, gn.d.registeredID)
        return x509.RegisteredID(x509.ObjectIdentifier(oid))
    elif gn.type == backend._lib.GEN_IPADD:
        data = _asn1_string_to_bytes(backend, gn.d.iPAddress)
        data_len = len(data)
        if data_len == 8 or data_len == 32:
            # This is an IPv4 or IPv6 Network and not a single IP. This
            # type of data appears in Name Constraints. Unfortunately,
            # ipaddress doesn't support packed bytes + netmask. Additionally,
            # IPv6Network can only handle CIDR rather than the full 16 byte
            # netmask. To handle this we convert the netmask to integer, then
            # find the first 0 bit, which will be the prefix. If another 1
            # bit is present after that the netmask is invalid.
            base = ipaddress.ip_address(data[:data_len // 2])
            netmask = ipaddress.ip_address(data[data_len // 2:])
            bits = bin(int(netmask))[2:]
            prefix = bits.find('0')
            # If no 0 bits are found it is a /32 or /128
            if prefix == -1:
                prefix = len(bits)

            if "1" in bits[prefix:]:
                raise ValueError("Invalid netmask")

            ip = ipaddress.ip_network(base.exploded + u"/{0}".format(prefix))
        else:
            ip = ipaddress.ip_address(data)

        return x509.IPAddress(ip)
    elif gn.type == backend._lib.GEN_DIRNAME:
        return x509.DirectoryName(
            _decode_x509_name(backend, gn.d.directoryName))
    elif gn.type == backend._lib.GEN_EMAIL:
        data = _asn1_string_to_ascii(backend, gn.d.rfc822Name)
        name, address = parseaddr(data)
        parts = address.split(u"@")
        if name or not address:
            # parseaddr has found a name (e.g. Name <email>) or the entire
            # value is an empty string.
            raise ValueError("Invalid rfc822name value")
        elif len(parts) == 1:
            # Single label email name. This is valid for local delivery. No
            # IDNA decoding can be done since there is no domain component.
            return x509.RFC822Name(address)
        else:
            # A normal email of the form [email protected]. Let's attempt to
            # decode the domain component and return the entire address.
            return x509.RFC822Name(parts[0] + u"@" + idna.decode(parts[1]))
    elif gn.type == backend._lib.GEN_OTHERNAME:
        type_id = _obj2txt(backend, gn.d.otherName.type_id)
        value = _asn1_to_der(backend, gn.d.otherName.value)
        return x509.OtherName(x509.ObjectIdentifier(type_id), value)
    else:
        # x400Address or ediPartyName
        raise x509.UnsupportedGeneralNameType(
            "{0} is not a supported type".format(
                x509._GENERAL_NAMES.get(gn.type, gn.type)), gn.type)
Esempio n. 6
0
def _decode_general_name(backend, gn):
    if gn.type == backend._lib.GEN_DNS:
        data = _asn1_string_to_bytes(backend, gn.d.dNSName)
        if data.startswith(b"*."):
            # This is a wildcard name. We need to remove the leading wildcard,
            # IDNA decode, then re-add the wildcard. Wildcard characters should
            # always be left-most (RFC 2595 section 2.4).
            decoded = u"*." + idna.decode(data[2:])
        else:
            # Not a wildcard, decode away. If the string has a * in it anywhere
            # invalid this will raise an InvalidCodePoint
            decoded = idna.decode(data)
            if data.startswith(b"."):
                # idna strips leading periods. Name constraints can have that
                # so we need to re-add it. Sigh.
                decoded = u"." + decoded

        return x509.DNSName(decoded)
    elif gn.type == backend._lib.GEN_URI:
        data = _asn1_string_to_ascii(backend, gn.d.uniformResourceIdentifier)
        parsed = urllib_parse.urlparse(data)
        hostname = idna.decode(parsed.hostname)
        if parsed.port:
            netloc = hostname + u":" + six.text_type(parsed.port)
        else:
            netloc = hostname

        # Note that building a URL in this fashion means it should be
        # semantically indistinguishable from the original but is not
        # guaranteed to be exactly the same.
        uri = urllib_parse.urlunparse(
            (parsed.scheme, netloc, parsed.path, parsed.params, parsed.query,
             parsed.fragment))
        return x509.UniformResourceIdentifier(uri)
    elif gn.type == backend._lib.GEN_RID:
        oid = _obj2txt(backend, gn.d.registeredID)
        return x509.RegisteredID(x509.ObjectIdentifier(oid))
    elif gn.type == backend._lib.GEN_IPADD:
        return x509.IPAddress(
            ipaddress.ip_address(_asn1_string_to_bytes(backend,
                                                       gn.d.iPAddress)))
    elif gn.type == backend._lib.GEN_DIRNAME:
        return x509.DirectoryName(
            _decode_x509_name(backend, gn.d.directoryName))
    elif gn.type == backend._lib.GEN_EMAIL:
        data = _asn1_string_to_ascii(backend, gn.d.rfc822Name)
        name, address = parseaddr(data)
        parts = address.split(u"@")
        if name or len(parts) > 2 or not address:
            # parseaddr has found a name (e.g. Name <email>) or the split
            # has found more than 2 parts (which means more than one @ sign)
            # or the entire value is an empty string.
            raise ValueError("Invalid rfc822name value")
        elif len(parts) == 1:
            # Single label email name. This is valid for local delivery. No
            # IDNA decoding can be done since there is no domain component.
            return x509.RFC822Name(address)
        else:
            # A normal email of the form [email protected]. Let's attempt to
            # decode the domain component and return the entire address.
            return x509.RFC822Name(parts[0] + u"@" + idna.decode(parts[1]))
    else:
        # otherName, x400Address or ediPartyName
        raise x509.UnsupportedGeneralNameType(
            "{0} is not a supported type".format(
                x509._GENERAL_NAMES.get(gn.type, gn.type)), gn.type)
Esempio n. 7
0
def uri(url):  # just a shortcut
    """Shortcut to get a :py:class:`cg:cryptography.x509.UniformResourceIdentifier`."""
    return x509.UniformResourceIdentifier(url)
Esempio n. 8
0
def create_ca_issuer(url):
    return x509.AccessDescription(
        x509.AuthorityInformationAccessOID.CA_ISSUERS,
        x509.UniformResourceIdentifier(url))
Esempio n. 9
0
    def action(self, act):
        if act == 'prep':
            img_name_tag = 'dtn-demo'

            LOGGER.info('Building image...')
            cmd = [
                'build', '-t', img_name_tag, '.', '-f', 'container/Dockerfile'
            ]
            if self.args.image_no_cache:
                cmd += ['--no-cache']
            self.run_docker(cmd)

            LOGGER.info("Ensuring networks...")
            for (net_name, net_opts) in self._config['nets'].items():
                try:
                    self.run_docker(['network', 'inspect', net_name])
                except subprocess.CalledProcessError:
                    LOGGER.info("Creating network %s", net_name)
                    cmd = ['network', 'create', net_name]
                    if 'subnet4' in net_opts:
                        cmd += ['--subnet', net_opts['subnet4']]
                    if 'subnet6' in net_opts:
                        cmd += ['--ipv6', '--subnet', net_opts['subnet6']]
                    self.run_docker(cmd)

            nowtime = datetime.datetime.now(datetime.timezone.utc)

            # Private CA
            ca_key = generate_key({})
            with open(os.path.join('container', 'workdir', 'ca.key'),
                      'wb') as outfile:
                outfile.write(
                    ca_key.private_bytes(
                        encoding=serialization.Encoding.PEM,
                        format=serialization.PrivateFormat.TraditionalOpenSSL,
                        encryption_algorithm=serialization.NoEncryption(),
                    ))
            ca_name = x509.Name([
                x509.NameAttribute(x509.oid.NameOID.COMMON_NAME,
                                   'Certificate Authority'),
            ])
            ca_cert = x509.CertificateBuilder().subject_name(
                ca_name).issuer_name(ca_name).public_key(
                    ca_key.public_key()).serial_number(
                        x509.random_serial_number()
                    ).not_valid_before(nowtime).not_valid_after(
                        nowtime + datetime.timedelta(days=10)).add_extension(
                            x509.BasicConstraints(ca=True, path_length=1),
                            critical=True,
                        ).add_extension(
                            x509.KeyUsage(
                                digital_signature=False,
                                content_commitment=False,
                                key_encipherment=False,
                                data_encipherment=False,
                                key_agreement=False,
                                key_cert_sign=True,
                                crl_sign=True,
                                encipher_only=False,
                                decipher_only=False,
                            ),
                            critical=False,
                        ).add_extension(
                            x509.SubjectKeyIdentifier.from_public_key(
                                ca_key.public_key()),
                            critical=False,
                        ).add_extension(
                            x509.AuthorityKeyIdentifier.from_issuer_public_key(
                                ca_key.public_key()),
                            critical=False,
                        ).sign(ca_key,
                               hashes.SHA256(),
                               backend=default_backend())

            for (node_name, node_opts) in self._config['nodes'].items():
                fqdn = node_name + '.local'

                configPath = os.path.join('container', 'workdir', node_name,
                                          'xdg', 'dtn')
                if not os.path.isdir(configPath):
                    os.makedirs(configPath)

                with open(os.path.join(configPath, 'ca.crt'), 'wb') as outfile:
                    outfile.write(
                        ca_cert.public_bytes(serialization.Encoding.PEM))

                # Generate node keys
                sans = [
                    x509.UniformResourceIdentifier(
                        'dtn://{}/'.format(node_name)),
                ]
                ekus = [
                    x509.oid.ObjectIdentifier(
                        '1.3.6.1.5.5.7.3.35')  # id-kp-bundleSecurity
                ]
                for key_name in ('transport', 'sign'):
                    key_opts = node_opts.get('keys', {}).get(key_name, {})
                    node_key = generate_key(key_opts)
                    with open(os.path.join(configPath, f'{key_name}.key'),
                              'wb') as outfile:
                        outfile.write(
                            node_key.private_bytes(
                                encoding=serialization.Encoding.PEM,
                                format=serialization.PrivateFormat.
                                TraditionalOpenSSL,
                                encryption_algorithm=serialization.
                                NoEncryption(),
                            ))

                    if key_name == 'transport':
                        sans += [
                            x509.DNSName(fqdn),
                        ]
                        ekus += [
                            x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH,
                            x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
                        ]

                    node_cert = x509.CertificateBuilder().subject_name(
                        x509.Name([
                            x509.NameAttribute(x509.oid.NameOID.COMMON_NAME,
                                               node_name),
                        ]), ).issuer_name(ca_cert.subject).public_key(
                            node_key.public_key()).serial_number(
                                x509.random_serial_number()
                            ).not_valid_before(nowtime).not_valid_after(
                                nowtime +
                                datetime.timedelta(days=10)).add_extension(
                                    x509.BasicConstraints(ca=False,
                                                          path_length=None),
                                    critical=True,
                                ).add_extension(
                                    x509.SubjectAlternativeName(sans),
                                    critical=False,
                                ).add_extension(
                                    x509.KeyUsage(
                                        digital_signature=True,
                                        content_commitment=False,
                                        key_encipherment=False,
                                        data_encipherment=False,
                                        key_agreement=False,
                                        key_cert_sign=False,
                                        crl_sign=False,
                                        encipher_only=False,
                                        decipher_only=False,
                                    ),
                                    critical=False,
                                ).add_extension(
                                    x509.ExtendedKeyUsage(ekus),
                                    critical=False,
                                ).add_extension(
                                    x509.SubjectKeyIdentifier.from_public_key(
                                        node_key.public_key()),
                                    critical=False,
                                ).add_extension(
                                    x509.AuthorityKeyIdentifier.
                                    from_issuer_public_key(
                                        ca_key.public_key()),
                                    critical=False,
                                ).sign(ca_key,
                                       hashes.SHA256(),
                                       backend=default_backend())
                    with open(os.path.join(configPath, f'{key_name}.crt'),
                              'wb') as outfile:
                        outfile.write(
                            node_cert.public_bytes(serialization.Encoding.PEM))

                extconfig = node_opts.get('config', {})
                use_ipv4 = node_opts.get('use_ipv4', True)
                use_ipv6 = node_opts.get('use_ipv6', True)
                nodeid = 'dtn://{}/'.format(node_name)

                udpcl_listen = []
                if extconfig.get('udpcl_listen', True):
                    if use_ipv4:
                        udpcl_listen.append({
                            'address':
                            '0.0.0.0',
                            'multicast_member': [
                                {
                                    'addr': '224.0.0.1',
                                },
                            ],
                        })
                    if use_ipv6:
                        udpcl_listen.append({
                            'address':
                            '::',
                            'multicast_member': [
                                {
                                    'addr': 'FF02:0:0:0:0:0:0:1',
                                    'iface': 'eth0',
                                },
                            ],
                        })

                tcpcl_listen = []
                if extconfig.get('tcpcl_listen', True):
                    if use_ipv4:
                        tcpcl_listen.append({
                            'address': '0.0.0.0',
                        })
                    if use_ipv6:
                        tcpcl_listen.append({
                            'address': '::',
                        })

                bp_rx_routes = extconfig.get('bp_rx_routes', [])
                bp_rx_routes += [
                    {
                        'eid_pattern': r'dtn://{{node_name}}/.*',
                        'action': 'deliver',
                    },
                    {
                        'eid_pattern': '".*"',
                        'action': 'forward',
                    },
                ]
                bp_tx_routes = extconfig.get('bp_tx_routes', [])

                nodeconf = {
                    'udpcl': {
                        'log_level': 'debug',
                        'bus_addr': 'system',
                        'bus_service': 'org.ietf.dtn.node.udpcl',
                        'node_id': nodeid,
                        'dtls_enable_tx': False,
                        'dtls_ca_file': '/etc/xdg/dtn/ca.crt',
                        'dtls_cert_file': '/etc/xdg/dtn/transport.crt',
                        'dtls_key_file': '/etc/xdg/dtn/transport.key',
                        'polling': extconfig.get('udpcl_polling', []),
                        'init_listen': udpcl_listen,
                    },
                    'tcpcl': {
                        'log_level': 'debug',
                        'bus_addr': 'system',
                        'bus_service': 'org.ietf.dtn.node.tcpcl',
                        'node_id': nodeid,
                        'tls_enable': False,
                        'tls_ca_file': '/etc/xdg/dtn/ca.crt',
                        'tls_cert_file': '/etc/xdg/dtn/transport.crt',
                        'tls_key_file': '/etc/xdg/dtn/transport.key',
                        'init_listen': tcpcl_listen,
                    },
                    'bp': {
                        'log_level': 'debug',
                        'bus_addr': 'system',
                        'bus_service': 'org.ietf.dtn.node.bp',
                        'node_id': nodeid,
                        'verify_ca_file': '/etc/xdg/dtn/ca.crt',
                        'sign_cert_file': '/etc/xdg/dtn/sign.crt',
                        'sign_key_file': '/etc/xdg/dtn/sign.key',
                        'rx_route_table': bp_rx_routes,
                        'tx_route_table': bp_tx_routes,
                        'apps': extconfig.get('apps', {})
                    },
                }
                with open(os.path.join(configPath, 'node.yaml'),
                          'w') as outfile:
                    outfile.write(yaml.dump(nodeconf))

                cmd = [
                    'container',
                    'create',
                    '--privileged',
                    '-e',
                    'container=docker',
                    '--mount',
                    f'type=bind,src={SELFDIR}/workdir/{node_name}/xdg/dtn,dst=/etc/xdg/dtn',
                    '--hostname',
                    fqdn,
                    '--name',
                    node_name,
                ]
                cmd += [img_name_tag]
                self.run_docker(cmd)

                cmd = ['network', 'disconnect', 'bridge', node_name]
                self.run_docker(cmd)

                for net_name in node_opts['nets']:
                    cmd = ['network', 'connect', net_name, node_name]
                    self.run_docker(cmd)

        elif act == 'start':
            for node_name in self._config['nodes'].keys():
                self.run_docker(['container', 'start', node_name])

        elif act == 'stop':
            for node_name in self._config['nodes'].keys():
                try:
                    self.run_docker(['container', 'stop', node_name])
                except subprocess.CalledProcessError:
                    pass

        elif act == 'delete':
            for node_name in self._config['nodes'].keys():
                try:
                    self.run_docker(['container', 'rm', '-f', node_name])
                except subprocess.CalledProcessError:
                    pass
            for net_name in self._config['nets'].keys():
                try:
                    self.run_docker(['network', 'rm', net_name])
                except subprocess.CalledProcessError:
                    pass
Esempio n. 10
0
    def test_generate_root_ca(self):
        key = Key().create_key(2048)
        certificate = CertificateFactory(key=key.serialize())
        certhandler = Certificate()
        certhandler.create_certificate(certificate)

        crt = certhandler.certificate

        self.assertEqual(crt.serial_number, int(certificate.serial))
        self.assertEqual(crt.public_key().public_numbers(),
                         key.key.public_key().public_numbers())
        self.assertEqual(
            crt.not_valid_before,
            datetime.datetime(year=certificate.created_at.year,
                              month=certificate.created_at.month,
                              day=certificate.created_at.day))

        self.assertEqual(
            crt.not_valid_after,
            datetime.datetime(year=certificate.expires_at.year,
                              month=certificate.expires_at.month,
                              day=certificate.expires_at.day))

        # subject
        subject = crt.subject
        self.assertIsInstance(subject, x509.Name)
        self.assertListEqual(list(subject), [
            x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME,
                               certificate.dn.organizationName),
            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                               certificate.dn.organizationalUnitName),
            x509.NameAttribute(NameOID.LOCALITY_NAME,
                               certificate.dn.localityName),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
                               certificate.dn.stateOrProvinceName),
            x509.NameAttribute(NameOID.EMAIL_ADDRESS,
                               certificate.dn.emailAddress),
            x509.NameAttribute(NameOID.COUNTRY_NAME,
                               str(certificate.dn.countryName)),
        ])

        # issuer
        issuer = crt.issuer
        self.assertIsInstance(issuer, x509.Name)
        self.assertListEqual(list(subject), [
            x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME,
                               certificate.dn.organizationName),
            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                               certificate.dn.organizationalUnitName),
            x509.NameAttribute(NameOID.LOCALITY_NAME,
                               certificate.dn.localityName),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
                               certificate.dn.stateOrProvinceName),
            x509.NameAttribute(NameOID.EMAIL_ADDRESS,
                               certificate.dn.emailAddress),
            x509.NameAttribute(NameOID.COUNTRY_NAME,
                               str(certificate.dn.countryName)),
        ])
        # crlDistributionspoints
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.CRL_DISTRIBUTION_POINTS)
        self.assertTrue(ext.critical)
        crl_dp = ext.value
        self.assertEqual(
            crl_dp[0].full_name[0].value,
            'URI:{}{}.crl'.format(certificate.crl_distribution_url,
                                  certificate.shortname))
        self.assertEqual(
            crl_dp[0].reasons,
            frozenset([
                x509.ReasonFlags.key_compromise,
                x509.ReasonFlags.ca_compromise,
                x509.ReasonFlags.affiliation_changed,
                x509.ReasonFlags.superseded,
                x509.ReasonFlags.privilege_withdrawn,
                x509.ReasonFlags.cessation_of_operation,
                x509.ReasonFlags.aa_compromise,
                x509.ReasonFlags.certificate_hold,
            ]))

        # keyUsage = basicConstraints = critical, CA:true
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.BASIC_CONSTRAINTS)
        self.assertTrue(ext.critical)
        self.assertEqual(ext.value,
                         x509.BasicConstraints(ca=True, path_length=None))

        # keyUsage = critical, digitalSignature, cRLSign, keyCertSign
        ext = crt.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE)
        self.assertTrue(ext.critical)
        self.assertEqual(
            ext.value,
            x509.KeyUsage(digital_signature=True,
                          content_commitment=False,
                          key_encipherment=False,
                          data_encipherment=False,
                          key_agreement=False,
                          key_cert_sign=True,
                          crl_sign=True,
                          encipher_only=False,
                          decipher_only=False))

        # OCSP
        # authorityInfoAccess = OCSP;URI:{{cert.ocsp_distribution_host}}
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.AUTHORITY_INFORMATION_ACCESS)
        self.assertTrue(ext.critical)
        self.assertEqual(
            ext.value[0],
            x509.AccessDescription(
                AuthorityInformationAccessOID.OCSP,
                x509.UniformResourceIdentifier(
                    certificate.ocsp_distribution_host)))

        # authorityKeyIdentifier = keyid:always, issuer
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.AUTHORITY_KEY_IDENTIFIER)
        self.assertTrue(ext.critical)
        self.assertEqual(ext.value.key_identifier,
                         _key_identifier_from_public_key(key.key.public_key()))

        # subjectKeyIdentifier = hash
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_KEY_IDENTIFIER)
        self.assertTrue(ext.critical)
        self.assertEqual(ext.value.digest,
                         _key_identifier_from_public_key(key.key.public_key()))
Esempio n. 11
0
    def test_generate_user_certificate(self):
        root_key = Key().create_key(2048)
        root_certificate = CertificateFactory(expires_at=arrow.get(
            timezone.now()).replace(days=+6).date(),
                                              key=root_key.serialize())
        root_certhandler = Certificate()
        root_certhandler.create_certificate(root_certificate)

        subject = DistinguishedNameFactory(
            countryName=root_certificate.dn.countryName,
            stateOrProvinceName=root_certificate.dn.stateOrProvinceName,
            organizationName=root_certificate.dn.organizationName)

        int_key = Key().create_key(2048)

        int_certificate = CertificateFactory(
            expires_at=arrow.get(timezone.now()).replace(days=+5).date(),
            type=CertificateTypes.INTERMEDIATE,
            parent=root_certificate,
            dn=subject,
            key=int_key.serialize())
        int_certhandler = Certificate()
        int_certhandler.create_certificate(int_certificate)

        key = Key().create_key(2048)
        server_subject = DistinguishedNameFactory(
            subjectAltNames=["jeroen", "*****@*****.**"])
        certificate = CertificateFactory(type=CertificateTypes.CLIENT_CERT,
                                         parent=int_certificate,
                                         dn=server_subject,
                                         key=key.serialize())
        certhandler = Certificate()
        certhandler.create_certificate(certificate)

        crt = certhandler.certificate

        self.assertEqual(crt.serial_number, int(certificate.serial))
        self.assertEqual(crt.public_key().public_numbers(),
                         key.key.public_key().public_numbers())
        self.assertEqual(
            crt.not_valid_before,
            datetime.datetime(year=certificate.created_at.year,
                              month=certificate.created_at.month,
                              day=certificate.created_at.day))

        self.assertEqual(
            crt.not_valid_after,
            datetime.datetime(year=certificate.expires_at.year,
                              month=certificate.expires_at.month,
                              day=certificate.expires_at.day))
        # basicConstraints = CA:FALSE
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.BASIC_CONSTRAINTS)
        self.assertFalse(ext.critical)
        self.assertEqual(ext.value,
                         x509.BasicConstraints(ca=False, path_length=None))

        # authorityKeyIdentifier = keyid:always, issuer
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.AUTHORITY_KEY_IDENTIFIER)
        self.assertTrue(ext.critical)
        self.assertEqual(
            ext.value.key_identifier,
            _key_identifier_from_public_key(int_key.key.public_key()))

        # subjectKeyIdentifier = hash
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_KEY_IDENTIFIER)
        self.assertTrue(ext.critical)
        self.assertEqual(ext.value.digest,
                         _key_identifier_from_public_key(key.key.public_key()))

        # keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
        ext = crt.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE)
        self.assertTrue(ext.critical)
        self.assertEqual(
            ext.value,
            x509.KeyUsage(digital_signature=True,
                          content_commitment=True,
                          key_encipherment=True,
                          data_encipherment=False,
                          key_agreement=False,
                          key_cert_sign=False,
                          crl_sign=False,
                          encipher_only=False,
                          decipher_only=False))

        # extendedKeyUsage = clientAuth, emailProtection
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.EXTENDED_KEY_USAGE)
        self.assertFalse(ext.critical)
        self.assertEqual([x for x in ext.value], [
            ExtendedKeyUsageOID.CLIENT_AUTH,
            ExtendedKeyUsageOID.EMAIL_PROTECTION
        ])

        # { % if cert.dn.subjectAltNames %}
        # subjectAltName = @alt_names
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
        self.assertFalse(ext.critical)
        self.assertEqual([x.value for x in ext.value],
                         ['jeroen', '*****@*****.**'])

        # crlDistributionPoints
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.CRL_DISTRIBUTION_POINTS)
        self.assertTrue(ext.critical)
        crl_dp = ext.value
        self.assertEqual(
            crl_dp[0].full_name[0].value,
            'URI:{}{}.crl'.format(int_certificate.crl_distribution_url,
                                  int_certificate.shortname))
        self.assertEqual(
            crl_dp[0].reasons,
            frozenset([
                x509.ReasonFlags.key_compromise,
                x509.ReasonFlags.ca_compromise,
                x509.ReasonFlags.affiliation_changed,
                x509.ReasonFlags.superseded,
                x509.ReasonFlags.privilege_withdrawn,
                x509.ReasonFlags.cessation_of_operation,
                x509.ReasonFlags.aa_compromise,
                x509.ReasonFlags.certificate_hold,
            ]))

        # OCSP
        # authorityInfoAccess = OCSP;URI:{{cert.ocsp_distribution_host}}
        ext = crt.extensions.get_extension_for_oid(
            ExtensionOID.AUTHORITY_INFORMATION_ACCESS)
        self.assertTrue(ext.critical)
        self.assertEqual(
            ext.value[0],
            x509.AccessDescription(
                AuthorityInformationAccessOID.OCSP,
                x509.UniformResourceIdentifier(
                    int_certificate.ocsp_distribution_host)))

        # subject
        subject = crt.subject
        self.assertIsInstance(subject, x509.Name)
        self.assertListEqual(list(subject), [
            x509.NameAttribute(NameOID.COMMON_NAME, certificate.dn.commonName),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME,
                               certificate.dn.organizationName),
            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                               certificate.dn.organizationalUnitName),
            x509.NameAttribute(NameOID.LOCALITY_NAME,
                               certificate.dn.localityName),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
                               certificate.dn.stateOrProvinceName),
            x509.NameAttribute(NameOID.EMAIL_ADDRESS,
                               certificate.dn.emailAddress),
            x509.NameAttribute(NameOID.COUNTRY_NAME,
                               str(certificate.dn.countryName)),
        ])

        # issuer
        issuer = crt.issuer
        self.assertIsInstance(issuer, x509.Name)
        self.assertListEqual(list(issuer), [
            x509.NameAttribute(NameOID.COMMON_NAME,
                               int_certificate.dn.commonName),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME,
                               int_certificate.dn.organizationName),
            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                               int_certificate.dn.organizationalUnitName),
            x509.NameAttribute(NameOID.LOCALITY_NAME,
                               int_certificate.dn.localityName),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
                               int_certificate.dn.stateOrProvinceName),
            x509.NameAttribute(NameOID.EMAIL_ADDRESS,
                               int_certificate.dn.emailAddress),
            x509.NameAttribute(NameOID.COUNTRY_NAME,
                               str(int_certificate.dn.countryName)),
        ])
Esempio n. 12
0
def _build_general_name(backend, gn):
    if gn.type == backend._lib.GEN_DNS:
        data = backend._ffi.buffer(gn.d.dNSName.data, gn.d.dNSName.length)[:]
        return x509.DNSName(idna.decode(data))
    elif gn.type == backend._lib.GEN_URI:
        data = backend._ffi.buffer(
            gn.d.uniformResourceIdentifier.data,
            gn.d.uniformResourceIdentifier.length
        )[:].decode("ascii")
        parsed = urllib_parse.urlparse(data)
        hostname = idna.decode(parsed.hostname)
        if parsed.port:
            netloc = hostname + u":" + six.text_type(parsed.port)
        else:
            netloc = hostname

        # Note that building a URL in this fashion means it should be
        # semantically indistinguishable from the original but is not
        # guaranteed to be exactly the same.
        uri = urllib_parse.urlunparse((
            parsed.scheme,
            netloc,
            parsed.path,
            parsed.params,
            parsed.query,
            parsed.fragment
        ))
        return x509.UniformResourceIdentifier(uri)
    elif gn.type == backend._lib.GEN_RID:
        oid = _obj2txt(backend, gn.d.registeredID)
        return x509.RegisteredID(x509.ObjectIdentifier(oid))
    elif gn.type == backend._lib.GEN_IPADD:
        return x509.IPAddress(
            ipaddress.ip_address(
                backend._ffi.buffer(
                    gn.d.iPAddress.data, gn.d.iPAddress.length
                )[:]
            )
        )
    elif gn.type == backend._lib.GEN_DIRNAME:
        return x509.DirectoryName(
            _build_x509_name(backend, gn.d.directoryName)
        )
    elif gn.type == backend._lib.GEN_EMAIL:
        data = backend._ffi.buffer(
            gn.d.rfc822Name.data, gn.d.rfc822Name.length
        )[:].decode("ascii")
        name, address = parseaddr(data)
        parts = address.split(u"@")
        if name or len(parts) > 2 or not address:
            # parseaddr has found a name (e.g. Name <email>) or the split
            # has found more than 2 parts (which means more than one @ sign)
            # or the entire value is an empty string.
            raise ValueError("Invalid rfc822name value")
        elif len(parts) == 1:
            # Single label email name. This is valid for local delivery. No
            # IDNA decoding can be done since there is no domain component.
            return x509.RFC822Name(address)
        else:
            # A normal email of the form [email protected]. Let's attempt to
            # decode the domain component and return the entire address.
            return x509.RFC822Name(
                parts[0] + u"@" + idna.decode(parts[1])
            )
    else:
        # otherName, x400Address or ediPartyName
        raise x509.UnsupportedGeneralNameType(
            "{0} is not a supported type".format(
                x509._GENERAL_NAMES.get(gn.type, gn.type)
            ),
            gn.type
        )
Esempio n. 13
0
def parse_general_name(name: ParsableGeneralName) -> x509.GeneralName:
    """Parse a general name from user input.

    This function will do its best to detect the intended type of any value passed to it:

    >>> parse_general_name('example.com')
    <DNSName(value='example.com')>
    >>> parse_general_name('*.example.com')
    <DNSName(value='*.example.com')>
    >>> parse_general_name('.example.com')  # Syntax used e.g. for NameConstraints: All levels of subdomains
    <DNSName(value='.example.com')>
    >>> parse_general_name('*****@*****.**')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('1.2.3.4')
    <IPAddress(value=1.2.3.4)>
    >>> parse_general_name('fd00::1')
    <IPAddress(value=fd00::1)>
    >>> parse_general_name('/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't
    work, an exception will be raised:

    >>> parse_general_name('foo..bar`*123')  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: Could not parse name: foo..bar`*123

    If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`:

    >>> parse_general_name('email:[email protected]')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('URI:https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('dirname:/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    Some more exotic values can only be generated by using this prefix:

    >>> parse_general_name('rid:2.5.4.3')
    <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)>
    >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com')
    <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')>

    If you give a prefixed value, this function is less forgiving of any typos and does not catch any
    exceptions:

    >>> parse_general_name('email:foo@bar com')
    Traceback (most recent call last):
        ...
    ValueError: Invalid domain: bar com

    """
    # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements

    if isinstance(name, x509.GeneralName):
        return name
    if not isinstance(name, str):
        raise ValueError(
            f"Cannot parse general name {name}: Must be of type str (was: {type(name).__name__})."
        )

    typ = None
    match = GENERAL_NAME_RE.match(name)
    if match is not None:
        typ, name = match.groups()
        typ = typ.lower()

    if typ is None:
        if re.match("[a-z0-9]{2,}://", name):  # Looks like a URI
            try:
                return x509.UniformResourceIdentifier(encode_url(name))
            except idna.IDNAError:
                pass

        if "@" in name:  # Looks like an Email address
            try:
                return x509.RFC822Name(validate_email(name))
            except ValueError:
                pass

        if name.strip().startswith("/"):  # maybe it's a dirname?
            return x509.DirectoryName(x509_name(name))

        # Try to parse this as IPAddress/Network
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass
        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        # Almost anything passes as DNS name, so this is our default fallback
        try:
            return x509.DNSName(encode_dns(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse name: {name}") from e

    if typ == "uri":
        try:
            return x509.UniformResourceIdentifier(encode_url(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse DNS name in URL: {name}") from e
    elif typ == "email":
        return x509.RFC822Name(
            validate_email(name))  # validate_email already raises ValueError
    elif typ == "ip":
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass

        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        raise ValueError("Could not parse IP address.")
    elif typ == "rid":
        return x509.RegisteredID(x509.ObjectIdentifier(name))
    elif typ == "othername":
        match = re.match("(.*);(.*):(.*)", name)
        if match is not None:
            oid, asn_typ, val = match.groups()
            if asn_typ == "UTF8":
                parsed_value = val.encode("utf-8")
            elif asn_typ == "OctetString":
                parsed_value = OctetString(bytes(
                    bytearray.fromhex(val))).dump()
            else:
                raise ValueError(
                    f"Unsupported ASN type in otherName: {asn_typ}")

            return x509.OtherName(x509.ObjectIdentifier(oid), parsed_value)

        raise ValueError(f"Incorrect otherName format: {name}")
    elif typ == "dirname":
        return x509.DirectoryName(x509_name(name))
    else:
        try:
            return x509.DNSName(encode_dns(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse DNS name: {name}") from e
Esempio n. 14
0
    def signing_publickey(self,
                          user: User,
                          service: Service,
                          publickey: str,
                          valid_time=DAY * 365):
        _public_key = serialization.load_pem_public_key(
            publickey.encode(), backend=default_backend())

        ca_private_key, ca_cert = self._init_ca(service)
        ca_name = service.name
        username = str(user.username)
        config = service.pki_config  #TODO use this config
        domain = self._domain
        not_valid_before = datetime.datetime.utcnow()

        ca_public_key = ca_private_key.public_key()
        end_entity_cert_builder = x509.CertificateBuilder().\
            subject_name(x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, config['cn'].format(username=username, domain=domain)),
                x509.NameAttribute(NameOID.EMAIL_ADDRESS, config['email'].format(username=username, domain=domain)),
            ])).\
            issuer_name(ca_cert.subject).\
            not_valid_before(not_valid_before).\
            not_valid_after(not_valid_before + valid_time).\
            serial_number(x509.random_serial_number()).\
            public_key(_public_key).\
            add_extension(
                x509.SubjectAlternativeName([
                    x509.DNSName(f'{username}'),
                    ]),
                critical=False).\
            add_extension(
                x509.BasicConstraints(ca=False, path_length=None),
                critical=True).\
            add_extension(
                x509.KeyUsage(digital_signature=True,
                              content_commitment=True,  # False
                              key_encipherment=True,
                              data_encipherment=False,
                              key_agreement=False,
                              key_cert_sign=False,
                              crl_sign=False,
                              encipher_only=False,
                              decipher_only=False),
                critical=True).\
            add_extension(
                x509.ExtendedKeyUsage([
                    ExtendedKeyUsageOID.CLIENT_AUTH,
                    ExtendedKeyUsageOID.SERVER_AUTH,
                ]), critical=False).\
            add_extension(
                x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_public_key),
                critical=False).\
            add_extension(
                x509.SubjectKeyIdentifier.from_public_key(_public_key),
                critical=False).\
            add_extension(
                x509.CRLDistributionPoints([
                    x509.DistributionPoint(
                        full_name=[x509.UniformResourceIdentifier(f'http://crl.{self._domain}/{ca_name}.crl')],
                        relative_name=None,
                        crl_issuer=None,
                        reasons=None)
                    ]),
                critical=False).\
            add_extension(
                x509.AuthorityInformationAccess([
                    x509.AccessDescription(
                        access_method=x509.AuthorityInformationAccessOID.CA_ISSUERS,
                        access_location=x509.UniformResourceIdentifier(f'https://www.{self._domain}')),
                    x509.AccessDescription(
                        access_method=x509.AuthorityInformationAccessOID.OCSP,
                        access_location=x509.UniformResourceIdentifier(f'http://ocsp.{self._domain}/{ca_name}/'))
                    ]),
                critical=False)

        end_entity_cert = end_entity_cert_builder.\
            sign(
                private_key=ca_private_key,
                algorithm=hashes.SHA256(),
                backend=default_backend()
            )

        serial_number = f'{end_entity_cert.serial_number:X}'
        end_entity_cert_filename = self._pki_path / ca_name / \
            f'{safe_filename(username)}-{serial_number}.crt.pem'
        # save cert
        with end_entity_cert_filename.open("wb") as end_entity_cert_file:
            end_entity_cert_file.write(
                end_entity_cert.public_bytes(
                    encoding=serialization.Encoding.PEM))

        return Certificate(user.username, service.name, end_entity_cert)
Esempio n. 15
0
class TestCertificateRevocationListBuilder(object):
    def test_issuer_name_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.issuer_name("notanx509name")  # type:ignore[arg-type]

    def test_set_issuer_name_twice(self):
        builder = x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]))
        with pytest.raises(ValueError):
            builder.issuer_name(
                x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]))

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_aware_last_update(self, backend):
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        tz = pytz.timezone("US/Pacific")
        last_time = tz.localize(last_time)
        utc_last = datetime.datetime(2012, 1, 17, 6, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        private_key = RSA_KEY_2048.private_key(backend)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_time).next_update(next_time))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.last_update == utc_last

    def test_last_update_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.last_update("notadatetime")  # type:ignore[arg-type]

    def test_last_update_before_1950(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(1940, 8, 10))

    def test_set_last_update_twice(self):
        builder = x509.CertificateRevocationListBuilder().last_update(
            datetime.datetime(2002, 1, 1, 12, 1))
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(2002, 1, 1, 12, 1))

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_aware_next_update(self, backend):
        next_time = datetime.datetime(2022, 1, 16, 22, 43)
        tz = pytz.timezone("US/Pacific")
        next_time = tz.localize(next_time)
        utc_next = datetime.datetime(2022, 1, 17, 6, 43)
        last_time = datetime.datetime(2012, 1, 17, 6, 43)
        private_key = RSA_KEY_2048.private_key(backend)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_time).next_update(next_time))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert crl.next_update == utc_next

    def test_next_update_invalid(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(TypeError):
            builder.next_update("notadatetime")  # type:ignore[arg-type]

    def test_next_update_before_1950(self):
        builder = x509.CertificateRevocationListBuilder()
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(1940, 8, 10))

    def test_set_next_update_twice(self):
        builder = x509.CertificateRevocationListBuilder().next_update(
            datetime.datetime(2002, 1, 1, 12, 1))
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(2002, 1, 1, 12, 1))

    def test_last_update_after_next_update(self):
        builder = x509.CertificateRevocationListBuilder()

        builder = builder.next_update(datetime.datetime(2002, 1, 1, 12, 1))
        with pytest.raises(ValueError):
            builder.last_update(datetime.datetime(2003, 1, 1, 12, 1))

    def test_next_update_after_last_update(self):
        builder = x509.CertificateRevocationListBuilder()

        builder = builder.last_update(datetime.datetime(2002, 1, 1, 12, 1))
        with pytest.raises(ValueError):
            builder.next_update(datetime.datetime(2001, 1, 1, 12, 1))

    def test_add_extension_checks_for_duplicates(self):
        builder = x509.CertificateRevocationListBuilder().add_extension(
            x509.CRLNumber(1), False)

        with pytest.raises(ValueError):
            builder.add_extension(x509.CRLNumber(2), False)

    def test_add_invalid_extension(self):
        builder = x509.CertificateRevocationListBuilder()

        with pytest.raises(TypeError):
            builder.add_extension(object(), False)  # type:ignore[arg-type]

    def test_add_invalid_revoked_certificate(self):
        builder = x509.CertificateRevocationListBuilder()

        with pytest.raises(TypeError):
            builder.add_revoked_certificate(object())  # type:ignore[arg-type]

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_issuer_name(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = (x509.CertificateRevocationListBuilder().last_update(
            datetime.datetime(2002, 1, 1, 12, 1)).next_update(
                datetime.datetime(2030, 1, 1, 12, 1)))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_last_update(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")
                       ])).next_update(datetime.datetime(2030, 1, 1, 12, 1)))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_no_next_update(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")
                       ])).last_update(datetime.datetime(2030, 1, 1, 12, 1)))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_empty_list(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert crl.last_update == last_update
        assert crl.next_update == next_update

    @pytest.mark.parametrize(
        "extension",
        [
            x509.CRLNumber(13),
            x509.DeltaCRLIndicator(12345678901234567890),
            x509.AuthorityKeyIdentifier(
                b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08"
                b"\xcbY",
                None,
                None,
            ),
            x509.AuthorityInformationAccess([
                x509.AccessDescription(
                    AuthorityInformationAccessOID.CA_ISSUERS,
                    x509.DNSName("cryptography.io"),
                )
            ]),
            x509.IssuerAlternativeName(
                [x509.UniformResourceIdentifier("https://cryptography.io")]),
        ],
    )
    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_extensions(self, backend, extension):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(
                next_update).add_extension(extension, False))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert len(crl.extensions) == 1
        ext = crl.extensions.get_extension_for_class(type(extension))
        assert ext.critical is False
        assert ext.value == extension

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_multiple_extensions_critical(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        ian = x509.IssuerAlternativeName(
            [x509.UniformResourceIdentifier("https://cryptography.io")])
        crl_number = x509.CRLNumber(13)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(
                next_update).add_extension(crl_number,
                                           False).add_extension(ian, True))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert len(crl.extensions) == 2
        ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber)
        assert ext1.critical is False
        assert ext1.value == crl_number
        ext2 = crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName)
        assert ext2.critical is True
        assert ext2.value == ian

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_freshestcrl_extension(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        freshest = x509.FreshestCRL([
            x509.DistributionPoint(
                [x509.UniformResourceIdentifier("http://d.om/delta")],
                None,
                None,
                None,
            )
        ])
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(
                next_update).add_extension(freshest, False))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 0
        assert len(crl.extensions) == 1
        ext1 = crl.extensions.get_extension_for_class(x509.FreshestCRL)
        assert ext1.critical is False
        assert isinstance(ext1.value[0], x509.DistributionPoint)
        uri = ext1.value[0].full_name[0]
        assert isinstance(uri, x509.UniformResourceIdentifier)
        assert uri.value == "http://d.om/delta"

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_add_unsupported_extension(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(
                next_update).add_extension(x509.OCSPNoCheck(), False))
        with pytest.raises(NotImplementedError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_rsa_key_too_small(self, backend):
        private_key = RSA_KEY_512.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA512(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_invalid_hash(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update))

        with pytest.raises(TypeError):
            builder.sign(private_key, object(), backend)

    @pytest.mark.supported(
        only_if=lambda backend: backend.ed25519_supported(),
        skip_message="Requires OpenSSL with Ed25519 support",
    )
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_invalid_hash_ed25519(self, backend):
        private_key = ed25519.Ed25519PrivateKey.generate()
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update))

        with pytest.raises(ValueError):
            builder.sign(private_key, object(), backend)
        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.supported(
        only_if=lambda backend: backend.ed448_supported(),
        skip_message="Requires OpenSSL with Ed448 support",
    )
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_invalid_hash_ed448(self, backend):
        private_key = ed448.Ed448PrivateKey.generate()
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update))

        with pytest.raises(ValueError):
            builder.sign(private_key, object(), backend)
        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.SHA256(), backend)

    @pytest.mark.requires_backend_interface(interface=DSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_dsa_key(self, backend):
        private_key = DSA_KEY_2048.private_key(backend)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0))
        ian = x509.IssuerAlternativeName(
            [x509.UniformResourceIdentifier("https://cryptography.io")])
        revoked_cert0 = (
            x509.RevokedCertificateBuilder().serial_number(2).revocation_date(
                datetime.datetime(2012, 1, 1, 1,
                                  1)).add_extension(invalidity_date,
                                                    False).build(backend))
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update).
                   add_revoked_certificate(revoked_cert0).add_extension(
                       ian, False))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert (crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName).value == ian)
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_ec_key(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())
        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0))
        ian = x509.IssuerAlternativeName(
            [x509.UniformResourceIdentifier("https://cryptography.io")])
        revoked_cert0 = (
            x509.RevokedCertificateBuilder().serial_number(2).revocation_date(
                datetime.datetime(2012, 1, 1, 1,
                                  1)).add_extension(invalidity_date,
                                                    False).build(backend))
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update).
                   add_revoked_certificate(revoked_cert0).add_extension(
                       ian, False))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert (crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName).value == ian)
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.supported(
        only_if=lambda backend: backend.ed25519_supported(),
        skip_message="Requires OpenSSL with Ed25519 support",
    )
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_ed25519_key(self, backend):
        private_key = ed25519.Ed25519PrivateKey.generate()
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0))
        ian = x509.IssuerAlternativeName(
            [x509.UniformResourceIdentifier("https://cryptography.io")])
        revoked_cert0 = (
            x509.RevokedCertificateBuilder().serial_number(2).revocation_date(
                datetime.datetime(2012, 1, 1, 1,
                                  1)).add_extension(invalidity_date,
                                                    False).build(backend))
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update).
                   add_revoked_certificate(revoked_cert0).add_extension(
                       ian, False))

        crl = builder.sign(private_key, None, backend)
        assert crl.signature_hash_algorithm is None
        assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED25519
        assert (crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName).value == ian)
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.supported(
        only_if=lambda backend: backend.ed448_supported(),
        skip_message="Requires OpenSSL with Ed448 support",
    )
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_ed448_key(self, backend):
        private_key = ed448.Ed448PrivateKey.generate()
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0))
        ian = x509.IssuerAlternativeName(
            [x509.UniformResourceIdentifier("https://cryptography.io")])
        revoked_cert0 = (
            x509.RevokedCertificateBuilder().serial_number(2).revocation_date(
                datetime.datetime(2012, 1, 1, 1,
                                  1)).add_extension(invalidity_date,
                                                    False).build(backend))
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(next_update).
                   add_revoked_certificate(revoked_cert0).add_extension(
                       ian, False))

        crl = builder.sign(private_key, None, backend)
        assert crl.signature_hash_algorithm is None
        assert crl.signature_algorithm_oid == SignatureAlgorithmOID.ED448
        assert (crl.extensions.get_extension_for_class(
            x509.IssuerAlternativeName).value == ian)
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 1
        ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date

    @pytest.mark.requires_backend_interface(interface=DSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_dsa_key_sign_md5(self, backend):
        private_key = DSA_KEY_2048.private_key(backend)
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_time).next_update(next_time))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.MD5(), backend)

    @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_ec_key_sign_md5(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())
        private_key = EC_KEY_SECP256R1.private_key(backend)
        last_time = datetime.datetime(2012, 1, 16, 22, 43)
        next_time = datetime.datetime(2022, 1, 17, 6, 43)
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_time).next_update(next_time))

        with pytest.raises(ValueError):
            builder.sign(private_key, hashes.MD5(), backend)

    @pytest.mark.requires_backend_interface(interface=RSABackend)
    @pytest.mark.requires_backend_interface(interface=X509Backend)
    def test_sign_with_revoked_certificates(self, backend):
        private_key = RSA_KEY_2048.private_key(backend)
        last_update = datetime.datetime(2002, 1, 1, 12, 1)
        next_update = datetime.datetime(2030, 1, 1, 12, 1)
        invalidity_date = x509.InvalidityDate(
            datetime.datetime(2002, 1, 1, 0, 0))
        revoked_cert0 = (
            x509.RevokedCertificateBuilder().serial_number(38).revocation_date(
                datetime.datetime(2011, 1, 1, 1, 1)).build(backend))
        revoked_cert1 = (
            x509.RevokedCertificateBuilder().serial_number(2).revocation_date(
                datetime.datetime(2012, 1, 1, 1,
                                  1)).add_extension(invalidity_date,
                                                    False).build(backend))
        builder = (x509.CertificateRevocationListBuilder().issuer_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "cryptography.io CA")
            ])).last_update(last_update).next_update(
                next_update).add_revoked_certificate(
                    revoked_cert0).add_revoked_certificate(revoked_cert1))

        crl = builder.sign(private_key, hashes.SHA256(), backend)
        assert len(crl) == 2
        assert crl.last_update == last_update
        assert crl.next_update == next_update
        assert crl[0].serial_number == revoked_cert0.serial_number
        assert crl[0].revocation_date == revoked_cert0.revocation_date
        assert len(crl[0].extensions) == 0
        assert crl[1].serial_number == revoked_cert1.serial_number
        assert crl[1].revocation_date == revoked_cert1.revocation_date
        assert len(crl[1].extensions) == 1
        ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate)
        assert ext.critical is False
        assert ext.value == invalidity_date
Esempio n. 16
0
def profile_ca(builder, ca_nick, ca):
    now = datetime.datetime.utcnow()

    builder = builder.not_valid_before(now)
    builder = builder.not_valid_after(now + 10 * YEAR)

    crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick))

    builder = builder.add_extension(
        x509.KeyUsage(
            digital_signature=True,
            content_commitment=True,
            key_encipherment=False,
            data_encipherment=False,
            key_agreement=False,
            key_cert_sign=True,
            crl_sign=True,
            encipher_only=False,
            decipher_only=False,
        ),
        critical=True,
    )
    builder = builder.add_extension(
        x509.BasicConstraints(ca=True, path_length=None),
        critical=True,
    )
    builder = builder.add_extension(
        x509.CRLDistributionPoints([
            x509.DistributionPoint(
                full_name=[x509.UniformResourceIdentifier(crl_uri)],
                relative_name=None,
                crl_issuer=None,
                reasons=None,
            ),
        ]),
        critical=False,
    )

    public_key = builder._public_key

    builder = builder.add_extension(
        x509.SubjectKeyIdentifier.from_public_key(public_key),
        critical=False,
    )
    # here we get "ca" only for "ca1/subca" CA
    if not ca:
        builder = builder.add_extension(
            x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key),
            critical=False,
        )
    else:
        ski_ext = ca.cert.extensions.get_extension_for_class(
            x509.SubjectKeyIdentifier)
        auth_keyidentifier = (
            x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier)
        '''
        cryptography < 2.7 accepts only Extension object.
        Remove this workaround when all supported platforms update
        python-cryptography.
        '''
        if (parse_version(cryptography_version) >= parse_version('2.7')):
            extension = auth_keyidentifier(ski_ext.value)
        else:
            extension = auth_keyidentifier(ski_ext)

        builder = builder.add_extension(extension, critical=False)
    return builder
Esempio n. 17
0
def profile_kdc(builder,
                ca_nick,
                ca,
                warp=datetime.timedelta(days=0),
                dns_name=None,
                badusage=False):
    now = datetime.datetime.utcnow() + warp

    builder = builder.not_valid_before(now)
    builder = builder.not_valid_after(now + YEAR)

    crl_uri = u'file://{}.crl'.format(os.path.join(cert_dir, ca_nick))

    builder = builder.add_extension(
        x509.ExtendedKeyUsage([x509.ObjectIdentifier('1.3.6.1.5.2.3.5')]),
        critical=False,
    )

    name = {
        'realm': realm,
        'principalName': {
            'name-type': 2,
            'name-string': ['krbtgt', realm],
        },
    }
    name = native_decoder.decode(name, asn1Spec=KRB5PrincipalName())
    name = der_encoder.encode(name)

    names = [x509.OtherName(x509.ObjectIdentifier('1.3.6.1.5.2.2'), name)]
    if dns_name is not None:
        names += [x509.DNSName(dns_name)]

    builder = builder.add_extension(
        x509.SubjectAlternativeName(names),
        critical=False,
    )

    builder = builder.add_extension(
        x509.CRLDistributionPoints([
            x509.DistributionPoint(
                full_name=[x509.UniformResourceIdentifier(crl_uri)],
                relative_name=None,
                crl_issuer=None,
                reasons=None,
            ),
        ]),
        critical=False,
    )

    if badusage:
        builder = builder.add_extension(x509.KeyUsage(digital_signature=False,
                                                      content_commitment=False,
                                                      key_encipherment=False,
                                                      data_encipherment=True,
                                                      key_agreement=True,
                                                      key_cert_sign=False,
                                                      crl_sign=False,
                                                      encipher_only=False,
                                                      decipher_only=False),
                                        critical=False)

    return builder
Esempio n. 18
0
def parse_general_name(name: ParsableGeneralName) -> x509.GeneralName:
    """Parse a general name from user input.

    This function will do its best to detect the intended type of any value passed to it:

    >>> parse_general_name('example.com')
    <DNSName(value='example.com')>
    >>> parse_general_name('*.example.com')
    <DNSName(value='*.example.com')>
    >>> parse_general_name('.example.com')  # Syntax used e.g. for NameConstraints: All levels of subdomains
    <DNSName(value='.example.com')>
    >>> parse_general_name('*****@*****.**')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('1.2.3.4')
    <IPAddress(value=1.2.3.4)>
    >>> parse_general_name('fd00::1')
    <IPAddress(value=fd00::1)>
    >>> parse_general_name('/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't
    work, an exception will be raised:

    >>> parse_general_name('foo..bar`*123')  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    ValueError: Could not parse name: foo..bar`*123

    If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`:

    >>> parse_general_name('email:[email protected]')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('URI:https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('dirname:/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    Some more exotic values can only be generated by using this prefix:

    >>> parse_general_name('rid:2.5.4.3')
    <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)>
    >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com')
    <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'\\x0c\\x0bexample.com')>

    If you give a prefixed value, this function is less forgiving of any typos and does not catch any
    exceptions:

    >>> parse_general_name('email:foo@bar com')
    Traceback (most recent call last):
        ...
    ValueError: Invalid domain: bar com

    """
    # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements

    if isinstance(name, x509.GeneralName):
        return name
    if not isinstance(name, str):
        raise ValueError(
            f"Cannot parse general name {name}: Must be of type str (was: {type(name).__name__})."
        )

    typ = None
    match = GENERAL_NAME_RE.match(name)
    if match is not None:
        typ, name = match.groups()
        typ = typ.lower()

    if typ is None:
        if re.match("[a-z0-9]{2,}://", name):  # Looks like a URI
            try:
                return x509.UniformResourceIdentifier(encode_url(name))
            except idna.IDNAError:
                pass

        if "@" in name:  # Looks like an Email address
            try:
                return x509.RFC822Name(validate_email(name))
            except ValueError:
                pass

        if name.strip().startswith("/"):  # maybe it's a dirname?
            return x509.DirectoryName(x509_name(name))

        # Try to parse this as IPAddress/Network
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass
        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        # Almost anything passes as DNS name, so this is our default fallback
        try:
            return x509.DNSName(encode_dns(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse name: {name}") from e

    if typ == "uri":
        try:
            return x509.UniformResourceIdentifier(encode_url(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse DNS name in URL: {name}") from e
    elif typ == "email":
        return x509.RFC822Name(validate_email(name))  # validate_email already raises ValueError
    elif typ == "ip":
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass

        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        raise ValueError("Could not parse IP address.")
    elif typ == "rid":
        return x509.RegisteredID(x509.ObjectIdentifier(name))
    elif typ == "othername":
        match = re.match("(.*?);(.*?):(.*)", name)
        if match is not None:
            oid, asn_typ, val = match.groups()

            # Get DER representation of the value for x509.OtherName()
            if asn_typ in ("UTF8", "UTF8String"):
                parsed_value = asn1crypto.core.UTF8String(val).dump()
            elif asn_typ in ("UNIV", "UNIVERSALSTRING"):
                parsed_value = asn1crypto.core.UniversalString(val).dump()
            elif asn_typ in ("IA5", "IA5STRING"):
                parsed_value = asn1crypto.core.IA5String(val).dump()
            elif asn_typ in ("BOOL", "BOOLEAN"):
                # nconf allows for true, y, yes, false, n and no as valid values
                if val.lower() in ("true", "y", "yes"):
                    parsed_value = asn1crypto.core.Boolean(True).dump()
                elif val.lower() in ("false", "n", "no"):
                    parsed_value = asn1crypto.core.Boolean(False).dump()
                else:
                    raise ValueError(
                        f"Unsupported {asn_typ} specification for otherName: {val}: Must be TRUE or FALSE"
                    )
            elif asn_typ in ("UTC", "UTCTIME"):
                parsed_datetime = datetime.strptime(val, "%y%m%d%H%M%SZ").replace(tzinfo=timezone.utc)
                parsed_value = asn1crypto.core.UTCTime(parsed_datetime).dump()
            elif asn_typ in ("GENTIME", "GENERALIZEDTIME"):
                parsed_datetime = datetime.strptime(val, "%Y%m%d%H%M%SZ").replace(tzinfo=timezone.utc)
                parsed_value = asn1crypto.core.GeneralizedTime(parsed_datetime).dump()
            elif asn_typ == "NULL":
                if val:
                    raise ValueError("Invalid NULL specification for otherName: Value must not be present")
                parsed_value = asn1crypto.core.Null().dump()
            elif asn_typ in ("INT", "INTEGER"):
                if val.startswith("0x"):
                    parsed_value = asn1crypto.core.Integer(int(val, 16)).dump()
                else:
                    parsed_value = asn1crypto.core.Integer(int(val)).dump()
            elif asn_typ == "OctetString":
                parsed_value = asn1crypto.core.OctetString(bytes(bytearray.fromhex(val))).dump()
            else:
                raise ValueError(f"Unsupported ASN type in otherName: {asn_typ}")

            # NOTE: cryptography docs are not really clear on what kind of bytes x509.OtherName() expects, but
            #       the test suite explicitly use b"derdata" as value, indicating DER encoded data.
            return x509.OtherName(x509.ObjectIdentifier(oid), parsed_value)

        raise ValueError(f"Incorrect otherName format: {name}")
    elif typ == "dirname":
        return x509.DirectoryName(x509_name(name))
    else:
        try:
            return x509.DNSName(encode_dns(name))
        except idna.IDNAError as e:
            raise ValueError(f"Could not parse DNS name: {name}") from e
Esempio n. 19
0
    def get_crl(self,
                expires=86400,
                encoding=None,
                algorithm=None,
                password=None,
                scope=None,
                counter=None,
                **kwargs):
        """Generate a Certificate Revocation List (CRL).

        The ``full_name`` and ``relative_name`` parameters describe how to retrieve the CRL and are used in
        the `Issuing Distribution Point extension <https://tools.ietf.org/html/rfc5280.html#section-5.2.5>`_.
        The former defaults to the ``crl_url`` field, pass ``None`` to not include the value. At most one of
        the two may be set.

        Parameters
        ----------

        expires : int
            The time in seconds when this CRL expires. Note that you should generate a new CRL until then.
        encoding : :py:class:`~cg:cryptography.hazmat.primitives.serialization.Encoding` or str, optional
            The encoding format for the CRL, passed to :py:func:`~django_ca.utils.parse_encoding`. The default
            value is ``"PEM"``.
        algorithm : :py:class:`~cg:cryptography.hazmat.primitives.hashes.Hash` or str, optional
            The hash algorithm to use, passed to :py:func:`~django_ca.utils.parse_hash_algorithm`. The default
            is to use :ref:`CA_DIGEST_ALGORITHM <settings-ca-digest-algorithm>`.
        password : bytes, optional
            Password used to load the private key of the certificate authority. If not passed, the private key
            is assumed to be unencrypted.
        scope : {None, 'ca', 'user', 'attribute'}, optional
            What to include in the CRL: Use ``"ca"`` to include only revoked certificate authorities and
            ``"user"`` to include only certificates or ``None`` (the default) to include both.
            ``"attribute"`` is reserved for future use and always produces an empty CRL.
        counter : str, optional
            Override the counter-variable for the CRL Number extension. Passing the same key to multiple
            invocations will yield a different sequence then what would ordinarily be returned. The default is
            to use the scope as the key.
        full_name : list of str or :py:class:`~cg:cryptography.x509.GeneralName`, optional
            List of general names to use in the Issuing Distribution Point extension. If not passed, use
            ``crl_url`` if set.
        relative_name : :py:class:`~cg:cryptography.x509.RelativeDistinguishedName`, optional
            Used in Issuing Distribution Point extension, retrieve the CRL relative to the issuer.

        Returns
        -------

        bytes
            The CRL in the requested format.
        """

        if scope is not None and scope not in ['ca', 'user', 'attribute']:
            raise ValueError(
                'Scope must be either None, "ca", "user" or "attribute"')
        encoding = parse_encoding(encoding)

        now = now_builder = timezone.now()
        algorithm = parse_hash_algorithm(algorithm)

        if timezone.is_aware(now_builder):
            now_builder = timezone.make_naive(now, pytz.utc)
        else:
            now_builder = datetime.utcnow()

        builder = x509.CertificateRevocationListBuilder()
        builder = builder.issuer_name(self.x509.subject)
        builder = builder.last_update(now_builder)
        builder = builder.next_update(now_builder + timedelta(seconds=expires))

        if 'full_name' in kwargs:
            full_name = kwargs['full_name']
            full_name = [parse_general_name(n) for n in full_name]
        elif self.crl_url:
            crl_url = [url.strip() for url in self.crl_url.split()]
            full_name = [x509.UniformResourceIdentifier(c) for c in crl_url]
        else:
            full_name = None

        # Keyword arguments for the IssuingDistributionPoint extension
        idp_kwargs = {
            'only_contains_ca_certs': False,
            'only_contains_user_certs': False,
            'indirect_crl': False,
            'only_contains_attribute_certs': False,
            'only_some_reasons': None,
            'full_name': full_name,
            'relative_name': kwargs.get('relative_name'),
        }

        ca_qs = self.children.filter(expires__gt=now).revoked()
        cert_qs = self.certificate_set.filter(expires__gt=now).revoked()

        if scope == 'ca':
            certs = ca_qs
            idp_kwargs['only_contains_ca_certs'] = True
        elif scope == 'user':
            certs = cert_qs
            idp_kwargs['only_contains_user_certs'] = True
        elif scope == 'attribute':
            # sorry, nothing we support right now
            certs = []
            idp_kwargs['only_contains_attribute_certs'] = True
        else:
            certs = itertools.chain(ca_qs, cert_qs)

        for cert in certs:
            builder = builder.add_revoked_certificate(cert.get_revocation())

        # We can only add the IDP extension if one of these properties is set, see RFC 5280, 5.2.5.
        add_idp = idp_kwargs['only_contains_attribute_certs'] or idp_kwargs['only_contains_user_certs'] \
            or idp_kwargs['only_contains_ca_certs'] or idp_kwargs['full_name'] or idp_kwargs['relative_name']

        if add_idp:  # pragma: no branch
            builder = builder.add_extension(
                x509.IssuingDistributionPoint(**idp_kwargs), critical=True)

        # Add AuthorityKeyIdentifier from CA if present
        try:
            aki = self.x509.extensions.get_extension_for_oid(
                ExtensionOID.AUTHORITY_KEY_IDENTIFIER)
            builder = builder.add_extension(aki.value, critical=aki.critical)
        except x509.ExtensionNotFound:
            pass

        # Add the CRLNumber extension (RFC 5280, 5.2.3)
        if counter is None:
            counter = scope or 'all'
        crl_number_data = json.loads(self.crl_number)
        crl_number = int(crl_number_data['scope'].get(counter, 0))
        builder = builder.add_extension(x509.CRLNumber(crl_number=crl_number),
                                        critical=False)

        # increase crl_number for the given scope and save
        crl_number_data['scope'][counter] = crl_number + 1
        self.crl_number = json.dumps(crl_number_data)
        self.save()

        crl = builder.sign(private_key=self.key(password),
                           algorithm=algorithm,
                           backend=default_backend())
        return crl.public_bytes(encoding)
Esempio n. 20
0
def sign_csr(key,
             csr,
             caprofile,
             valid=30,
             isca=False,
             cacert=None,
             aia_issuers=None,
             ocsp_responders=None):
    global SETTINGS

    one_day = datetime.timedelta(1, 0, 0)

    builder = x509.CertificateBuilder()
    builder = builder.subject_name(csr.subject)

    if not cacert:
        builder = builder.issuer_name(x509.Name(construct_sn(caprofile)))
    else:
        builder = builder.issuer_name(cacert.subject)

    builder = builder.not_valid_before(datetime.datetime.today() - one_day)
    builder = builder.not_valid_after(datetime.datetime.today() +
                                      (one_day * valid))
    #builder = builder.serial_number(x509.random_serial_number()) # too new to some systems
    builder = builder.serial_number(
        int.from_bytes(os.urandom(10), byteorder="big"))
    builder = builder.public_key(csr.public_key())

    builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(
        csr.public_key()),
                                    critical=False)

    # more info about issuer

    has_ski = False
    try:
        if cacert:
            ski = cacert.extensions.get_extension_for_class(
                x509.SubjectKeyIdentifier)
            builder = builder.add_extension(
                x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
                    ski),
                critical=False)
            has_ski = True
    except AttributeError:
        # this is workaround for older versions of python cryptography, not having from_issuer_subject_key_identifier
        # -> which throws AttributeError
        has_ski = False
    except x509.extensions.ExtensionNotFound:
        has_ski = False

    if not has_ski:
        builder = builder.add_extension(
            x509.AuthorityKeyIdentifier.from_issuer_public_key(
                key.public_key()),
            critical=False)

    all_aias = []
    if aia_issuers:
        for loc in aia_issuers:
            aia_uri = x509.AccessDescription(
                AuthorityInformationAccessOID.CA_ISSUERS,
                x509.UniformResourceIdentifier(loc))
            all_aias.append(aia_uri)

    if ocsp_responders:
        for resp in ocsp_responders:
            aia_uri = x509.AccessDescription(
                AuthorityInformationAccessOID.OCSP,
                x509.UniformResourceIdentifier(resp))
            all_aias.append(aia_uri)

    if all_aias:
        alist = x509.AuthorityInformationAccess(all_aias)
        builder = builder.add_extension(alist, critical=False)

    print("sign CSR: == extensions ==")
    for e in csr.extensions:
        if isinstance(e.value, x509.BasicConstraints):
            print("sign CSR: %s" % (e.oid, ))

            if e.value.ca:
                print("           CA=TRUE requested")

                if isca and not SETTINGS["ca"]["settings"]["grant_ca"]:
                    print("           not allowed but overridden")
                elif not SETTINGS["ca"]["settings"]["grant_ca"]:
                    print("           not allowed by rule")
                    continue
                else:
                    print("           allowed by rule")

        builder = builder.add_extension(e.value, e.critical)

    certificate = builder.sign(private_key=key,
                               algorithm=hashes.SHA256(),
                               backend=default_backend())
    return certificate
Esempio n. 21
0
 def get_idp_full_name(self, ca):
     """Get the IDP full name for `ca`."""
     crl_url = [url.strip() for url in ca.crl_url.split()]
     return [x509.UniformResourceIdentifier(c) for c in crl_url] or None
Esempio n. 22
0
 def get_idp_full_name(
     self, ca: CertificateAuthority
 ) -> typing.Optional[typing.List[x509.UniformResourceIdentifier]]:
     """Get the IDP full name for `ca`."""
     crl_url = [url.strip() for url in ca.crl_url.split()]
     return [x509.UniformResourceIdentifier(c) for c in crl_url] or None
Esempio n. 23
0
def parse_general_name(name):
    """Parse a general name from user input.

    This function will do its best to detect the intended type of any value passed to it:

    >>> parse_general_name('example.com')
    <DNSName(value='example.com')>
    >>> parse_general_name('*.example.com')
    <DNSName(value='*.example.com')>
    >>> parse_general_name('.example.com')  # Syntax used e.g. for NameConstraints: All levels of subdomains
    <DNSName(value='.example.com')>
    >>> parse_general_name('*****@*****.**')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('1.2.3.4')
    <IPAddress(value=1.2.3.4)>
    >>> parse_general_name('fd00::1')
    <IPAddress(value=fd00::1)>
    >>> parse_general_name('/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    The default fallback is to assume a :py:class:`~cg:cryptography.x509.DNSName`. If this doesn't
    work, an exception will be raised:

    >>> parse_general_name('foo..bar`*123')  # doctest: +ELLIPSIS
    Traceback (most recent call last):
        ...
    idna.core.IDNAError: ...

    If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`:

    >>> parse_general_name('email:[email protected]')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('URI:https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('dirname:/CN=example.com')
    <DirectoryName(value=<Name(CN=example.com)>)>

    Some more exotic values can only be generated by using this prefix:

    >>> parse_general_name('rid:2.5.4.3')
    <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)>
    >>> parse_general_name('otherName:2.5.4.3;UTF8:example.com')
    <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')>

    If you give a prefixed value, this function is less forgiving of any typos and does not catch any
    exceptions:

    >>> parse_general_name('email:foo@bar com')
    Traceback (most recent call last):
        ...
    ValueError: Invalid domain: bar com

    """
    name = force_text(name)
    typ = None
    match = GENERAL_NAME_RE.match(name)
    if match is not None:
        typ, name = match.groups()
        typ = typ.lower()

    if typ is None:
        if re.match('[a-z0-9]{2,}://', name):  # Looks like a URI
            try:
                return x509.UniformResourceIdentifier(name)
            except Exception:  # pragma: no cover - this really accepts anything
                pass

        if '@' in name:  # Looks like an Email address
            try:
                return x509.RFC822Name(validate_email(name))
            except Exception:
                pass

        if name.strip().startswith('/'):  # maybe it's a dirname?
            return x509.DirectoryName(x509_name(name))

        # Try to parse this as IPAddress/Network
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass
        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        # Try to encode as domain name. DNSName() does not validate the domain name, but this check will fail.
        if name.startswith('*.'):
            idna.encode(name[2:])
        elif name.startswith('.'):
            idna.encode(name[1:])
        else:
            idna.encode(name)

        # Almost anything passes as DNS name, so this is our default fallback
        return x509.DNSName(name)

    if typ == 'uri':
        return x509.UniformResourceIdentifier(name)
    elif typ == 'email':
        return x509.RFC822Name(validate_email(name))
    elif typ == 'ip':
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass

        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        raise ValueError('Could not parse IP address.')
    elif typ == 'rid':
        return x509.RegisteredID(x509.ObjectIdentifier(name))
    elif typ == 'othername':
        regex = "(.*);(.*):(.*)"
        if re.match(regex, name) is not None:
            oid, asn_typ, val = re.match(regex, name).groups()
            oid = x509.ObjectIdentifier(oid)
            if asn_typ == 'UTF8':
                val = val.encode('utf-8')
            elif asn_typ == 'OctetString':
                val = bytes(bytearray.fromhex(val))
                val = OctetString(val).dump()
            else:
                raise ValueError('Unsupported ASN type in otherName: %s' %
                                 asn_typ)
            val = force_bytes(val)
            return x509.OtherName(oid, val)
        else:
            raise ValueError('Incorrect otherName format: %s' % name)
    elif typ == 'dirname':
        return x509.DirectoryName(x509_name(name))
    else:
        # Try to encode the domain name. DNSName() does not validate the domain name, but this
        # check will fail.
        if name.startswith('*.'):
            idna.encode(name[2:])
        elif name.startswith('.'):
            idna.encode(name[1:])
        else:
            idna.encode(name)

        return x509.DNSName(name)
Esempio n. 24
0
def parse_general_name(name):
    """Parse a general name from user input.

    This function will do its best to detect the intended type of any value passed to it:

    >>> parse_general_name('example.com')
    <DNSName(value='example.com')>
    >>> parse_general_name('*.example.com')
    <DNSName(value='*.example.com')>
    >>> parse_general_name('*****@*****.**')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('1.2.3.4')
    <IPAddress(value=1.2.3.4)>
    >>> parse_general_name('fd00::1')
    <IPAddress(value=fd00::1)>
    >>> parse_general_name('/CN=example.com')  # doctest: +NORMALIZE_WHITESPACE
    <DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>,
                                               value='example.com')>])>)>

    The default fallback is to assume a :py:class:`~cryptography:cryptography.x509.DNSName`. If this doesn't
    work, an exception will be raised:

    >>> parse_general_name('foo..bar`*123')
    Traceback (most recent call last):
        ...
    idna.core.IDNAError: The label b'' is not a valid A-label
    >>> parse_general_name('foo bar')
    Traceback (most recent call last):
        ...
    idna.core.IDNAError: The label b'foo bar' is not a valid A-label

    If you want to override detection, you can prefix the name to match :py:const:`GENERAL_NAME_RE`:

    >>> parse_general_name('email:[email protected]')
    <RFC822Name(value='*****@*****.**')>
    >>> parse_general_name('URI:https://example.com')
    <UniformResourceIdentifier(value='https://example.com')>
    >>> parse_general_name('dirname:/CN=example.com')  # doctest: +NORMALIZE_WHITESPACE
    <DirectoryName(value=<Name([<NameAttribute(oid=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>,
                                               value='example.com')>])>)>

    Some more exotic values can only be generated by using this prefix:

    >>> parse_general_name('rid:2.5.4.3')
    <RegisteredID(value=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>)>
    >>> parse_general_name('otherName:2.5.4.3,example.com')
    <OtherName(type_id=<ObjectIdentifier(oid=2.5.4.3, name=commonName)>, value=b'example.com')>

    If you give a prefixed value, this function is less forgiving of any typos and does not catch any
    exceptions:

    >>> parse_general_name('email:foo@bar com')
    Traceback (most recent call last):
        ...
    ValueError: Invalid domain: bar com

    """
    name = force_text(name)
    typ = None
    match = GENERAL_NAME_RE.match(name)
    if match is not None:
        typ, name = match.groups()
        typ = typ.lower()

    if typ is None:
        if re.match('[a-z0-9]{2,}://', name):  # Looks like a URI
            try:
                return x509.UniformResourceIdentifier(name)
            except Exception:  # pragma: no cover - this really accepts anything
                pass

        if '@' in name:  # Looks like an Email address
            try:
                return x509.RFC822Name(validate_email(name))
            except Exception:
                pass

        if name.strip().startswith('/'):  # maybe it's a dirname?
            return x509.DirectoryName(x509_name(name))

        # Try to parse this as IPAddress/Network
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass
        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        # Try to encode the domain name. DNSName() does not validate the domain name, but this
        # check will fail.
        if name.startswith('*.'):
            idna.encode(name[2:])
        else:
            idna.encode(name)

        # Almost anything passes as DNS name, so this is our default fallback
        return x509.DNSName(name)

    if typ == 'uri':
        return x509.UniformResourceIdentifier(name)
    elif typ == 'email':
        return x509.RFC822Name(validate_email(name))
    elif typ == 'ip':
        try:
            return x509.IPAddress(ip_address(name))
        except ValueError:
            pass

        try:
            return x509.IPAddress(ip_network(name))
        except ValueError:
            pass

        raise ValueError('Could not parse IP address.')
    elif typ == 'rid':
        return x509.RegisteredID(x509.ObjectIdentifier(name))
    elif typ == 'othername':
        type_id, value = name.split(',', 1)
        type_id = x509.ObjectIdentifier(type_id)
        value = force_bytes(value)
        return x509.OtherName(type_id, value)
    elif typ == 'dirname':
        return x509.DirectoryName(x509_name(name))
    else:
        # Try to encode the domain name. DNSName() does not validate the domain name, but this
        # check will fail.
        if name.startswith('*.'):
            idna.encode(name[2:])
        else:
            idna.encode(name)

        return x509.DNSName(name)
Esempio n. 25
0
    def build_csr(self):
        if not self.private_key:
            self._gen_key()

        csr_builder = x509.CertificateSigningRequestBuilder()
        subject = [
            x509.NameAttribute(
                NameOID.COMMON_NAME,
                self.common_name,
            )
        ]
        if self.locality:
            subject.append(
                x509.NameAttribute(NameOID.LOCALITY_NAME, self.locality))
        if self.province:
            subject.append(
                x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME,
                                   self.province))
        if self.country:
            subject.append(
                x509.NameAttribute(NameOID.COUNTRY_NAME, self.country))
        if self.organization:
            subject.append(
                x509.NameAttribute(NameOID.ORGANIZATION_NAME,
                                   self.organization))
        if self.organizational_unit:
            if isinstance(self.organizational_unit, string_types):
                subject.append(
                    x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                                       self.organizational_unit))
            elif isinstance(self.organizational_unit, list):
                for u in self.organizational_unit:
                    subject.append(
                        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,
                                           u))

        csr_builder = csr_builder.subject_name(x509.Name(subject))

        alt_names = []
        if self.ip_addresses:
            for ip in self.ip_addresses:
                alt_names.append(x509.IPAddress(ipaddress.IPv4Address(ip)))

        if self.san_dns:
            for ns in self.san_dns:
                alt_names.append(x509.DNSName(ns))

        if self.email_addresses:
            for mail in self.email_addresses:
                alt_names.append(x509.RFC822Name(mail))

        if self.uniform_resource_identifiers:
            for uri in self.uniform_resource_identifiers:
                alt_names.append(x509.UniformResourceIdentifier(uri))

        if self.user_principal_names:
            for upn in self.user_principal_names:
                # Python cryptography library doesn't include
                # OID for UPN in any of the OID constants
                # See http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3
                # and https://docs.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-certificate-requirements-and-enumeration
                UPNOID = x509.ObjectIdentifier('1.3.6.1.4.1.311.20.2.3')
                # x509 library expects DER encoded string, so encode UPN into bytes
                # with ASN1 syntax for UTF8String, which is a type/length/value encoding
                # per https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-utf8string
                # Construct the array of bytes (note bytes are strings in python2)
                # by inserting the header consisting of the tag '0x0C' followed by the length of the upn
                if sys.version_info > (3, 0):
                    # For python3, since we have native bytes available we'll convert the string into bytes.
                    # However, for cases when this method is called with inputs extracted from existing certificates
                    # & csrs, the input will already be encoded as bytes. So we'll check the input, if it is a
                    # string we'll convert it into a bytes object then insert our header. Otherwise, we'll just
                    # insert the header in the passed in bytes.
                    if isinstance(upn, str):
                        bupn = bytes(upn, 'utf-8')
                    else:
                        bupn = upn
                    values = [12, len(upn)]
                    header = bytes(values)
                    data = header + bupn
                    alt_names.append(x509.OtherName(UPNOID, data))
                else:
                    # For python2, since there is no native bytes method, we'll use the bytes method
                    # in the future module to convert the string we constructed into bytes.
                    bupn = ''.join(chr(x) for x in [12, len(upn)])
                    bupn = bupn + str(upn)
                    alt_names.append(
                        x509.OtherName(UPNOID, bytes(bupn, 'utf-8')))

        csr_builder = csr_builder.add_extension(
            x509.SubjectAlternativeName(alt_names), critical=False)

        csr_builder = csr_builder.sign(self.private_key, hashes.SHA256(),
                                       default_backend())
        self.csr = csr_builder.public_bytes(
            serialization.Encoding.PEM).decode()
        return
Esempio n. 26
0
    def generate_cert(self, csr, lifetime, fingerprint):
        """Generate a Certificate from a CSR.

        Args:
            csr: The CSR object
            lifetime: The lifetime of the certificate in seconds
            fingerprint: The fingerprint of the signer for the CSR.

        Raises:
            CertProcessorNotAdminUserError: When an admin request is made
            without and admin key
            CertProcessorInvalidSignatureError: When an invalid user attempts
            to sign a request for a certificate

        Returns:
            The certificates public bytes
        """
        ca_pkey = self.get_ca_key()
        ca_cert = self.get_ca_cert(ca_pkey)
        now = datetime.datetime.utcnow()
        lifetime_delta = now + datetime.timedelta(seconds=int(lifetime))
        alts = []
        is_admin = self.is_admin(fingerprint)
        logger.info(f"generate_cert: getting gpg key for {fingerprint}")
        user_gpg_key = self.get_gpg_key_by_fingerprint(fingerprint, is_admin)
        if user_gpg_key is None:
            raise CertProcessorNoPGPKeyFoundError()
        email_in_key = self.check_subject_against_key(csr.subject,
                                                      user_gpg_key)
        if not email_in_key and not is_admin:
            raise CertProcessorNotAdminUserError()
        for alt in self.config.get("ca", "alternate_name").split(","):
            alts.append(x509.DNSName("{}".format(alt)))
        cert = (x509.CertificateBuilder().subject_name(
            csr.subject).issuer_name(ca_cert.subject).public_key(
                csr.public_key()).serial_number(
                    uuid.uuid4().int).not_valid_before(now).not_valid_after(
                        lifetime_delta))
        if len(alts) > 0:
            cert = cert.add_extension(x509.SubjectAlternativeName(alts),
                                      critical=False)
        crl_dp = x509.DistributionPoint(
            [
                x509.UniformResourceIdentifier(
                    "{protocol}://{server_url}/crl".format(
                        protocol=self.PROTOCOL, server_url=self.SERVER_URL))
            ],
            relative_name=None,
            reasons=None,
            crl_issuer=None,
        )
        cert = cert.add_extension(x509.CRLDistributionPoints([crl_dp]),
                                  critical=False)

        logger.info(f"generate_cert: Signing certificate for {fingerprint}")
        cert = cert.sign(
            private_key=ca_pkey,
            algorithm=hashes.SHA256(),
            backend=default_backend(),
        )
        try:
            logger.info(f"generate_cert: saving certificate for {fingerprint}")
            self.storage.save_cert(cert, fingerprint)
        except StorageEngineCertificateConflict:
            logger.info(
                f"generate_cert: updating certificate for {fingerprint}")
            cert = self.update_cert(csr, lifetime)
        return cert.public_bytes(serialization.Encoding.PEM)