Exemplo n.º 1
0
def probe_sni(
        name: bytes,
        host: bytes,
        port: int = 443,
        timeout: int = 300,  # pylint: disable=too-many-arguments
        method: int = _DEFAULT_SSL_METHOD,
        source_address: Tuple[str, int] = ('', 0),
        alpn_protocols: Optional[Sequence[bytes]] = None) -> crypto.X509:
    """Probe SNI server for SSL certificate.

    :param bytes name: Byte string to send as the server name in the
        client hello message.
    :param bytes host: Host to connect to.
    :param int port: Port to connect to.
    :param int timeout: Timeout in seconds.
    :param method: See `OpenSSL.SSL.Context` for allowed values.
    :param tuple source_address: Enables multi-path probing (selection
        of source interface). See `socket.creation_connection` for more
        info. Available only in Python 2.7+.
    :param alpn_protocols: Protocols to request using ALPN.
    :type alpn_protocols: `Sequence` of `bytes`

    :raises acme.errors.Error: In case of any problems.

    :returns: SSL certificate presented by the server.
    :rtype: OpenSSL.crypto.X509

    """
    context = SSL.Context(method)
    context.set_timeout(timeout)

    socket_kwargs = {'source_address': source_address}

    try:
        logger.debug(
            "Attempting to connect to %s:%d%s.", host, port,
            " from {0}:{1}".format(source_address[0], source_address[1])
            if any(source_address) else "")
        socket_tuple: Tuple[bytes, int] = (host, port)
        sock = socket.create_connection(
            socket_tuple, **socket_kwargs)  # type: ignore[arg-type]
    except socket.error as error:
        raise errors.Error(error)

    with contextlib.closing(sock) as client:
        client_ssl = SSL.Connection(context, client)
        client_ssl.set_connect_state()
        client_ssl.set_tlsext_host_name(name)  # pyOpenSSL>=0.13
        if alpn_protocols is not None:
            client_ssl.set_alpn_protos(alpn_protocols)
        try:
            client_ssl.do_handshake()
            client_ssl.shutdown()
        except SSL.Error as error:
            raise errors.Error(error)
    cert = client_ssl.get_peer_certificate()
    assert cert  # Appease mypy. We would have crashed out by now if there was no certificate.
    return cert
Exemplo n.º 2
0
def probe_sni(name,
              host,
              port=443,
              timeout=300,
              method=_DEFAULT_TLSSNI01_SSL_METHOD,
              source_address=('', 0)):
    """Probe SNI server for SSL certificate.

    :param bytes name: Byte string to send as the server name in the
        client hello message.
    :param bytes host: Host to connect to.
    :param int port: Port to connect to.
    :param int timeout: Timeout in seconds.
    :param method: See `OpenSSL.SSL.Context` for allowed values.
    :param tuple source_address: Enables multi-path probing (selection
        of source interface). See `socket.creation_connection` for more
        info. Available only in Python 2.7+.

    :raises acme.errors.Error: In case of any problems.

    :returns: SSL certificate presented by the server.
    :rtype: OpenSSL.crypto.X509

    """
    context = SSL.Context(method)
    context.set_timeout(timeout)

    socket_kwargs = {'source_address': source_address}

    host_protocol_agnostic = host
    if host == '::' or host == '0':
        # https://github.com/python/typeshed/pull/2136
        # while PR is not merged, we need to ignore
        host_protocol_agnostic = None

    try:
        # pylint: disable=star-args
        logger.debug(
            "Attempting to connect to %s:%d%s.",
            host_protocol_agnostic, port, " from {0}:{1}".format(
                source_address[0], source_address[1]) if socket_kwargs else "")
        socket_tuple = (host_protocol_agnostic, port
                        )  # type: Tuple[Optional[str], int]
        sock = socket.create_connection(socket_tuple,
                                        **socket_kwargs)  # type: ignore
    except socket.error as error:
        raise errors.Error(error)

    with contextlib.closing(sock) as client:
        client_ssl = SSL.Connection(context, client)
        client_ssl.set_connect_state()
        client_ssl.set_tlsext_host_name(name)  # pyOpenSSL>=0.13
        try:
            client_ssl.do_handshake()
            client_ssl.shutdown()
        except SSL.Error as error:
            raise errors.Error(error)
    return client_ssl.get_peer_certificate()
Exemplo n.º 3
0
    def fetch_chain(self, certr, max_length=10):
        """Fetch chain for certificate.

        :param .CertificateResource certr: Certificate Resource
        :param int max_length: Maximum allowed length of the chain.
            Note that each element in the certificate requires new
            ``HTTP GET`` request, and the length of the chain is
            controlled by the ACME CA.

        :raises errors.Error: if recursion exceeds `max_length`

        :returns: Certificate chain for the Certificate Resource. It is
            a list ordered so that the first element is a signer of the
            certificate from Certificate Resource. Will be empty if
            ``cert_chain_uri`` is ``None``.
        :rtype: `list` of `OpenSSL.crypto.X509` wrapped in `.ComparableX509`

        """
        chain = []  # type: List[jose.ComparableX509]
        uri = certr.cert_chain_uri
        while uri is not None and len(chain) < max_length:
            response, cert = self._get_cert(uri)
            uri = response.links.get('up', {}).get('url')
            chain.append(cert)
        if uri is not None:
            raise errors.Error(
                "Recursion limit reached. Didn't get {0}".format(uri))
        return chain
Exemplo n.º 4
0
 def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes:
     if isinstance(cert, jose.ComparableX509):
         if isinstance(cert.wrapped, crypto.X509Req):
             raise errors.Error(
                 "Unexpected CSR provided.")  # pragma: no cover
         cert = cert.wrapped
     return crypto.dump_certificate(filetype, cert)
Exemplo n.º 5
0
    def simple_verify(self, chall, domain, account_public_key):
        """Simple verify.

        :param challenges.DNS01 chall: Corresponding challenge.
        :param unicode domain: Domain name being verified.
        :param JWK account_public_key: Public key for the key pair
            being authorized.

        :returns: ``True`` iff validation with the TXT records resolved from a
            DNS server is successful.
        :rtype: bool

        """
        if not self.verify(chall, account_public_key):
            logger.debug(
                "Verification of key authorization in response failed")
            return False

        validation_domain_name = chall.validation_domain_name(domain)
        validation = chall.validation(account_public_key)
        logger.debug("Verifying %s at %s...", chall.typ,
                     validation_domain_name)

        try:
            from acme import dns_resolver
        except ImportError:  # pragma: no cover
            raise errors.Error("Local validation for 'dns-01' challenges "
                               "requires 'dnspython'")
        txt_records = dns_resolver.txt_records_for_name(validation_domain_name)
        exists = validation in txt_records
        if not exists:
            logger.debug(
                "Key authorization from response (%r) doesn't match "
                "any DNS response in %r", self.key_authorization, txt_records)
        return exists
Exemplo n.º 6
0
def probe_sni(name,
              host,
              port=443,
              timeout=300,
              method=_DEFAULT_TLSSNI01_SSL_METHOD,
              source_address=('0', 0)):
    """Probe SNI server for SSL certificate.

    :param bytes name: Byte string to send as the server name in the
        client hello message.
    :param bytes host: Host to connect to.
    :param int port: Port to connect to.
    :param int timeout: Timeout in seconds.
    :param method: See `OpenSSL.SSL.Context` for allowed values.
    :param tuple source_address: Enables multi-path probing (selection
        of source interface). See `socket.creation_connection` for more
        info. Available only in Python 2.7+.

    :raises acme.errors.Error: In case of any problems.

    :returns: SSL certificate presented by the server.
    :rtype: OpenSSL.crypto.X509

    """
    context = OpenSSL.SSL.Context(method)
    context.set_timeout(timeout)

    socket_kwargs = {} if sys.version_info < (2, 7) else {
        'source_address': source_address
    }

    try:
        # pylint: disable=star-args
        sock = socket.create_connection((host, port), **socket_kwargs)
    except socket.error as error:
        raise errors.Error(error)

    with contextlib.closing(sock) as client:
        client_ssl = OpenSSL.SSL.Connection(context, client)
        client_ssl.set_connect_state()
        client_ssl.set_tlsext_host_name(name)  # pyOpenSSL>=0.13
        try:
            client_ssl.do_handshake()
            client_ssl.shutdown()
        except OpenSSL.SSL.Error as error:
            raise errors.Error(error)
    return client_ssl.get_peer_certificate()
Exemplo n.º 7
0
    def obtain_certificate_from_csr(self, domains, csr, authzr=None):
        """Obtain certificate.

        Internal function with precondition that `domains` are
        consistent with identifiers present in the `csr`.

        :param list domains: Domain names.
        :param .util.CSR csr: PEM-encoded Certificate Signing
            Request. The key used to generate this CSR can be different
            than `authkey`.
        :param list authzr: List of
            :class:`acme.messages.AuthorizationResource`

        :returns: `.CertificateResource` and certificate chain (as
            returned by `.fetch_chain`).
        :rtype: tuple

        """
        if self.auth_handler is None:
            msg = ("Unable to obtain certificate because authenticator is "
                   "not set.")
            logger.warning(msg)
            raise errors.Error(msg)
        if self.account.regr is None:
            raise errors.Error("Please register with the ACME server first.")

        logger.debug("CSR: %s, domains: %s", csr, domains)

        if authzr is None:
            authzr = self.auth_handler.get_authorizations(domains)

        certr = self.acme.request_issuance(
            jose.ComparableX509(
                OpenSSL.crypto.load_certificate_request(
                    OpenSSL.crypto.FILETYPE_PEM, csr.data)), authzr)

        notify = zope.component.getUtility(interfaces.IDisplay).notification
        retries = 0
        chain = None

        while retries <= 1:
            if retries:
                notify(
                    'Failed to fetch chain, please check your network '
                    'and continue',
                    pause=True)
            try:
                chain = self.acme.fetch_chain(certr)
                break
            except acme_errors.Error:
                logger.debug('Failed to fetch chain', exc_info=True)
                retries += 1

        if chain is None:
            raise acme_errors.Error(
                'Failed to fetch chain. You should not deploy the generated '
                'certificate, please rerun the command for a new one.')

        return certr, chain
Exemplo n.º 8
0
 def _mock_deactivate(authzr):
     if authzr.body.status == messages.STATUS_VALID:
         if authzr.body.identifier.value == "is_valid_but_will_fail":
             raise acme_errors.Error("Mock deactivation ACME error")
         authzb = authzr.body.update(status=messages.STATUS_DEACTIVATED)
         authzr = messages.AuthorizationResource(body=authzb)
     else:  # pragma: no cover
         raise errors.Error("Can't deactivate non-valid authz")
     return authzr
Exemplo n.º 9
0
    def obtain_certificate_from_csr(self, csr, orderr=None):
        """Obtain certificate.

        :param .util.CSR csr: PEM-encoded Certificate Signing
            Request. The key used to generate this CSR can be different
            than `authkey`.
        :param acme.messages.OrderResource orderr: contains authzrs

        :returns: `.CertificateResource` and certificate chain (as
            returned by `.fetch_chain`).
        :rtype: tuple

        """
        if self.auth_handler is None:
            msg = ("Unable to obtain certificate because authenticator is "
                   "not set.")
            logger.warning(msg)
            raise errors.Error(msg)
        if self.account.regr is None:
            raise errors.Error("Please register with the ACME server first.")

        logger.debug("CSR: %s", csr)

        if orderr is None:
            orderr = self.acme.new_order(csr.data)
            authzr = self.auth_handler.handle_authorizations(orderr)
            orderr = orderr.update(authorizations=authzr)
        authzr = orderr.authorizations

        certr = self.acme.request_issuance(
            jose.ComparableX509(
                OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr.data)),
                authzr)

        notify = zope.component.getUtility(interfaces.IDisplay).notification
        retries = 0
        chain = None

        while retries <= 1:
            if retries:
                notify('Failed to fetch chain, please check your network '
                       'and continue', pause=True)
            try:
                chain = self.acme.fetch_chain(certr)
                break
            except acme_errors.Error:
                logger.debug('Failed to fetch chain', exc_info=True)
                retries += 1

        if chain is None:
            raise acme_errors.Error(
                'Failed to fetch chain. You should not deploy the generated '
                'certificate, please rerun the command for a new one.')

        return certr, chain
Exemplo n.º 10
0
def _serve_sni(certs,
               sock,
               reuseaddr=True,
               method=_DEFAULT_DVSNI_SSL_METHOD,
               accept=None):
    """Start SNI-enabled server, that drops connection after handshake.

    :param certs: Mapping from SNI name to ``(key, cert)`` `tuple`.
    :param sock: Already bound socket.
    :param bool reuseaddr: Should `socket.SO_REUSEADDR` be set?
    :param method: See `OpenSSL.SSL.Context` for allowed values.
    :param accept: Callable that doesn't take any arguments and
        returns ``True`` if more connections should be served.

    """
    def _pick_certificate(connection):
        try:
            key, cert = certs[connection.get_servername()]
        except KeyError:
            return
        new_context = OpenSSL.SSL.Context(method)
        new_context.use_privatekey(key)
        new_context.use_certificate(cert)
        connection.set_context(new_context)

    if reuseaddr:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.listen(1)  # TODO: add func arg?

    while accept is None or accept():
        server, addr = sock.accept()
        logger.debug('Received connection from %s', addr)

        with contextlib.closing(server):
            context = OpenSSL.SSL.Context(method)
            context.set_tlsext_servername_callback(_pick_certificate)

            server_ssl = OpenSSL.SSL.Connection(context, server)
            server_ssl.set_accept_state()
            try:
                server_ssl.do_handshake()
                server_ssl.shutdown()
            except OpenSSL.SSL.Error as error:
                raise errors.Error(error)
Exemplo n.º 11
0
 def nonce(value):  # pylint: disable=missing-docstring,no-self-argument
     error = Header.validate_nonce(value)
     if error is not None:
         # TODO: custom error
         raise errors.Error("Invalid nonce: {0}".format(error))
     return value