예제 #1
0
def read_private_key(path):
    """
    Read details about a private key.

    path:
        Path to a private key in PEM format.

    CLI Example:

    .. code-block:: bash

        salt '*' pki.read_private_key /etc/ssl/private/example.key
    """

    ret = {}

    with _fopen(path, "rb") as f:
        key = serialization.load_pem_private_key(f.read(),
                                                 password=None,
                                                 backend=_default_backend())

    if isinstance(key, ec.EllipticCurvePrivateKey):
        ret["type"] = "ec"
        ret["curve"] = key.curve.name

    elif isinstance(key, rsa.RSAPrivateKey):
        ret["type"] = "rsa"
        ret["size"] = key.key_size

    else:
        raise SaltInvocationError("Unsupported private key object: {0}".format(
            type(key)))

    return ret
예제 #2
0
def read_certificate(crt):
    """
    Read details about a certificate.

    crt:
        Path to PEM-encoded certificate file or PEM-encoded string.


    CLI Example:

    .. code-block:: bash

        salt '*' pki.read_certificate /etc/ssl/certs/example.crt
    """

    if os.path.exists(crt):
        with _fopen(crt, "rb") as f:
            crt = x509.load_pem_x509_certificate(f.read(), _default_backend())
    else:
        crt = x509.load_pem_x509_certificate(crt.encode(), _default_backend())

    ret = {
        "extensions": _read_extensions(crt.extensions),
        "issuer": _read_name(crt.issuer),
        "not_valid_after": str(crt.not_valid_after),
        "not_valid_before": str(crt.not_valid_before),
        "public_key": _read_public_key(crt.public_key(), text=True),
        "serial": crt.serial_number,
        "subject": _read_name(crt.subject),
    }

    return ret
예제 #3
0
파일: acme.py 프로젝트: jgraichen/salt-acme
    def _registration(self, email):
        path = os.path.join(self.base, "registration.json")
        if not os.path.exists(path):
            logging.info("ACME: creating new account...")
            registration = self.client.new_account(
                messages.NewRegistration.from_data(
                    email=email, terms_of_service_agreed=True
                )
            )

            with _fopen(path, "w") as f:
                f.write(registration.json_dumps_pretty())

            return registration

        with _fopen(path, "r") as f:
            return messages.RegistrationResource.from_json(json.load(f))
예제 #4
0
파일: acme.py 프로젝트: jgraichen/salt-acme
 def _private_key(self):
     path = os.path.join(self.base, "private.key")
     if not os.path.exists(path):
         logging.info("ACME: creating new private account key...")
         with _fpopen(path, "wb", mode=0o600) as f:
             f.write(
                 rsa.generate_private_key(
                     public_exponent=65537, key_size=4096, backend=_default_backend()
                 ).private_bytes(
                     encoding=serialization.Encoding.PEM,
                     format=serialization.PrivateFormat.PKCS8,
                     encryption_algorithm=serialization.NoEncryption(),
                 )
             )
     with _fopen(path, "rb") as f:
         return josepy.JWK.load(f.read())
예제 #5
0
def renewal_needed(path, days_remaining=28):
    """
    Check if a certificate expires within the specified days.

    path:
        Path to PEM encoded certificate file.

    days_remaining:
        The minimum number of days remaining when the certificate should be
        renewed. Defaults to 28 days.
    """

    with _fopen(path, "rb") as f:
        crt = x509.load_pem_x509_certificate(f.read(),
                                             backend=_default_backend())

    remaining_days = (crt.not_valid_after - datetime.datetime.now()).days

    return remaining_days < days_remaining
예제 #6
0
def read_csr(csr):
    """
    Read details about a certificate signing request.

    csr:
        Path to a certificate signing request file or a PEM-encoded string.
    """

    if not isinstance(csr, x509.CertificateSigningRequest):
        if os.path.isfile(csr):
            with _fopen(csr, "rb") as f:
                csr = x509.load_pem_x509_csr(f.read(), _default_backend())
        else:
            csr = x509.load_pem_x509_csr(csr.encode(), _default_backend())

    return {
        "extensions": _read_extensions(csr.extensions),
        "public_key": _read_public_key(csr.public_key(), text=True),
        "subject": _read_name(csr.subject),
    }
예제 #7
0
def create_certificate(path=None, text=False, csr=None, timeout=120, **kwargs):
    """
    Create a certificate by asking the master to sign a certificate signing
    request (CSR) or create a CSR on-the-fly.

    path:
        Path to write certificate to. Either ``path`` or ``text`` must be
        specified.

    text:
        Return certificate as PEM-encoded string. Either ``path`` or ``text``
        must be specified.

    csr:
        Path to certificate signing request file.

    module:
        Execution module called to sign the CSR. The CSR is passed as a
        PEM-encoded string as the first argument to the module. It is expected
        to return a dictionary with the following field(s):

        text:
            The resulting certificate as a PEM-encoded string. It may contain
            additional intermediate certificates.

        A default module is fetched with ``config.get`` from ``pki:default:module``.

    runner:
        Runner to call on the master. The CSR is passed as a PEM-encoded string
        as the first argument to the runner. It is expected to return a
        dictionary with the following field(s):

        text:
            The resulting certificate as a PEM-encoded string. It may contain
            additional intermediate certificates.

        A default runner is fetched with ``config.get`` from ``pki:default:runner``.

    timeout:
        Maximum time to wait on a response from the runner.
    """

    if not path and not text:
        raise SaltInvocationError("Either path or text must be specified")

    if not csr:
        raise SaltInvocationError("CSR is required")

    if "runner" in kwargs and "module" in kwargs:
        raise SaltInvocationError("Either use 'runner' or 'module'")

    if "runner" not in kwargs and "module" not in kwargs:
        default = __salt__["config.get"]("pki:default", {})

        if "runner" in default:
            kwargs["runner"] = default["runner"]
        if "module" in default:
            kwargs["module"] = default["module"]

    if not kwargs.get("runner", None) and not kwargs.get("module", None):
        raise SaltInvocationError("Either 'runner' or 'module' is required")

    if os.path.exists(csr):
        with _fopen(csr, "r") as f:
            csr = f.read()

    if "runner" in kwargs:
        runner = kwargs["runner"]
        resp = __salt__["publish.runner"](runner, arg=csr, timeout=timeout)

        if not resp:
            raise SaltInvocationError(
                f"Nothing returned from runner, do you have permissions run {runner}?"
            )

        if isinstance(resp, str) and "timed out" in resp:
            raise SaltReqTimeoutError(resp)

        if isinstance(resp, str):
            raise CommandExecutionError(resp)

    elif "module" in kwargs:
        module = kwargs["module"]
        if module not in __salt__:
            raise SaltInvocationError(f"Module {module} not available")

        resp = __salt__[module](csr)

    if not isinstance(resp, dict):
        raise CommandExecutionError(
            f"Expected response to be a dict, but got {type(resp)}")

    try:
        ret = read_certificate(resp["text"])
    except ValueError as e:
        raise CommandExecutionError(
            f"Did not return a valid PEM-encoded certificate: {e}")

    if path:
        with _fopen(path, "w") as f:
            f.write(resp["text"])

    if text:
        return resp["text"]

    return ret
예제 #8
0
def create_csr(path=None, text=False, algorithm="sha384", **kwargs):
    """
    Create a certificate signing request (CSR).

    path:
        Path to write the CSR to.

    text:
        Directly return PEM-encoded CSR instead of dictionary with details.
        Either ``path`` or ``text`` must be specified.

    key:
        Path to the private key used to sign the CSR.

    subject:
        Dictionary with subject name pairs.

        .. code-block::
            {"commonName": "localhost"}

    domains:
        Alias method to quickly create a CSR with DNS names. All given domains
        will be added as DNS names to the subject alternative name extension and
        the first domain will additionally used for the subjects common name.

    extensions:
        Dictionary with x509v3 extensions.

        Supported extensions are:

        subjectAltName:
            x509v3 Subject Alternative Name

            .. code-block::

                {"subjectAltName": "DNS:www.example.org,IP:192.0.2.1"}
    """

    key = kwargs.get("key", None)
    subject = kwargs.get("subject", {})
    extensions = kwargs.get("extensions", {})

    if not path and not text:
        raise SaltInvocationError("Either path or text must be specified")

    if not key:
        raise SaltInvocationError("Key required")

    if "domains" in kwargs:
        domains = kwargs["domains"]
        if isinstance(domains, str):
            domains = [d.strip() for d in domains.split(",")]
        if isinstance(domains, list):
            if not subject:
                subject = {"commonName": domains[0]}
            if "subjectAltName" not in extensions:
                extensions["subjectAltName"] = [f"DNS:{n}" for n in domains]

    if not subject:
        raise SaltInvocationError("Subject required")

    if algorithm not in _HASHES:
        raise SaltInvocationError(f"Algorithm not supported: {algorithm}")

    subject = _create_name(subject)
    extensions = _create_extensions(extensions)

    with _fopen(key, "rb") as f:
        key = serialization.load_pem_private_key(f.read(),
                                                 password=None,
                                                 backend=_default_backend())

    csr = x509.CertificateSigningRequestBuilder(subject, extensions).sign(
        key, _HASHES[algorithm], backend=_default_backend())

    out = csr.public_bytes(serialization.Encoding.PEM)

    if path:
        with _fopen(path, "wb") as f:
            f.write(out)

    if text:
        return out.decode()

    return read_csr(csr)