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
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()
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
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)
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
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()
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
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
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
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)
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