<commit_before># -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from synapse.api.errors import (
    cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
)
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
from synapse.util.caches import intern_dict
import synapse.metrics
import synapse.events

from canonicaljson import (
    encode_canonical_json, encode_pretty_printed_json
)

from twisted.internet import defer
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo

import collections
import logging
import urllib
import ujson

logger = logging.getLogger(__name__)

metrics = synapse.metrics.get_metrics_for(__name__)

incoming_requests_counter = metrics.register_counter(
    "requests",
    labels=["method", "servlet", "tag"],
)
outgoing_responses_counter = metrics.register_counter(
    "responses",
    labels=["method", "code"],
)

response_timer = metrics.register_distribution(
    "response_time",
    labels=["method", "servlet", "tag"]
)

response_ru_utime = metrics.register_distribution(
    "response_ru_utime", labels=["method", "servlet", "tag"]
)

response_ru_stime = metrics.register_distribution(
    "response_ru_stime", labels=["method", "servlet", "tag"]
)

response_db_txn_count = metrics.register_distribution(
    "response_db_txn_count", labels=["method", "servlet", "tag"]
)

response_db_txn_duration = metrics.register_distribution(
    "response_db_txn_duration", labels=["method", "servlet", "tag"]
)

_next_request_id = 0

def request_handler(report_metrics=True):
    """Decorator for ``wrap_request_handler``"""
    return lambda request_handler: wrap_request_handler(request_handler, report_metrics)

def wrap_request_handler(request_handler, report_metrics):
    """Wraps a method that acts as a request handler with the necessary logging
    and exception handling.

    The method must have a signature of "handle_foo(self, request)". The
    argument "self" must have "version_string" and "clock" attributes. The
    argument "request" must be a twisted HTTP request.

    The method must return a deferred. If the deferred succeeds we assume that
    a response has been sent. If the deferred fails with a SynapseError we use
    it to send a JSON response with the appropriate HTTP reponse code. If the
    deferred fails with any other type of error we send a 500 reponse.

    We insert a unique request-id into the logging context for this request and
    log the response and duration for this request.
    """

    @defer.inlineCallbacks
    def wrapped_request_handler(self, request):
        global _next_request_id
        request_id = "%s-%s" % (request.method, _next_request_id)
        _next_request_id += 1

        with LoggingContext(request_id) as request_context:
            if report_metrics:
                request_metrics = RequestMetrics()
                request_metrics.start(self.clock)

            request_context.request = request_id
            with request.processing():
                try:
                    with PreserveLoggingContext(request_context):
                        yield request_handler(self, request)
                except CodeMessageException as e:
                    code = e.code
                    if isinstance(e, SynapseError):
                        logger.info(
                            "%s SynapseError: %s - %s", request, code, e.msg
                        )
                    else:
                        logger.exception(e)
                    outgoing_responses_counter.inc(request.method, str(code))
                    respond_with_json(
                        request, code, cs_exception(e), send_cors=True,
                        pretty_print=_request_user_agent_is_curl(request),
                        version_string=self.version_string,
                    )
                except:
                    logger.exception(
                        "Failed handle request %s.%s on %r: %r",
                        request_handler.__module__,
                        request_handler.__name__,
                        self,
                        request
                    )
                    respond_with_json(
                        request,
                        500,
                        {
                            "error": "Internal server error",
                            "errcode": Codes.UNKNOWN,
                        },
                        send_cors=True
                    )
                finally:
                    try:
                        if report_metrics:
                            request_metrics.stop(
                                self.clock, request, self.__class__.__name__
                            )
                    except:
                        pass
    return wrapped_request_handler

class HttpServer(object):
    """ Interface for registering callbacks on a HTTP server
    """

    def register_paths(self, method, path_patterns, callback):
        """ Register a callback that gets fired if we receive a http request
        with the given method for a path that matches the given regex.

        If the regex contains groups these gets passed to the calback via
        an unpacked tuple.

        Args:
            method (str): The method to listen to.
            path_patterns (list<SRE_Pattern>): The regex used to match requests.
            callback (function): The function to fire if we receive a matched
                request. The first argument will be the request object and
                subsequent arguments will be any matched groups from the regex.
                This should return a tuple of (code, response).
        """
        pass

class JsonResource(HttpServer, resource.Resource):
    """ This implements the HttpServer interface and provides JSON support for
    Resources.

    Register callbacks via register_path()

    Callbacks can return a tuple of status code and a dict in which case the
    the dict will automatically be sent to the client as a JSON object.

    The JsonResource is primarily intended for returning JSON, but callbacks
    may send something other than JSON, they may do so by using the methods
    on the request object and instead returning None.
    """

    isLeaf = True

    _PathEntry = collections.namedtuple("_PathEntry", ["pattern", "callback"])

    def __init__(self, hs, canonical_json=True):
        resource.Resource.__init__(self)

        self.canonical_json = canonical_json
        self.clock = hs.get_clock()
        self.path_regexs = {}
        self.version_string = hs.version_string
        self.hs = hs

    def register_paths(self, method, path_patterns, callback):
        for path_pattern in path_patterns:
            logger.debug("Registering for %s %s", method, path_pattern.pattern)
            self.path_regexs.setdefault(method, []).append(
                self._PathEntry(path_pattern, callback)
            )

    def render(self, request):
        """ This gets called by twisted every time someone sends us a request.
        """
        self._async_render(request)
        return server.NOT_DONE_YET

    # Disable metric reporting because _async_render does its own metrics.
    # It does its own metric reporting because _async_render dispatches to
    # a callback and it's the class name of that callback we want to report
    # against rather than the JsonResource itself.
    @request_handler(report_metrics=False)
    @defer.inlineCallbacks
    def _async_render(self, request):
        """ This gets called from render() every time someone sends us a request.
            This checks if anyone has registered a callback for that method and
            path.
        """
        if request.method == "OPTIONS":
            self._send_response(request, 200, {})
            return

        request_metrics = RequestMetrics()
        request_metrics.start(self.clock)

        # Loop through all the registered callbacks to check if the method
        # and path regex match
        for path_entry in self.path_regexs.get(request.method, []):
            m = path_entry.pattern.match(request.path)
            if not m:
                continue

            # We found a match! Trigger callback and then return the
            # returned response. We pass both the request and any
            # matched groups from the regex to the callback.

            callback = path_entry.callback

            servlet_instance = getattr(callback, "__self__", None)
            if servlet_instance is not None:
                servlet_classname = servlet_instance.__class__.__name__
            else:
                servlet_classname = "%r" % callback

            kwargs = intern_dict({
                name: urllib.unquote(value).decode("UTF-8") if value else value
                for name, value in m.groupdict().items()
            })

            callback_return = yield callback(request, **kwargs)
            if callback_return is not None:
                code, response = callback_return
                self._send_response(request, code, response)

            try:
                request_metrics.stop(self.clock, request, servlet_classname)
            except:
                pass

            return

        # Huh. No one wanted to handle that? Fiiiiiine. Send 400.
        raise UnrecognizedRequestError()

    def _send_response(self, request, code, response_json_object,
                       response_code_message=None):
        # could alternatively use request.notifyFinish() and flip a flag when
        # the Deferred fires, but since the flag is RIGHT THERE it seems like
        # a waste.
        if request._disconnected:
            logger.warn(
                "Not sending response to request %s, already disconnected.",
                request)
            return

        outgoing_responses_counter.inc(request.method, str(code))

        # TODO: Only enable CORS for the requests that need it.
        respond_with_json(
            request, code, response_json_object,
            send_cors=True,
            response_code_message=response_code_message,
            pretty_print=_request_user_agent_is_curl(request),
            version_string=self.version_string,
            canonical_json=self.canonical_json,
        )

class RequestMetrics(object):
    def start(self, clock):
        self.start = clock.time_msec()
        self.start_context = LoggingContext.current_context()

    def stop(self, clock, request, servlet_classname):
        context = LoggingContext.current_context()

        tag = ""
        if context:
            tag = context.tag

            if context != self.start_context:
                logger.warn(
                    "Context have unexpectedly changed %r, %r",
                    context, self.start_context
                )
                return

        incoming_requests_counter.inc(request.method, servlet_classname, tag)

        response_timer.inc_by(
            clock.time_msec() - self.start, request.method,
            servlet_classname, tag
        )

        ru_utime, ru_stime = context.get_resource_usage()

        response_ru_utime.inc_by(
            ru_utime, request.method, servlet_classname, tag
        )
        response_ru_stime.inc_by(
            ru_stime, request.method, servlet_classname, tag
        )
        response_db_txn_count.inc_by(
            context.db_txn_count, request.method, servlet_classname, tag
        )
        response_db_txn_duration.inc_by(
            context.db_txn_duration, request.method, servlet_classname, tag
        )

class RootRedirect(resource.Resource):
    """Redirects the root '/' path to another path."""

    def __init__(self, path):
        resource.Resource.__init__(self)
        self.url = path

    def render_GET(self, request):
        return redirectTo(self.url, request)

    def getChild(self, name, request):
        if len(name) == 0:
            return self  # select ourselves as the child to render
        return resource.Resource.getChild(self, name, request)

def respond_with_json(request, code, json_object, send_cors=False,
                      response_code_message=None, pretty_print=False,
                      version_string="", canonical_json=True):
    if pretty_print:
        json_bytes = encode_pretty_printed_json(json_object) + "\n"
    else:
        if canonical_json or synapse.events.USE_FROZEN_DICTS:
            json_bytes = encode_canonical_json(json_object)
        else:
            # ujson doesn't like frozen_dicts.
            json_bytes = ujson.dumps(json_object, ensure_ascii=False)

    return respond_with_json_bytes(
        request, code, json_bytes,
        send_cors=send_cors,
        response_code_message=response_code_message,
        version_string=version_string
    )

def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
                            version_string="", response_code_message=None):
    """Sends encoded JSON in response to the given request.

    Args:
        request (twisted.web.http.Request): The http request to respond to.
        code (int): The HTTP response code.
        json_bytes (bytes): The json bytes to use as the response body.
        send_cors (bool): Whether to send Cross-Origin Resource Sharing headers
            http://www.w3.org/TR/cors/
    Returns:
        twisted.web.server.NOT_DONE_YET"""

    request.setResponseCode(code, message=response_code_message)
    request.setHeader(b"Content-Type", b"application/json")
    request.setHeader(b"Server", version_string)
    request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))

    if send_cors:
        request.setHeader("Access-Control-Allow-Origin", "*")
        request.setHeader("Access-Control-Allow-Methods",
                          "GET, POST, PUT, DELETE, OPTIONS")
        request.setHeader("Access-Control-Allow-Headers",
                          "Origin, X-Requested-With, Content-Type, Accept")

    request.write(json_bytes)
    finish_request(request)
    return NOT_DONE_YET

def finish_request(request):
    """ Finish writing the response to the request.

    Twisted throws a RuntimeException if the connection closed before the
    response was written but doesn't provide a convenient or reliable way to
    determine if the connection was closed. So we catch and log the RuntimeException

    You might think that ``request.notifyFinish`` could be used to tell if the
    request was finished. However the deferred it returns won't fire if the
    connection was already closed, meaning we'd have to have called the method
    right at the start of the request. By the time we want to write the response
    it will already be too late.
    """
    try:
        request.finish()
    except RuntimeError as e:
        logger.info("Connection disconnected before response was written: %r", e)

def _request_user_agent_is_curl(request):
    user_agents = request.requestHeaders.getRawHeaders(
        "User-Agent", default=[]
    )
    for user_agent in user_agents:
        if "curl" in user_agent:
            return True
    return False
<commit_msg>Measure http.server render<commit_after># -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from synapse.api.errors import (
    cs_exception, SynapseError, CodeMessageException, UnrecognizedRequestError, Codes
)
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
from synapse.util.caches import intern_dict
from synapse.util.metrics import Measure
import synapse.metrics
import synapse.events

from canonicaljson import (
    encode_canonical_json, encode_pretty_printed_json
)

from twisted.internet import defer
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo

import collections
import logging
import urllib
import ujson

logger = logging.getLogger(__name__)

metrics = synapse.metrics.get_metrics_for(__name__)

incoming_requests_counter = metrics.register_counter(
    "requests",
    labels=["method", "servlet", "tag"],
)
outgoing_responses_counter = metrics.register_counter(
    "responses",
    labels=["method", "code"],
)

response_timer = metrics.register_distribution(
    "response_time",
    labels=["method", "servlet", "tag"]
)

response_ru_utime = metrics.register_distribution(
    "response_ru_utime", labels=["method", "servlet", "tag"]
)

response_ru_stime = metrics.register_distribution(
    "response_ru_stime", labels=["method", "servlet", "tag"]
)

response_db_txn_count = metrics.register_distribution(
    "response_db_txn_count", labels=["method", "servlet", "tag"]
)

response_db_txn_duration = metrics.register_distribution(
    "response_db_txn_duration", labels=["method", "servlet", "tag"]
)

_next_request_id = 0

def request_handler(report_metrics=True):
    """Decorator for ``wrap_request_handler``"""
    return lambda request_handler: wrap_request_handler(request_handler, report_metrics)

def wrap_request_handler(request_handler, report_metrics):
    """Wraps a method that acts as a request handler with the necessary logging
    and exception handling.

    The method must have a signature of "handle_foo(self, request)". The
    argument "self" must have "version_string" and "clock" attributes. The
    argument "request" must be a twisted HTTP request.

    The method must return a deferred. If the deferred succeeds we assume that
    a response has been sent. If the deferred fails with a SynapseError we use
    it to send a JSON response with the appropriate HTTP reponse code. If the
    deferred fails with any other type of error we send a 500 reponse.

    We insert a unique request-id into the logging context for this request and
    log the response and duration for this request.
    """

    @defer.inlineCallbacks
    def wrapped_request_handler(self, request):
        global _next_request_id
        request_id = "%s-%s" % (request.method, _next_request_id)
        _next_request_id += 1

        with LoggingContext(request_id) as request_context:
            if report_metrics:
                request_metrics = RequestMetrics()
                request_metrics.start(self.clock)

            request_context.request = request_id
            with request.processing():
                try:
                    with PreserveLoggingContext(request_context):
                        yield request_handler(self, request)
                except CodeMessageException as e:
                    code = e.code
                    if isinstance(e, SynapseError):
                        logger.info(
                            "%s SynapseError: %s - %s", request, code, e.msg
                        )
                    else:
                        logger.exception(e)
                    outgoing_responses_counter.inc(request.method, str(code))
                    respond_with_json(
                        request, code, cs_exception(e), send_cors=True,
                        pretty_print=_request_user_agent_is_curl(request),
                        version_string=self.version_string,
                    )
                except:
                    logger.exception(
                        "Failed handle request %s.%s on %r: %r",
                        request_handler.__module__,
                        request_handler.__name__,
                        self,
                        request
                    )
                    respond_with_json(
                        request,
                        500,
                        {
                            "error": "Internal server error",
                            "errcode": Codes.UNKNOWN,
                        },
                        send_cors=True
                    )
                finally:
                    try:
                        if report_metrics:
                            request_metrics.stop(
                                self.clock, request, self.__class__.__name__
                            )
                    except:
                        pass
    return wrapped_request_handler

class HttpServer(object):
    """ Interface for registering callbacks on a HTTP server
    """

    def register_paths(self, method, path_patterns, callback):
        """ Register a callback that gets fired if we receive a http request
        with the given method for a path that matches the given regex.

        If the regex contains groups these gets passed to the calback via
        an unpacked tuple.

        Args:
            method (str): The method to listen to.
            path_patterns (list<SRE_Pattern>): The regex used to match requests.
            callback (function): The function to fire if we receive a matched
                request. The first argument will be the request object and
                subsequent arguments will be any matched groups from the regex.
                This should return a tuple of (code, response).
        """
        pass

class JsonResource(HttpServer, resource.Resource):
    """ This implements the HttpServer interface and provides JSON support for
    Resources.

    Register callbacks via register_path()

    Callbacks can return a tuple of status code and a dict in which case the
    the dict will automatically be sent to the client as a JSON object.

    The JsonResource is primarily intended for returning JSON, but callbacks
    may send something other than JSON, they may do so by using the methods
    on the request object and instead returning None.
    """

    isLeaf = True

    _PathEntry = collections.namedtuple("_PathEntry", ["pattern", "callback"])

    def __init__(self, hs, canonical_json=True):
        resource.Resource.__init__(self)

        self.canonical_json = canonical_json
        self.clock = hs.get_clock()
        self.path_regexs = {}
        self.version_string = hs.version_string
        self.hs = hs

    def register_paths(self, method, path_patterns, callback):
        for path_pattern in path_patterns:
            logger.debug("Registering for %s %s", method, path_pattern.pattern)
            self.path_regexs.setdefault(method, []).append(
                self._PathEntry(path_pattern, callback)
            )

    def render(self, request):
        """ This gets called by twisted every time someone sends us a request.
        """
        self._async_render(request)
        return server.NOT_DONE_YET

    # Disable metric reporting because _async_render does its own metrics.
    # It does its own metric reporting because _async_render dispatches to
    # a callback and it's the class name of that callback we want to report
    # against rather than the JsonResource itself.
    @request_handler(report_metrics=False)
    @defer.inlineCallbacks
    def _async_render(self, request):
        """ This gets called from render() every time someone sends us a request.
            This checks if anyone has registered a callback for that method and
            path.
        """
        if request.method == "OPTIONS":
            self._send_response(request, 200, {})
            return

        request_metrics = RequestMetrics()
        request_metrics.start(self.clock)

        with Measure(self.clock, "http.render"):
            # Loop through all the registered callbacks to check if the method
            # and path regex match
            for path_entry in self.path_regexs.get(request.method, []):
                m = path_entry.pattern.match(request.path)
                if not m:
                    continue

                # We found a match! Trigger callback and then return the
                # returned response. We pass both the request and any
                # matched groups from the regex to the callback.

                callback = path_entry.callback

                servlet_instance = getattr(callback, "__self__", None)
                if servlet_instance is not None:
                    servlet_classname = servlet_instance.__class__.__name__
                else:
                    servlet_classname = "%r" % callback

                kwargs = intern_dict({
                    name: urllib.unquote(value).decode("UTF-8") if value else value
                    for name, value in m.groupdict().items()
                })

                callback_return = yield callback(request, **kwargs)
                if callback_return is not None:
                    code, response = callback_return
                    self._send_response(request, code, response)

                try:
                    request_metrics.stop(self.clock, request, servlet_classname)
                except:
                    pass

                return

        # Huh. No one wanted to handle that? Fiiiiiine. Send 400.
        raise UnrecognizedRequestError()

    def _send_response(self, request, code, response_json_object,
                       response_code_message=None):
        # could alternatively use request.notifyFinish() and flip a flag when
        # the Deferred fires, but since the flag is RIGHT THERE it seems like
        # a waste.
        if request._disconnected:
            logger.warn(
                "Not sending response to request %s, already disconnected.",
                request)
            return

        outgoing_responses_counter.inc(request.method, str(code))

        # TODO: Only enable CORS for the requests that need it.
        respond_with_json(
            request, code, response_json_object,
            send_cors=True,
            response_code_message=response_code_message,
            pretty_print=_request_user_agent_is_curl(request),
            version_string=self.version_string,
            canonical_json=self.canonical_json,
        )

class RequestMetrics(object):
    def start(self, clock):
        self.start = clock.time_msec()
        self.start_context = LoggingContext.current_context()

    def stop(self, clock, request, servlet_classname):
        context = LoggingContext.current_context()

        tag = ""
        if context:
            tag = context.tag

            if context != self.start_context:
                logger.warn(
                    "Context have unexpectedly changed %r, %r",
                    context, self.start_context
                )
                return

        incoming_requests_counter.inc(request.method, servlet_classname, tag)

        response_timer.inc_by(
            clock.time_msec() - self.start, request.method,
            servlet_classname, tag
        )

        ru_utime, ru_stime = context.get_resource_usage()

        response_ru_utime.inc_by(
            ru_utime, request.method, servlet_classname, tag
        )
        response_ru_stime.inc_by(
            ru_stime, request.method, servlet_classname, tag
        )
        response_db_txn_count.inc_by(
            context.db_txn_count, request.method, servlet_classname, tag
        )
        response_db_txn_duration.inc_by(
            context.db_txn_duration, request.method, servlet_classname, tag
        )

class RootRedirect(resource.Resource):
    """Redirects the root '/' path to another path."""

    def __init__(self, path):
        resource.Resource.__init__(self)
        self.url = path

    def render_GET(self, request):
        return redirectTo(self.url, request)

    def getChild(self, name, request):
        if len(name) == 0:
            return self  # select ourselves as the child to render
        return resource.Resource.getChild(self, name, request)

def respond_with_json(request, code, json_object, send_cors=False,
                      response_code_message=None, pretty_print=False,
                      version_string="", canonical_json=True):
    if pretty_print:
        json_bytes = encode_pretty_printed_json(json_object) + "\n"
    else:
        if canonical_json or synapse.events.USE_FROZEN_DICTS:
            json_bytes = encode_canonical_json(json_object)
        else:
            # ujson doesn't like frozen_dicts.
            json_bytes = ujson.dumps(json_object, ensure_ascii=False)

    return respond_with_json_bytes(
        request, code, json_bytes,
        send_cors=send_cors,
        response_code_message=response_code_message,
        version_string=version_string
    )

def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
                            version_string="", response_code_message=None):
    """Sends encoded JSON in response to the given request.

    Args:
        request (twisted.web.http.Request): The http request to respond to.
        code (int): The HTTP response code.
        json_bytes (bytes): The json bytes to use as the response body.
        send_cors (bool): Whether to send Cross-Origin Resource Sharing headers
            http://www.w3.org/TR/cors/
    Returns:
        twisted.web.server.NOT_DONE_YET"""

    request.setResponseCode(code, message=response_code_message)
    request.setHeader(b"Content-Type", b"application/json")
    request.setHeader(b"Server", version_string)
    request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))

    if send_cors:
        request.setHeader("Access-Control-Allow-Origin", "*")
        request.setHeader("Access-Control-Allow-Methods",
                          "GET, POST, PUT, DELETE, OPTIONS")
        request.setHeader("Access-Control-Allow-Headers",
                          "Origin, X-Requested-With, Content-Type, Accept")

    request.write(json_bytes)
    finish_request(request)
    return NOT_DONE_YET

def finish_request(request):
    """ Finish writing the response to the request.

    Twisted throws a RuntimeException if the connection closed before the
    response was written but doesn't provide a convenient or reliable way to
    determine if the connection was closed. So we catch and log the RuntimeException

    You might think that ``request.notifyFinish`` could be used to tell if the
    request was finished. However the deferred it returns won't fire if the
    connection was already closed, meaning we'd have to have called the method
    right at the start of the request. By the time we want to write the response
    it will already be too late.
    """
    try:
        request.finish()
    except RuntimeError as e:
        logger.info("Connection disconnected before response was written: %r", e)

def _request_user_agent_is_curl(request):
    user_agents = request.requestHeaders.getRawHeaders(
        "User-Agent", default=[]
    )
    for user_agent in user_agents:
        if "curl" in user_agent:
            return True
    return False
<|endoftext|>


<commit_before># Copyright (c) 2017, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.

'''Class for server environment configuration and defaults.'''

from os import environ

import lib.util as lib_util

class EnvBase(lib_util.LoggedClass):
    '''Wraps environment configuration.'''

    class Error(Exception):
        pass

    def __init__(self):
        super().__init__()
        self.allow_root = self.boolean('ALLOW_ROOT', False)
        self.host = self.default('HOST', 'localhost')
        self.rpc_host = self.default('RPC_HOST', 'localhost')
        self.loop_policy = self.event_loop_policy()

    def default(self, envvar, default):
        return environ.get(envvar, default)

    def boolean(self, envvar, default):
        default = 'Yes' if default else ''
        return bool(self.default(envvar, default).strip())

    def required(self, envvar):
        value = environ.get(envvar)
        if value is None:
            raise self.Error('required envvar {} not set'.format(envvar))
        return value

    def integer(self, envvar, default):
        value = environ.get(envvar)
        if value is None:
            return default
        try:
            return int(value)
        except Exception:
            raise self.Error('cannot convert envvar {} value {} to an integer'
                             .format(envvar, value))

    def obsolete(self, envvars):
        bad = [envvar for envvar in envvars if environ.get(envvar)]
        if bad:
            raise self.Error('remove obsolete environment variables {}'
                             .format(bad))

    def event_loop_policy(self):
        policy = self.default('EVENT_LOOP_POLICY', None)
        if policy is None:
            return None
        if policy == 'uvloop':
            import uvloop
            return uvloop.EventLoopPolicy()
        raise self.Error('unknown event loop policy "{}"'.format(policy))

    def cs_host(self, *, for_rpc):
        '''Returns the 'host' argument to pass to asyncio's create_server
        call.  The result can be a single host name string, a list of
        host name strings, or an empty string to bind to all interfaces.

        If rpc is True the host to use for the RPC server is returned.
        Otherwise the host to use for SSL/TCP servers is returned.
        '''
        host = self.rpc_host if for_rpc else self.host
        result = [part.strip() for part in host.split(',')]
        if len(result) == 1:
            result = result[0]
        # An empty result indicates all interfaces, which we do not
        # permitted for an RPC server.
        if for_rpc and not result:
            result = 'localhost'
        return result
<commit_msg>Make some EnvBase methods class methods<commit_after># Copyright (c) 2017, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.

'''Class for server environment configuration and defaults.'''

from os import environ

import lib.util as lib_util

class EnvBase(lib_util.LoggedClass):
    '''Wraps environment configuration.'''

    class Error(Exception):
        pass

    def __init__(self):
        super().__init__()
        self.allow_root = self.boolean('ALLOW_ROOT', False)
        self.host = self.default('HOST', 'localhost')
        self.rpc_host = self.default('RPC_HOST', 'localhost')
        self.loop_policy = self.event_loop_policy()

    @classmethod
    def default(cls, envvar, default):
        return environ.get(envvar, default)

    @classmethod
    def boolean(cls, envvar, default):
        default = 'Yes' if default else ''
        return bool(cls.default(envvar, default).strip())

    @classmethod
    def required(cls, envvar):
        value = environ.get(envvar)
        if value is None:
            raise cls.Error('required envvar {} not set'.format(envvar))
        return value

    @classmethod
    def integer(cls, envvar, default):
        value = environ.get(envvar)
        if value is None:
            return default
        try:
            return int(value)
        except Exception:
            raise cls.Error('cannot convert envvar {} value {} to an integer'
                            .format(envvar, value))

    @classmethod
    def obsolete(cls, envvars):
        bad = [envvar for envvar in envvars if environ.get(envvar)]
        if bad:
            raise cls.Error('remove obsolete environment variables {}'
                            .format(bad))

    def event_loop_policy(self):
        policy = self.default('EVENT_LOOP_POLICY', None)
        if policy is None:
            return None
        if policy == 'uvloop':
            import uvloop
            return uvloop.EventLoopPolicy()
        raise self.Error('unknown event loop policy "{}"'.format(policy))

    def cs_host(self, *, for_rpc):
        '''Returns the 'host' argument to pass to asyncio's create_server
        call.  The result can be a single host name string, a list of
        host name strings, or an empty string to bind to all interfaces.

        If rpc is True the host to use for the RPC server is returned.
        Otherwise the host to use for SSL/TCP servers is returned.
        '''
        host = self.rpc_host if for_rpc else self.host
        result = [part.strip() for part in host.split(',')]
        if len(result) == 1:
            result = result[0]
        # An empty result indicates all interfaces, which we do not
        # permitted for an RPC server.
        if for_rpc and not result:
            result = 'localhost'
        return result
<|endoftext|>


<commit_before>from __future__ import absolute_import
import os
import time

import synapse.common as s_common

from OpenSSL import crypto

defdir = os.getenv('SYN_CERT_DIR')
if defdir is None:
    defdir = '~/.syn/certs'

def iterFqdnUp(fqdn):
    levs = fqdn.split('.')
    for i in range(len(levs)):
        yield '.'.join(levs[i:])

class CertDir:

    def __init__(self, path=None):

        if path is None:
            path = defdir

        s_common.gendir(path, 'cas')
        s_common.gendir(path, 'hosts')
        s_common.gendir(path, 'users')

        self.certdir = s_common.reqdir(path)

    def getPathJoin(self, *paths):
        return s_common.genpath(self.certdir, *paths)

    def getCaCert(self, name):
        return self._loadCertPath(self.getCaCertPath(name))

    def getHostCert(self, name):
        return self._loadCertPath(self.getHostCertPath(name))

    def getUserCert(self, name):
        return self._loadCertPath(self.getUserCertPath(name))

    def getCaKey(self, name):
        return self._loadKeyPath(self.getCaKeyPath(name))

    def getHostKey(self, name):
        return self._loadKeyPath(self.getHostKeyPath(name))

    def getUserKey(self, name):
        return self._loadKeyPath(self.getUserKeyPath(name))

    def _loadKeyPath(self, path):
        if path is None:
            return None

        byts = s_common.getbytes(path)
        if byts is None:
            return None

        return crypto.load_privatekey(crypto.FILETYPE_PEM, byts)

    def _loadCertPath(self, path):
        if path is None:
            return None

        byts = s_common.getbytes(path)
        if byts is None:
            return None

        return crypto.load_certificate(crypto.FILETYPE_PEM, byts)

    #def saveCaCert(self, cert):
    #def saveUserCert(self, cert):
    #def saveHostCert(self, cert):
    #def saveX509Cert(self, cert):
    #def loadX509Cert(self, path):

    def _genBasePkeyCert(self, name, pkey=None):

        if pkey is None:
            pkey = crypto.PKey()
            pkey.generate_key(crypto.TYPE_RSA, 2048)

        cert = crypto.X509()
        cert.set_pubkey(pkey)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)

        cert.set_serial_number(int(s_common.guid(), 16))
        cert.get_subject().CN = name

        return pkey, cert

    def _saveCertTo(self, cert, *paths):
        path = self.getPathJoin(*paths)
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

        data = None
        if isinstance(cert, crypto.X509):
            data = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
        elif isinstance(cert, crypto.PKCS12):
            data = cert.export()

        if data:
            with s_common.genfile(path) as fd:
                fd.write(data)

        return path

    def _savePkeyTo(self, pkey, *paths):
        path = self.getPathJoin(*paths)
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
        return path

    def genCaCert(self, name, signas=None, outp=None):
        pkey, cert = self._genBasePkeyCert(name)
        ext0 = crypto.X509Extension(b'basicConstraints', False, b'CA:TRUE')
        cert.add_extensions([ext0])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        keypath = self._savePkeyTo(pkey, 'cas', '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'cas', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCert(self, name, signas=None, outp=None, pkey=None, sans=None):
        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        ext_sans = {'DNS:' + name}
        if isinstance(sans, str):
            ext_sans = ext_sans.union(sans.split(','))
        ext_sans = ','.join(sorted(ext_sans))

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'server'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature,keyEncipherment'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'serverAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
            crypto.X509Extension(b'subjectAltName', False, ext_sans.encode('utf-8')),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'hosts', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'hosts', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genUserCert(self, name, signas=None, outp=None, pkey=None):

        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'client'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'users', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'users', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        ccert = crypto.PKCS12()
        ccert.set_friendlyname(name.encode('utf-8'))
        ccert.set_certificate(cert)
        ccert.set_privatekey(pkey)

        if signas:
            cacert = self.getCaCert(signas)
            ccert.set_ca_certificates([cacert])

        crtpath = self._saveCertTo(ccert, 'users', '%s.p12' % name)
        if outp is not None:
            outp.printf('client cert saved: %s' % (crtpath,))

        return pkey, cert

    def _loadCsrPath(self, path):
        byts = s_common.getbytes(path)
        if byts is None:
            return None
        return crypto.load_certificate_request(crypto.FILETYPE_PEM, byts)

    def genUserCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'users', outp=outp)

    def genHostCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'hosts', outp=outp)

    def signUserCsr(self, xcsr, signas, outp=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genUserCert(name, pkey=pkey, signas=signas, outp=outp)

    def signHostCsr(self, xcsr, signas, outp=None, sans=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genHostCert(name, pkey=pkey, signas=signas, outp=outp, sans=sans)

    def _genPkeyCsr(self, name, mode, outp=None):
        pkey = crypto.PKey()
        pkey.generate_key(crypto.TYPE_RSA, 2048)

        xcsr = crypto.X509Req()
        xcsr.get_subject().CN = name

        xcsr.set_pubkey(pkey)
        xcsr.sign(pkey, 'sha256')

        keypath = self._savePkeyTo(pkey, mode, '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        csrpath = self.getPathJoin(mode, '%s.csr' % name)
        if os.path.isfile(csrpath):
            raise s_common.DupFileName(path=csrpath)

        with s_common.genfile(csrpath) as fd:
            fd.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, xcsr))

        if outp is not None:
            outp.printf('csr saved: %s' % (csrpath,))

    def signCertAs(self, cert, signas):
        cakey = self.getCaKey(signas)
        cacert = self.getCaCert(signas)

        cert.set_issuer(cacert.get_subject())
        cert.sign(cakey, 'sha256')

    def selfSignCert(self, cert, pkey):
        cert.set_issuer(cert.get_subject())
        cert.sign(pkey, 'sha256')

    def getUserForHost(self, user, host):
        for name in iterFqdnUp(host):
            usercert = '%s@%s' % (user, name)
            if self.isUserCert(usercert):
                return usercert

    def getCaCertPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getCaKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostCertPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCertPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCaPath(self, name):
        cert = self.getUserCert(name)
        if cert is None:
            return None

        subj = cert.get_issuer()

        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def getHostCaPath(self, name):
        cert = self.getHostCert(name)
        if cert is None:
            return None

        subj = cert.get_issuer()

        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def isUserCert(self, name):
        crtpath = self.getPathJoin('users', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isCaCert(self, name):
        crtpath = self.getPathJoin('cas', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isHostCert(self, name):
        crtpath = self.getPathJoin('hosts', '%s.crt' % name)
        return os.path.isfile(crtpath)
<commit_msg>visi's PR feedback<commit_after>from __future__ import absolute_import
import os
import time

import synapse.common as s_common

from OpenSSL import crypto

defdir = os.getenv('SYN_CERT_DIR')
if defdir is None:
    defdir = '~/.syn/certs'

def iterFqdnUp(fqdn):
    levs = fqdn.split('.')
    for i in range(len(levs)):
        yield '.'.join(levs[i:])

class CertDir:

    def __init__(self, path=None):

        if path is None:
            path = defdir

        s_common.gendir(path, 'cas')
        s_common.gendir(path, 'hosts')
        s_common.gendir(path, 'users')

        self.certdir = s_common.reqdir(path)

    def getPathJoin(self, *paths):
        return s_common.genpath(self.certdir, *paths)

    def getCaCert(self, name):
        return self._loadCertPath(self.getCaCertPath(name))

    def getHostCert(self, name):
        return self._loadCertPath(self.getHostCertPath(name))

    def getUserCert(self, name):
        return self._loadCertPath(self.getUserCertPath(name))

    def getCaKey(self, name):
        return self._loadKeyPath(self.getCaKeyPath(name))

    def getHostKey(self, name):
        return self._loadKeyPath(self.getHostKeyPath(name))

    def getUserKey(self, name):
        return self._loadKeyPath(self.getUserKeyPath(name))

    def _loadKeyPath(self, path):
        if path is None:
            return None

        byts = s_common.getbytes(path)
        if byts is None:
            return None

        return crypto.load_privatekey(crypto.FILETYPE_PEM, byts)

    def _loadCertPath(self, path):
        if path is None:
            return None

        byts = s_common.getbytes(path)
        if byts is None:
            return None

        return crypto.load_certificate(crypto.FILETYPE_PEM, byts)

    #def saveCaCert(self, cert):
    #def saveUserCert(self, cert):
    #def saveHostCert(self, cert):
    #def saveX509Cert(self, cert):
    #def loadX509Cert(self, path):

    def _genBasePkeyCert(self, name, pkey=None):

        if pkey is None:
            pkey = crypto.PKey()
            pkey.generate_key(crypto.TYPE_RSA, 2048)

        cert = crypto.X509()
        cert.set_pubkey(pkey)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)

        cert.set_serial_number(int(s_common.guid(), 16))
        cert.get_subject().CN = name

        return pkey, cert

    def _saveCertTo(self, cert, *paths):
        path = self.getPathJoin(*paths)
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

        return path

    def _saveP12To(self, cert, *paths):
        path = self.getPathJoin(*paths)
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

        with s_common.genfile(path) as fd:
            fd.write(cert.export())

        return path

    def _savePkeyTo(self, pkey, *paths):
        path = self.getPathJoin(*paths)
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
        return path

    def genCaCert(self, name, signas=None, outp=None):
        pkey, cert = self._genBasePkeyCert(name)
        ext0 = crypto.X509Extension(b'basicConstraints', False, b'CA:TRUE')
        cert.add_extensions([ext0])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        keypath = self._savePkeyTo(pkey, 'cas', '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'cas', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCert(self, name, signas=None, outp=None, pkey=None, sans=None):
        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        ext_sans = {'DNS:' + name}
        if isinstance(sans, str):
            ext_sans = ext_sans.union(sans.split(','))
        ext_sans = ','.join(sorted(ext_sans))

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'server'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature,keyEncipherment'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'serverAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
            crypto.X509Extension(b'subjectAltName', False, ext_sans.encode('utf-8')),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'hosts', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'hosts', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genUserCert(self, name, signas=None, outp=None, pkey=None):

        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'client'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'users', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'users', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        ccert = crypto.PKCS12()
        ccert.set_friendlyname(name.encode('utf-8'))
        ccert.set_certificate(cert)
        ccert.set_privatekey(pkey)

        if signas:
            cacert = self.getCaCert(signas)
            ccert.set_ca_certificates([cacert])

        crtpath = self._saveP12To(ccert, 'users', '%s.p12' % name)
        if outp is not None:
            outp.printf('client cert saved: %s' % (crtpath,))

        return pkey, cert

    def _loadCsrPath(self, path):
        byts = s_common.getbytes(path)
        if byts is None:
            return None
        return crypto.load_certificate_request(crypto.FILETYPE_PEM, byts)

    def genUserCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'users', outp=outp)

    def genHostCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'hosts', outp=outp)

    def signUserCsr(self, xcsr, signas, outp=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genUserCert(name, pkey=pkey, signas=signas, outp=outp)

    def signHostCsr(self, xcsr, signas, outp=None, sans=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genHostCert(name, pkey=pkey, signas=signas, outp=outp, sans=sans)

    def _genPkeyCsr(self, name, mode, outp=None):
        pkey = crypto.PKey()
        pkey.generate_key(crypto.TYPE_RSA, 2048)

        xcsr = crypto.X509Req()
        xcsr.get_subject().CN = name

        xcsr.set_pubkey(pkey)
        xcsr.sign(pkey, 'sha256')

        keypath = self._savePkeyTo(pkey, mode, '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        csrpath = self.getPathJoin(mode, '%s.csr' % name)
        if os.path.isfile(csrpath):
            raise s_common.DupFileName(path=csrpath)

        with s_common.genfile(csrpath) as fd:
            fd.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, xcsr))

        if outp is not None:
            outp.printf('csr saved: %s' % (csrpath,))

    def signCertAs(self, cert, signas):
        cakey = self.getCaKey(signas)
        cacert = self.getCaCert(signas)

        cert.set_issuer(cacert.get_subject())
        cert.sign(cakey, 'sha256')

    def selfSignCert(self, cert, pkey):
        cert.set_issuer(cert.get_subject())
        cert.sign(pkey, 'sha256')

    def getUserForHost(self, user, host):
        for name in iterFqdnUp(host):
            usercert = '%s@%s' % (user, name)
            if self.isUserCert(usercert):
                return usercert

    def getCaCertPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getCaKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostCertPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCertPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCaPath(self, name):
        cert = self.getUserCert(name)
        if cert is None:
            return None

        subj = cert.get_issuer()

        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def getHostCaPath(self, name):
        cert = self.getHostCert(name)
        if cert is None:
            return None

        subj = cert.get_issuer()

        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def isUserCert(self, name):
        crtpath = self.getPathJoin('users', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isCaCert(self, name):
        crtpath = self.getPathJoin('cas', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isHostCert(self, name):
        crtpath = self.getPathJoin('hosts', '%s.crt' % name)
        return os.path.isfile(crtpath)
<|endoftext|>


<commit_before>import os

from OpenSSL import crypto

import synapse.common as s_common

defdir = os.getenv('SYN_CERT_DIR')
if defdir is None:
    defdir = '~/.syn/certs'

def iterFqdnUp(fqdn):
    levs = fqdn.split('.')
    for i in range(len(levs)):
        yield '.'.join(levs[i:])

class CertDir:

    def __init__(self, path=None):
        self.crypto_numbits = 4096

        if path is None:
            path = defdir

        s_common.gendir(path, 'cas')
        s_common.gendir(path, 'hosts')
        s_common.gendir(path, 'users')

        self.certdir = s_common.reqdir(path)

    def genCaCert(self, name, signas=None, outp=None):
        pkey, cert = self._genBasePkeyCert(name)
        ext0 = crypto.X509Extension(b'basicConstraints', False, b'CA:TRUE')
        cert.add_extensions([ext0])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        keypath = self._savePkeyTo(pkey, 'cas', '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'cas', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCert(self, name, signas=None, outp=None, pkey=None, sans=None):
        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        ext_sans = {'DNS:' + name}
        if isinstance(sans, str):
            ext_sans = ext_sans.union(sans.split(','))
        ext_sans = ','.join(sorted(ext_sans))

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'server'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature,keyEncipherment'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'serverAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
            crypto.X509Extension(b'subjectAltName', False, ext_sans.encode('utf-8')),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'hosts', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'hosts', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'hosts', outp=outp)

    def genUserCert(self, name, signas=None, outp=None, pkey=None):

        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'client'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        crtpath = self._saveCertTo(cert, 'users', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'users', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        return pkey, cert

    def genClientCert(self, name, outp=None):

        ucert = self.getUserCert(name)
        if not ucert:
            raise s_common.NoSuchFile('missing User cert')

        cacert = self._loadCertPath(self._getCaPath(ucert))
        if not cacert:
            raise s_common.NoSuchFile('missing CA cert')

        ukey = self.getUserKey(name)
        if not ukey:
            raise s_common.NoSuchFile('missing User private key')

        ccert = crypto.PKCS12()
        ccert.set_friendlyname(name.encode('utf-8'))
        ccert.set_ca_certificates([cacert])
        ccert.set_certificate(ucert)
        ccert.set_privatekey(ukey)

        crtpath = self._saveP12To(ccert, 'users', '%s.p12' % name)
        if outp is not None:
            outp.printf('client cert saved: %s' % (crtpath,))

    def genUserCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'users', outp=outp)

    def getCaCert(self, name):
        return self._loadCertPath(self.getCaCertPath(name))

    def getCaCertPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getCaKey(self, name):
        return self._loadKeyPath(self.getCaKeyPath(name))

    def getCaKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getClientCert(self, name):
        '''
        Loads the PKCS12 object for a given client certificate.

        Example:
            mypkcs12 = cdir.getClientCert('mycert')

        Args:
            name (str): The name of the client certificate.

        Returns:
            OpenSSL.crypto.PKCS12: The certificate if exists.
        '''
        return self._loadP12Path(self.getClientCertPath(name))

    def getClientCertPath(self, name):
        '''
        Gets the path to a client certificate.

        Example:
            mypath = cdir.getClientCertPath('mycert')

        Args:
            name (str): The name of the client certificate.

        Returns:
            str: The path if exists.
        '''
        path = s_common.genpath(self.certdir, 'users', '%s.p12' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostCaPath(self, name):
        cert = self.getHostCert(name)
        if cert is None:
            return None

        return self._getCaPath(cert)

    def getHostCert(self, name):
        return self._loadCertPath(self.getHostCertPath(name))

    def getHostCertPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostKey(self, name):
        return self._loadKeyPath(self.getHostKeyPath(name))

    def getHostKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCaPath(self, name):
        cert = self.getUserCert(name)
        if cert is None:
            return None

        return self._getCaPath(cert)

    def getUserCert(self, name):
        return self._loadCertPath(self.getUserCertPath(name))

    def getUserCertPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserForHost(self, user, host):
        for name in iterFqdnUp(host):
            usercert = '%s@%s' % (user, name)
            if self.isUserCert(usercert):
                return usercert

    def getUserKey(self, name):
        return self._loadKeyPath(self.getUserKeyPath(name))

    def getUserKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getPathJoin(self, *paths):
        return s_common.genpath(self.certdir, *paths)

    def isCaCert(self, name):
        crtpath = self.getPathJoin('cas', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isClientCert(self, name):
        '''
        Checks if a client certificate exists.

        Example:
            exists = cdir.isClientCert('mycert')

        Args:
            name (str): The name of the client certificate.

        Returns:
            bool: True if the certificate is present, False otherwise.
        '''
        crtpath = self.getPathJoin('users', '%s.p12' % name)
        return os.path.isfile(crtpath)

    def isHostCert(self, name):
        crtpath = self.getPathJoin('hosts', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isUserCert(self, name):
        crtpath = self.getPathJoin('users', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def signCertAs(self, cert, signas):
        cakey = self.getCaKey(signas)
        cacert = self.getCaCert(signas)

        cert.set_issuer(cacert.get_subject())
        cert.sign(cakey, 'sha256')

    def signHostCsr(self, xcsr, signas, outp=None, sans=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genHostCert(name, pkey=pkey, signas=signas, outp=outp, sans=sans)

    def selfSignCert(self, cert, pkey):
        cert.set_issuer(cert.get_subject())
        cert.sign(pkey, 'sha256')

    def signUserCsr(self, xcsr, signas, outp=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genUserCert(name, pkey=pkey, signas=signas, outp=outp)

    def _checkDupFile(self, path):
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

    def _genBasePkeyCert(self, name, pkey=None):

        if pkey is None:
            pkey = crypto.PKey()
            pkey.generate_key(crypto.TYPE_RSA, self.crypto_numbits)

        cert = crypto.X509()
        cert.set_pubkey(pkey)
        cert.set_version(2)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)

        cert.set_serial_number(int(s_common.guid(), 16))
        cert.get_subject().CN = name

        return pkey, cert

    def _genPkeyCsr(self, name, mode, outp=None):
        pkey = crypto.PKey()
        pkey.generate_key(crypto.TYPE_RSA, self.crypto_numbits)

        xcsr = crypto.X509Req()
        xcsr.get_subject().CN = name

        xcsr.set_pubkey(pkey)
        xcsr.sign(pkey, 'sha256')

        keypath = self._savePkeyTo(pkey, mode, '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        csrpath = self.getPathJoin(mode, '%s.csr' % name)
        self._checkDupFile(csrpath)

        with s_common.genfile(csrpath) as fd:
            fd.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, xcsr))

        if outp is not None:
            outp.printf('csr saved: %s' % (csrpath,))

    def _getCaPath(self, cert):

        subj = cert.get_issuer()
        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def _getPathBytes(self, path):
        if path is None:
            return None
        return s_common.getbytes(path)

    def _loadCertPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_certificate(crypto.FILETYPE_PEM, byts)

    def _loadCsrPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_certificate_request(crypto.FILETYPE_PEM, byts)

    def _loadKeyPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_privatekey(crypto.FILETYPE_PEM, byts)

    def _loadP12Path(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_pkcs12(byts)

    def _saveCertTo(self, cert, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

        return path

    def _savePkeyTo(self, pkey, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))

        return path

    def _saveP12To(self, cert, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(cert.export())

        return path
<commit_msg>Adding docstrings<commit_after>import os

from OpenSSL import crypto

import synapse.common as s_common

defdir = os.getenv('SYN_CERT_DIR')
if defdir is None:
    defdir = '~/.syn/certs'

def iterFqdnUp(fqdn):
    levs = fqdn.split('.')
    for i in range(len(levs)):
        yield '.'.join(levs[i:])

class CertDir:

    def __init__(self, path=None):
        self.crypto_numbits = 4096

        if path is None:
            path = defdir

        s_common.gendir(path, 'cas')
        s_common.gendir(path, 'hosts')
        s_common.gendir(path, 'users')

        self.certdir = s_common.reqdir(path)

    def genCaCert(self, name, signas=None, outp=None):
        pkey, cert = self._genBasePkeyCert(name)
        ext0 = crypto.X509Extension(b'basicConstraints', False, b'CA:TRUE')
        cert.add_extensions([ext0])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        keypath = self._savePkeyTo(pkey, 'cas', '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'cas', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCert(self, name, signas=None, outp=None, pkey=None, sans=None):
        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        ext_sans = {'DNS:' + name}
        if isinstance(sans, str):
            ext_sans = ext_sans.union(sans.split(','))
        ext_sans = ','.join(sorted(ext_sans))

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'server'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature,keyEncipherment'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'serverAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
            crypto.X509Extension(b'subjectAltName', False, ext_sans.encode('utf-8')),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'hosts', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        crtpath = self._saveCertTo(cert, 'hosts', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        return pkey, cert

    def genHostCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'hosts', outp=outp)

    def genUserCert(self, name, signas=None, outp=None, pkey=None):

        pkey, cert = self._genBasePkeyCert(name, pkey=pkey)

        cert.add_extensions([
            crypto.X509Extension(b'nsCertType', False, b'client'),
            crypto.X509Extension(b'keyUsage', False, b'digitalSignature'),
            crypto.X509Extension(b'extendedKeyUsage', False, b'clientAuth'),
            crypto.X509Extension(b'basicConstraints', False, b'CA:FALSE'),
        ])

        if signas is not None:
            self.signCertAs(cert, signas)
        else:
            self.selfSignCert(cert, pkey)

        crtpath = self._saveCertTo(cert, 'users', '%s.crt' % name)
        if outp is not None:
            outp.printf('cert saved: %s' % (crtpath,))

        if not pkey._only_public:
            keypath = self._savePkeyTo(pkey, 'users', '%s.key' % name)
            if outp is not None:
                outp.printf('key saved: %s' % (keypath,))

        return pkey, cert

    def genClientCert(self, name, outp=None):

        ucert = self.getUserCert(name)
        if not ucert:
            raise s_common.NoSuchFile('missing User cert')

        cacert = self._loadCertPath(self._getCaPath(ucert))
        if not cacert:
            raise s_common.NoSuchFile('missing CA cert')

        ukey = self.getUserKey(name)
        if not ukey:
            raise s_common.NoSuchFile('missing User private key')

        ccert = crypto.PKCS12()
        ccert.set_friendlyname(name.encode('utf-8'))
        ccert.set_ca_certificates([cacert])
        ccert.set_certificate(ucert)
        ccert.set_privatekey(ukey)

        crtpath = self._saveP12To(ccert, 'users', '%s.p12' % name)
        if outp is not None:
            outp.printf('client cert saved: %s' % (crtpath,))

    def genUserCsr(self, name, outp=None):
        return self._genPkeyCsr(name, 'users', outp=outp)

    def getCaCert(self, name):
        '''
        Loads the X509 object for a given CA keypair.

        Example:
            myca = cdir.getCaCert('mycacert')

        Args:
            name (str): The name of the CA keypair.

        Returns:
            OpenSSL.crypto.X509: The certificate, if exists.
        '''
        return self._loadCertPath(self.getCaCertPath(name))

    def getCaCertPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getCaKey(self, name):
        '''
        Loads the PKey object for a given CA keypair.

        Example:
            mycakey = cdir.getCaKey('mycakey')

        Args:
            name (str): The name of the CA keypair.

        Returns:
            OpenSSL.crypto.PKey: The private key, if exists.
        '''
        return self._loadKeyPath(self.getCaKeyPath(name))

    def getCaKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'cas', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getClientCert(self, name):
        '''
        Loads the PKCS12 object for a given user keypair.

        Example:
            mypkcs12 = cdir.getClientCert('mycert')

        Args:
            name (str): The name of the user keypair.

        Returns:
            OpenSSL.crypto.PKCS12: The PKCS12 archive, if exists.
        '''
        return self._loadP12Path(self.getClientCertPath(name))

    def getClientCertPath(self, name):
        '''
        Gets the path to a client certificate.

        Example:
            mypath = cdir.getClientCertPath('mycert')

        Args:
            name (str): The name of the client certificate.

        Returns:
            str: The path if exists.
        '''
        path = s_common.genpath(self.certdir, 'users', '%s.p12' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostCaPath(self, name):
        cert = self.getHostCert(name)
        if cert is None:
            return None

        return self._getCaPath(cert)

    def getHostCert(self, name):
        return self._loadCertPath(self.getHostCertPath(name))

    def getHostCertPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getHostKey(self, name):
        return self._loadKeyPath(self.getHostKeyPath(name))

    def getHostKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'hosts', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserCaPath(self, name):
        cert = self.getUserCert(name)
        if cert is None:
            return None

        return self._getCaPath(cert)

    def getUserCert(self, name):
        return self._loadCertPath(self.getUserCertPath(name))

    def getUserCertPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.crt' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getUserForHost(self, user, host):
        for name in iterFqdnUp(host):
            usercert = '%s@%s' % (user, name)
            if self.isUserCert(usercert):
                return usercert

    def getUserKey(self, name):
        return self._loadKeyPath(self.getUserKeyPath(name))

    def getUserKeyPath(self, name):
        path = s_common.genpath(self.certdir, 'users', '%s.key' % name)
        if not os.path.isfile(path):
            return None
        return path

    def getPathJoin(self, *paths):
        return s_common.genpath(self.certdir, *paths)

    def isCaCert(self, name):
        crtpath = self.getPathJoin('cas', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isClientCert(self, name):
        '''
        Checks if a client certificate exists.

        Example:
            exists = cdir.isClientCert('mycert')

        Args:
            name (str): The name of the client certificate.

        Returns:
            bool: True if the certificate is present, False otherwise.
        '''
        crtpath = self.getPathJoin('users', '%s.p12' % name)
        return os.path.isfile(crtpath)

    def isHostCert(self, name):
        crtpath = self.getPathJoin('hosts', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def isUserCert(self, name):
        crtpath = self.getPathJoin('users', '%s.crt' % name)
        return os.path.isfile(crtpath)

    def signCertAs(self, cert, signas):
        cakey = self.getCaKey(signas)
        cacert = self.getCaCert(signas)

        cert.set_issuer(cacert.get_subject())
        cert.sign(cakey, 'sha256')

    def signHostCsr(self, xcsr, signas, outp=None, sans=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genHostCert(name, pkey=pkey, signas=signas, outp=outp, sans=sans)

    def selfSignCert(self, cert, pkey):
        cert.set_issuer(cert.get_subject())
        cert.sign(pkey, 'sha256')

    def signUserCsr(self, xcsr, signas, outp=None):
        pkey = xcsr.get_pubkey()
        name = xcsr.get_subject().CN
        return self.genUserCert(name, pkey=pkey, signas=signas, outp=outp)

    def _checkDupFile(self, path):
        if os.path.isfile(path):
            raise s_common.DupFileName(path=path)

    def _genBasePkeyCert(self, name, pkey=None):

        if pkey is None:
            pkey = crypto.PKey()
            pkey.generate_key(crypto.TYPE_RSA, self.crypto_numbits)

        cert = crypto.X509()
        cert.set_pubkey(pkey)
        cert.set_version(2)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)

        cert.set_serial_number(int(s_common.guid(), 16))
        cert.get_subject().CN = name

        return pkey, cert

    def _genPkeyCsr(self, name, mode, outp=None):
        pkey = crypto.PKey()
        pkey.generate_key(crypto.TYPE_RSA, self.crypto_numbits)

        xcsr = crypto.X509Req()
        xcsr.get_subject().CN = name

        xcsr.set_pubkey(pkey)
        xcsr.sign(pkey, 'sha256')

        keypath = self._savePkeyTo(pkey, mode, '%s.key' % name)
        if outp is not None:
            outp.printf('key saved: %s' % (keypath,))

        csrpath = self.getPathJoin(mode, '%s.csr' % name)
        self._checkDupFile(csrpath)

        with s_common.genfile(csrpath) as fd:
            fd.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, xcsr))

        if outp is not None:
            outp.printf('csr saved: %s' % (csrpath,))

    def _getCaPath(self, cert):

        subj = cert.get_issuer()
        capath = self.getPathJoin('cas', '%s.crt' % subj.CN)
        if not os.path.isfile(capath):
            return None

        return capath

    def _getPathBytes(self, path):
        if path is None:
            return None
        return s_common.getbytes(path)

    def _loadCertPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_certificate(crypto.FILETYPE_PEM, byts)

    def _loadCsrPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_certificate_request(crypto.FILETYPE_PEM, byts)

    def _loadKeyPath(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_privatekey(crypto.FILETYPE_PEM, byts)

    def _loadP12Path(self, path):
        byts = self._getPathBytes(path)
        if byts:
            return crypto.load_pkcs12(byts)

    def _saveCertTo(self, cert, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

        return path

    def _savePkeyTo(self, pkey, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))

        return path

    def _saveP12To(self, cert, *paths):
        path = self.getPathJoin(*paths)
        self._checkDupFile(path)

        with s_common.genfile(path) as fd:
            fd.write(cert.export())

        return path
<|endoftext|>