Example #1
0
def create_client_certificate(client_csr: x509.CertificateSigningRequest,
                              serial_number: int, ca_key: rsa.RSAPrivateKey,
                              ca_cert: x509.Certificate) -> x509.Certificate:
    """
    Creates client (slave) certificate signed by CA.

    Parameters
    ----------
    client_csr
        Client Certificate Signing Request.
    serial_number
        Client certificate serial number.
        Number must be unique within CA and less than 20 bytes.
    ca_key
        Certificate Authority private key.
    ca_cert
        Certificate Authority certificate.

    Returns
    -------
        Client certificate.
    """

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

    builder = x509.CertificateBuilder(
        issuer_name=ca_cert.subject,
        subject_name=client_csr.subject,
        public_key=client_csr.public_key(),
        serial_number=serial_number,
        not_valid_before=datetime.datetime.today() - one_day,
        not_valid_after=datetime.datetime.today() + ten_days,
    )

    builder = builder.add_extension(x509.BasicConstraints(False,
                                                          path_length=None),
                                    critical=False)
    builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(
        client_csr.public_key()),
                                    critical=False)
    builder = builder.add_extension(
        x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
            ca_cert.extensions.get_extension_for_class(
                x509.SubjectKeyIdentifier)),
        critical=False)

    cert = builder.sign(ca_key, hashes.SHA256(), backend=default_backend())
    return cert
Example #2
0
    def from_crypto(cls, csr: x509.CertificateSigningRequest):
        # type: (type, x509.CertificateSigningRequest, CertificateType) -> Certificate
        m = cls()
        m.pem_data = csr.public_bytes(serialization.Encoding.PEM)
        m.not_before = datetime.datetime.utcnow()
        m.not_after = datetime.datetime.utcnow() + datetime.timedelta(days=700)
        h = hashes.Hash(hashes.SHA256(), default_backend())
        h.update(m.pem_data)
        m.fingerprint = h.finalize()

        m.discriminator = CertificateType.CSR.value

        subject: x509.Name = csr.subject
        cns = subject.get_attributes_for_oid(NameOID.COMMON_NAME)
        if cns is not None:
            m.x509_cn = cns[0].value

        return m
Example #3
0
    def sign(self, request: x509.CertificateSigningRequest) -> x509.Certificate:
        """
        Sign a Certificate Signing Request.

        The issued certificate is automatically persisted to the database.

        Args:
            request (x509.CertificateSigningRequest): The CSR object (cryptography) not the SQLAlchemy model.

        Returns:
            x509.Certificate: A signed certificate
        """
        b = x509.CertificateBuilder()
        self.serial += 1

        private_key_model = self.rsa_private_key
        private_key = private_key_model.to_crypto()
        # ca_certificate_model = self.certificate
        # ca_certificate = ca_certificate_model.to_crypto()

        name = x509.Name([
            x509.NameAttribute(NameOID.COMMON_NAME, self.common_name),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'commandment')
        ])

        cert = b.not_valid_before(
            datetime.datetime.utcnow()
        ).not_valid_after(
            datetime.datetime.utcnow() + datetime.timedelta(days=self.validity_period)
        ).serial_number(
            self.serial
        ).issuer_name(
            name
        ).subject_name(
            request.subject
        ).public_key(
            request.public_key()
        ).sign(private_key, hashes.SHA256(), default_backend())

        # cert_model = DeviceIdentityCertificate().from_crypto(cert)
        # db.session.add(cert_model)
        # db.session.commit()

        return cert
Example #4
0
def sign_csr(
    csr: CertificateSigningRequest, ca_cert: Certificate,
    key: EllipticCurvePrivateKey, expiration_date: date,
    custom_extensions: Iterable[Union[KeyUsage, UnrecognizedExtension,
                                      BasicConstraints]]
) -> Certificate:
    """
    Sign a CSR with CA credentials.
    :param csr: the CSR
    :param ca_cert: the CA certificate
    :param key: the CA private key
    :param expiration_date: expiration date
    :param custom_extensions: custom extensions to be added to the certificate
    :return: a certificate object
    """
    issuer = ca_cert.subject
    now = datetime.utcnow()
    cert_builder = CertificateBuilder().issuer_name(issuer).subject_name(
        csr.subject).public_key(csr.public_key()).serial_number(
            x509.random_serial_number()).not_valid_before(now).not_valid_after(
                datetime.combine(expiration_date, time(), None)).add_extension(
                    extension=AuthorityKeyIdentifier.from_issuer_public_key(
                        ca_cert.public_key()),
                    critical=False)
    try:
        cert_builder = cert_builder.add_extension(
            csr.extensions.get_extension_for_class(
                SubjectAlternativeName).value,
            critical=False)
    except ExtensionNotFound:
        pass
    for extension in custom_extensions:
        if isinstance(extension, UnrecognizedExtension):
            critical = False
        else:
            critical = True
        # pyre-fixme[6]: Expected `ExtensionType` for 1st param but got
        #  `Union[BasicConstraints, KeyUsage, UnrecognizedExtension]`.
        cert_builder = cert_builder.add_extension(extension, critical=critical)
    return cert_builder.sign(key, SHA256(), backends.default_backend())
Example #5
0
    def sign(self, csr: x509.CertificateSigningRequest) -> x509.Certificate:
        """Sign a certificate signing request.

        Args:
            csr (x509.CertificateSigningRequest): The certificate signing request
        Returns:
            Instance of x509.Certificate
        """
        serial = self.serial + 1
        builder = x509.CertificateBuilder()
        cert = builder.subject_name(csr.subject).issuer_name(
            self.certificate.subject).not_valid_before(
                datetime.datetime.utcnow()).not_valid_after(
                    datetime.datetime.utcnow() + datetime.timedelta(
                        days=365)).serial_number(serial).public_key(
                            csr.public_key()).sign(self.private_key,
                                                   hashes.SHA256(),
                                                   default_backend())

        self._storage.save_issued_certificate(cert)
        self.serial = serial

        return cert
Example #6
0
def submit_mdmcert_request(email: str,
                           csr: x509.CertificateSigningRequest,
                           encrypt_with: x509.Certificate,
                           api_key: str = MDMCERT_API_KEY) -> Dict:
    """Submit a CSR signing request to mdmcert.download.

    Args:
          email (str): Your registered mdmcert.download e-mail address.
          api_key (str): Your registered mdmcert.download API key.
          csr (cryptography.x509.CertificateSigningRequest): The MDM CSR to sign.
          encrypt_with (cryptography.x509.Certificate): The certificate which will be used to encrypt the response.

    Returns:
          dict: Response from the mdmcert.download service.
    """
    base64_csr = b64encode(csr.public_bytes(serialization.Encoding.PEM))
    base64_recipient = b64encode(
        encrypt_with.public_bytes(serialization.Encoding.PEM))

    mdmcert_dict = {
        'csr': base64_csr.decode('utf8'),
        'email': email,
        'key': api_key,
        'encrypt': base64_recipient.decode('utf8'),
    }

    req = urllib.request.Request(MDMCERT_REQ_URL,
                                 json.dumps(mdmcert_dict).encode('utf8'), {
                                     'Content-Type': 'application/json',
                                     'User-Agent': 'coMmanDMent/0.1'
                                 })

    f = urllib.request.urlopen(req)
    resp = f.read()
    f.close()

    return json.loads(resp)
Example #7
0
 def get_certificate_signing_request_content(cls, csr: x509.CertificateSigningRequest) -> str:
     public_bytes = csr.public_bytes(serialization.Encoding.PEM)
     return cls._str(public_bytes)
Example #8
0
 def process_bind_param(self, value: x509.CertificateSigningRequest,
                        dialect):
     return value.public_bytes(serialization.Encoding.DER)
Example #9
0
    def create_cert(
        self,
        ca: "CertificateAuthority",
        csr: x509.CertificateSigningRequest,
        subject: Optional[Subject] = None,
        expires: Expires = None,
        algorithm: typing.Optional[HashAlgorithm] = None,
        extensions: Optional[Union[Dict[str, Union[
            "x509.Extension[x509.ExtensionType]", Extension[Any, Any, Any]]],
                                   Iterable[Union[Extension[Any, Any,
                                                            Any]]], ]] = None,
        cn_in_san: Optional[bool] = None,
        add_crl_url: Optional[bool] = None,
        add_ocsp_url: Optional[bool] = None,
        add_issuer_url: Optional[bool] = None,
        add_issuer_alternative_name: Optional[bool] = None,
        password: Optional[Union[str, bytes]] = None,
    ) -> x509.Certificate:
        """Create a x509 certificate based on this profile, the passed CA and input parameters.

        .. deprecated:: 1.18.0

           Since ``django-ca==1.18.0``, passing unparsed values is deprecated and will be removed in
           ``django-ca==1.20.0``. This affects the following parameters:

           * Passing a ``str`` as `algorithm`.

        This function is the core function used to create x509 certificates. In it's simplest form, you only
        need to pass a ca, a CSR and a subject to get a valid certificate::

            >>> profile = get_profile('webserver')
            >>> profile.create_cert(ca, csr, subject='/CN=example.com')  # doctest: +ELLIPSIS
            <Certificate(subject=<Name(...,CN=example.com)>, ...)>

        The function will add CRL, OCSP, Issuer and IssuerAlternativeName URLs based on the CA if the profile
        has the *add_crl_url*, *add_ocsp_url* and *add_issuer_url* and *add_issuer_alternative_name* values
        set. Parameters to this function with the same name allow you override this behavior.

        The function allows you to override profile values using the *expires* and *algorithm* values. You can
        pass additional *extensions* as a list, which will override any extensions from the profile, but the
        CA passed will append to these extensions unless the *add_...* values are ``False``.

        Parameters
        ----------

        ca : :py:class:`~django_ca.models.CertificateAuthority`
            The CA to sign the certificate with.
        csr : :py:class:`~cg:cryptography.x509.CertificateSigningRequest`
            The CSR for the certificate.
        subject : dict or str or :py:class:`~django_ca.subject.Subject`
            Update the subject string, e.g. ``"/CN=example.com"`` or ``Subject("/CN=example.com")``. The
            values from the passed subject will update the profiles subject.
        expires : int or datetime or timedelta, optional
            Override when this certificate will expire.
        algorithm : :py:class:`~cg:cryptography.hazmat.primitives.hashes.HashAlgorithm`, optional
            Override the hash algorithm used when signing the certificate.
        extensions : list or dict of :py:class:`~django_ca.extensions.base.Extension`
            List or dict of additional extensions to set for the certificate. Note that values from the CA
            might update the passed extensions: For example, if you pass an
            :py:class:`~django_ca.extensions.IssuerAlternativeName` extension, *add_issuer_alternative_name*
            is ``True`` and the passed CA has an IssuerAlternativeName set, that value will be appended to the
            extension you pass here. If you pass a dict with a ``None`` value, that extension will be removed
            from the profile.
        cn_in_san : bool, optional
            Override if the CommonName should be added as an SubjectAlternativeName. If not passed, the value
            set in the profile is used.
        add_crl_url : bool, optional
            Override if any CRL URLs from the CA should be added to the CA. If not passed, the value set in
            the profile is used.
        add_ocsp_url : bool, optional
            Override if any OCSP URLs from the CA should be added to the CA. If not passed, the value set in
            the profile is used.
        add_issuer_url : bool, optional
            Override if any Issuer URLs from the CA should be added to the CA. If not passed, the value set in
            the profile is used.
        add_issuer_alternative_name : bool, optional
            Override if any IssuerAlternativeNames from the CA should be added to the CA. If not passed, the
            value set in the profile is used.
        password: bytes or str, optional
            The password to the private key of the CA.

        Returns
        -------

        cryptography.x509.Certificate
            The signed certificate.
        """
        # pylint: disable=too-many-locals,too-many-arguments

        # Compute default values
        if extensions is None:
            extensions_update: Dict[str, Extension[Any, Any, Any]] = {}
        elif isinstance(extensions, dict):
            extensions_update = {
                k: self._parse_extension_value(k, v)
                for k, v in extensions.items() if v is not None
            }
        else:
            extensions_update = {e.key: e for e in extensions}

        # Get overrides values from profile if not passed as parameter
        if cn_in_san is None:
            cn_in_san = self.cn_in_san
        if add_crl_url is None:
            add_crl_url = self.add_crl_url
        if add_ocsp_url is None:
            add_ocsp_url = self.add_ocsp_url
        if add_issuer_url is None:
            add_issuer_url = self.add_issuer_url
        if add_issuer_alternative_name is None:
            add_issuer_alternative_name = self.add_issuer_alternative_name

        cert_extensions = deepcopy(self.extensions)
        cert_extensions.update(extensions_update)
        cert_extensions = {
            k: v
            for k, v in cert_extensions.items() if v is not None
        }
        cert_subject = deepcopy(self.subject)

        # If extensions is a dict, filter any extenions where the value is None
        if isinstance(extensions, dict):
            for key in {
                    k
                    for k, v in extensions.items()
                    if v is None and k in cert_extensions
            }:
                del cert_extensions[key]

        issuer_name = self._update_from_ca(
            ca,
            cert_extensions,
            add_crl_url=add_crl_url,
            add_ocsp_url=add_ocsp_url,
            add_issuer_url=add_issuer_url,
            add_issuer_alternative_name=add_issuer_alternative_name,
        )

        if not isinstance(subject, Subject):
            subject = Subject(subject)  # NOTE: also accepts None
        cert_subject.update(subject)

        if expires is None:
            expires = self.expires
        if algorithm is None:
            algorithm = self.algorithm
        elif isinstance(algorithm, str):
            warnings.warn(
                "Passing a str as algorithm is deprecated and will be removed in django-ca==1.20.0.",
                category=RemovedInDjangoCA120Warning,
                stacklevel=2,
            )
            algorithm = parse_hash_algorithm(algorithm)

        # Make sure that expires is a fixed timestamp
        expires = parse_expires(expires)

        # Finally, update SAN with the current CN, if set and requested
        self._update_san_from_cn(cn_in_san,
                                 subject=cert_subject,
                                 extensions=cert_extensions)

        if not subject.get("CN") and (
                SubjectAlternativeName.key not in extensions_update or
                not cast(SubjectAlternativeName,
                         extensions_update[SubjectAlternativeName.key]).value):
            raise ValueError(
                "Must name at least a CN or a subjectAlternativeName.")

        pre_issue_cert.send(
            sender=self.__class__,
            ca=ca,
            csr=csr,
            expires=expires,
            algorithm=algorithm,
            subject=cert_subject,
            extensions=cert_extensions,
            password=password,
        )

        public_key = csr.public_key()
        builder = get_cert_builder(expires)
        builder = builder.public_key(public_key)
        builder = builder.issuer_name(issuer_name)
        builder = builder.subject_name(cert_subject.name)

        for _key, extension in cert_extensions.items():
            builder = builder.add_extension(*extension.for_builder())

        # Add the SubjectKeyIdentifier
        if SubjectKeyIdentifier.key not in cert_extensions:
            builder = builder.add_extension(
                x509.SubjectKeyIdentifier.from_public_key(public_key),
                critical=False)

        return builder.sign(private_key=ca.key(password),
                            algorithm=algorithm,
                            backend=default_backend())
Example #10
0
def serialize_csr(csr: CertificateSigningRequest) -> str:
    return csr.public_bytes(Encoding.PEM).decode("utf-8")
Example #11
0
    def sign(self,
             csr: x509.CertificateSigningRequest,
             algorithm: str = 'sha256') -> x509.Certificate:
        """Sign a certificate signing request.

        Args:
            csr (x509.CertificateSigningRequest): The certificate signing request
        Returns:
            Instance of x509.Certificate
        """
        serial = self.serial + 1
        csr_subject = csr.subject

        builder = x509.CertificateBuilder()

        hash_functions = {
            'sha1': hashes.SHA1,
            'sha256': hashes.SHA256,
            'sha512': hashes.SHA512,
        }

        builder = builder.subject_name(
            x509.Name([
                x509.NameAttribute(NameOID.COMMON_NAME, "iOS Client")
            ])).issuer_name(self.certificate.subject).not_valid_before(
                datetime.datetime.utcnow()).not_valid_after(
                    datetime.datetime.utcnow() + datetime.timedelta(
                        days=365)).serial_number(serial).public_key(
                            csr.public_key())

        builder = builder.add_extension(
            #  Absolutely critical for SCEP
            x509.KeyUsage(digital_signature=True,
                          content_commitment=False,
                          key_encipherment=True,
                          data_encipherment=False,
                          key_agreement=False,
                          key_cert_sign=False,
                          crl_sign=False,
                          encipher_only=False,
                          decipher_only=False),
            True)

        # builder = builder.add_extension(
        #     x509.ExtendedKeyUsage([ObjectIdentifier('1.3.6.1.5.5.8.2.2')]), False
        # )
        #
        # builder = builder.add_extension(
        #     x509.SubjectKeyIdentifier.from_public_key(csr.public_key()), False
        # )
        #
        # for requested_extension in csr.extensions:
        #     builder = builder.add_extension(requested_extension.value, critical=requested_extension.critical)

        cert = builder.sign(self.private_key,
                            hash_functions.get(algorithm, hashes.SHA256)(),
                            default_backend())

        self._storage.save_issued_certificate(cert)
        self.serial = serial

        return cert
Example #12
0
def check_csr(csr: x509.CertificateSigningRequest, template: Template) -> str:
    """ find all differences (errors) between info in csr and template, return empty string if no errors"""
    extensions = {
        OID_NAMES.get(extension.oid, extension.oid): extension.value
        for extension in csr.extensions
    }
    # noinspection PyProtectedMember
    return '\n'.join(error for error in (
        # verify csr signature
        '' if csr.
        is_signature_valid else 'hmmm... csr signature is not valid!!!',
        # verify signature hash algorithm
        '' if csr.signature_hash_algorithm.name ==
        template.hash_algorithm.lower() else 'hmmm wrong hash algorithm!!!',
        # verify subject matches
        '' if template.subject == tuple(
            (OID_NAMES.get(attrib.oid), attrib.value)
            for attrib in csr.subject) else 'subject mismatch:\n{}\n{}\n'.
        format(csr.subject, template.subject),
        # verify subjectAltName
        '' if template.subject_alt_names is None
        and 'subject_alt_names' not in extensions else '' if tuple(
            x.value for x in extensions['subjectAltName']) == template.
        subject_alt_names else 'subject_alt_names mismatch:\n{}\n{}\n'.
        format(extensions['subjectAltName'], template.subject_alt_names),
        # verify basicConstraints ca
        '' if extensions['basicConstraints'].ca == template.basic_constraints.
        ca else 'basicConstraints ca mismatch:\n{}\n{}\n'.format(
            extensions['basicConstraints'].ca, template.basic_constraints.ca),
        # verify basicConstraints path_length
        '' if extensions['basicConstraints'].path_length ==
        template.basic_constraints.
        path_length else 'basicConstraints path_length mismatch:\n{}\n{}\n'.
        format(extensions['basicConstraints'].path_length, template.
               basic_constraints.path_length),
        # verify keyUsage
        '' if template.key_usage is None and 'keyUsage' not in extensions else
        '' if all((
            extensions['keyUsage'].digital_signature ==
            template.key_usage.digital_signature,
            extensions['keyUsage'].content_commitment ==
            template.key_usage.content_commitment,
            extensions['keyUsage'].key_encipherment ==
            template.key_usage.key_encipherment,
            extensions['keyUsage'].data_encipherment ==
            template.key_usage.data_encipherment,
            extensions['keyUsage'].key_agreement ==
            template.key_usage.key_agreement,
            extensions['keyUsage'].key_cert_sign ==
            template.key_usage.key_cert_sign,
            extensions['keyUsage'].crl_sign == template.key_usage.crl_sign,
            extensions['keyUsage']._encipher_only ==
            template.key_usage.encipher_only,
            extensions['keyUsage']._decipher_only ==
            template.key_usage.decipher_only,
        )) else 'keyUsage mismatch:\n{}\n{}\n'.
        format(extensions['keyUsage'], template.key_usage),
        # verify KeySize
        '' if template.key_size ==
        csr.public_key().key_size else 'KeySize mismatch:\n{}\n{}\n'.
        format(csr.public_key().key_size, template.key_size),
        # verify KeySize >= 2048
        '' if csr.public_key().key_size >= 2048 else 'weak key size {}'.
        format(csr.public_key().key_size)) if error != '')