def _test_registration(self): return ( DeferredContext(self._test_create_client()) .addCallback(partial(setattr, self, 'client')) .addCallback(lambda _: self._test_register()) .addCallback(tap( lambda reg1: self.assertEqual(reg1.body.contact, ()))) .addCallback(tap( lambda reg1: self._test_register( NewRegistration.from_data(email=u'*****@*****.**')) .addCallback(tap( lambda reg2: self.assertEqual(reg1.uri, reg2.uri))) .addCallback(lambda reg2: self.assertEqual( reg2.body.contact, (u'mailto:[email protected]',))))) .addCallback(self._test_agree_to_tos) .addCallback( lambda _: self._test_request_challenges(self.HOST)) .addCallback(partial(setattr, self, 'authzr')) .addCallback(lambda _: self._create_responder()) .addCallback(tap(lambda _: self._test_poll_pending(self.authzr))) .addCallback(self._test_answer_challenge) .addCallback(tap(lambda _: self._test_poll(self.authzr))) .addCallback(lambda stop_responding: stop_responding()) .addCallback(lambda _: self._test_issue(self.HOST)) .addCallback(self._test_chain) .addActionFinish())
def poll(self, authzr): """ Update an authorization from the server (usually to check its status). """ action = LOG_ACME_POLL_AUTHORIZATION(authorization=authzr) with action.context(): return ( DeferredContext(self._client.get(authzr.uri)) # Spec says we should get 202 while pending, Boulder actually # sends us 200 always, so just don't check. # .addCallback(self._expect_response, http.ACCEPTED) .addCallback( lambda res: self._parse_authorization(res, uri=authzr.uri) .addCallback( self._check_authorization, authzr.body.identifier) .addCallback( lambda authzr: (authzr, self.retry_after(res, _now=self._clock.seconds))) ) .addCallback(tap( lambda a_r: action.add_success_fields( authorization=a_r[0], retry_after=a_r[1]))) .addActionFinish())
def _send_request(self, method, url, *args, **kwargs): """ Send HTTP request. :param str method: The HTTP method to use. :param str url: The URL to make the request to. :return: Deferred firing with the HTTP response. """ if self._current_request is not None: return defer.fail(RuntimeError('Overlapped HTTP request')) def cb_request_done(result): """ Called when we got a response from the request. """ self._current_request = None return result action = LOG_JWS_REQUEST(url=url) with action.context(): headers = kwargs.setdefault('headers', Headers()) headers.setRawHeaders(b'user-agent', [self._user_agent]) kwargs.setdefault('timeout', self.timeout) self._current_request = self._treq.request(method, url, *args, **kwargs) return (DeferredContext(self._current_request).addCallback( cb_request_done).addCallback( tap(lambda r: action.add_success_fields( code=r.code, content_type=r.headers.getRawHeaders( b'content-type', [None])[0]))).addActionFinish())
def answer_challenge(self, challenge_body, response): """ Respond to an authorization challenge. This send a POST with the empty object '{}' as the payload. :param ~acme.messages.ChallengeBody challenge_body: The challenge being responded to. :param ~acme.challenges.ChallengeResponse response: The response to the challenge. :return: The updated challenge resource. :rtype: Deferred[`~acme.messages.ChallengeResource`] """ action = LOG_ACME_ANSWER_CHALLENGE(challenge_body=challenge_body, response=response) if challenge_body.status != STATUS_PENDING: # We already have an answer. return challenge_body with action.context(): return (DeferredContext( self._client.post( challenge_body.uri, jose.JSONObjectWithFields())).addCallback( self._parse_challenge).addCallback( self._check_challenge, challenge_body).addCallback( tap(lambda c: action.add_success_fields( challenge_resource=c))).addActionFinish())
def register(self, email=None): """ Create a new registration with the ACME server or update an existing account. It should be called before doing any ACME requests. :param str: Comma separated contact emails used by the account. :return: The registration resource. :rtype: Deferred[`~acme.messages.RegistrationResource`] """ uri = self.directory.newAccount new_reg = messages.Registration.from_data( email=email, terms_of_service_agreed=True, ) action = LOG_ACME_REGISTER(registration=new_reg) with action.context(): return (DeferredContext( self._client.post(uri, new_reg)).addCallback( self._cb_check_existing_account, new_reg).addCallback( self._cb_check_registration).addCallback( tap(lambda r: action.add_success_fields( registration=r))).addActionFinish())
def _create_client(self, key): return ( Client.from_url(reactor, LETSENCRYPT_STAGING_DIRECTORY, key=key) .addCallback(tap( lambda client: self.addCleanup( client._client._treq._agent._pool.closeCachedConnections))) )
def from_url(cls, reactor, url, key, alg=jose.RS256, jws_client=None): """ Construct a client from an ACME directory at a given URL. :param url: The ``twisted.python.url.URL`` to fetch the directory from. See `txacme.urls` for constants for various well-known public directories. :param reactor: The Twisted reactor to use. :param ~acme.jose.jwk.JWK key: The client key to use. :param alg: The signing algorithm to use. Needs to be compatible with the type of key used. :param JWSClient jws_client: The underlying client to use, or ``None`` to construct one. :return: The constructed client. :rtype: Deferred[`Client`] """ action = LOG_ACME_CONSUME_DIRECTORY( url=url, key_type=key.typ, alg=alg.name) with action.context(): check_directory_url_type(url) jws_client = _default_client(jws_client, reactor, key, alg) return ( DeferredContext(jws_client.get(url.asText())) .addCallback(json_content) .addCallback(messages.Directory.from_json) .addCallback( tap(lambda d: action.add_success_fields(directory=d))) .addCallback(cls, reactor, key, jws_client) .addActionFinish())
def update_registration(self, regr, uri=None): """ Submit a registration to the server to update it. :param ~acme.messages.RegistrationResource regr: The registration to update. Can be a :class:`~acme.messages.NewRegistration` instead, in order to create a new registration. :param str uri: The url to submit to. Must be specified if a :class:`~acme.messages.NewRegistration` is provided. :return: The updated registration resource. :rtype: Deferred[`~acme.messages.RegistrationResource`] """ if uri is None: uri = regr.uri if isinstance(regr, messages.RegistrationResource): message = messages.UpdateRegistration(**dict(regr.body)) else: message = regr action = LOG_ACME_UPDATE_REGISTRATION(uri=uri, registration=message) with action.context(): return ( DeferredContext(self._client.post(uri, message)) .addCallback(self._parse_regr_response, uri=uri) .addCallback(self._check_regr, regr) .addCallback( tap(lambda r: action.add_success_fields(registration=r))) .addActionFinish())
def from_url(cls, reactor, url, key, alg=jose.RS256, jws_client=None): """ Construct a client from an ACME directory at a given URL. :param url: The ``twisted.python.url.URL`` to fetch the directory from. :param reactor: The Twisted reactor to use. :param ~acme.jose.jwk.JWK key: The client key to use. :param alg: The signing algorithm to use. Needs to be compatible with the type of key used. :param JWSClient jws_client: The underlying client to use, or ``None`` to construct one. :return: The constructed client. :rtype: Deferred[`Client`] """ action = LOG_ACME_CONSUME_DIRECTORY( url=url, key_type=key.typ, alg=alg.name) with action.context(): jws_client = _default_client(jws_client, reactor, key, alg) return ( DeferredContext(jws_client.get(url.asText())) .addCallback(json_content) .addCallback(messages.Directory.from_json) .addCallback( tap(lambda d: action.add_success_fields(directory=d))) .addCallback(cls, reactor, key, jws_client) .addActionFinish())
def stopService(self): Service.stopService(self) self.ready = False for d in list(self._waiting): d.cancel() self._waiting = [] def stop_timer(ignored): if not self._timer_service: return return self._timer_service.stopService() def cleanup(ignored): self._timer_service = None return (self._client.stop().addBoth(tap(stop_timer)).addBoth( tap(cleanup)))
def got_challenge(r): responder, response = r def stop_responding(result): return responder.stop_responding(response) return ( poll_until_valid(authzr, self._clock, self._client) .addBoth(tap(stop_responding)))
def _get_nonce(self, url): """ Get a nonce to use in a request, removing it from the nonces on hand. """ action = LOG_JWS_GET_NONCE() if len(self._nonces) > 0: with action: nonce = self._nonces.pop() action.add_success_fields(nonce=nonce) return defer.succeed(nonce) else: with action.context(): return (DeferredContext(self.head( self._new_nonce)).addCallback(self._add_nonce).addCallback( lambda _: self._nonces.pop()).addCallback( tap(lambda nonce: action.add_success_fields( nonce=nonce))).addActionFinish())
def _get_nonce(self, url): """ Get a nonce to use in a request, removing it from the nonces on hand. """ action = LOG_JWS_GET_NONCE() if len(self._nonces) > 0: with action: nonce = self._nonces.pop() action.add_success_fields(nonce=nonce) return succeed(nonce) else: with action.context(): return ( DeferredContext(self.head(url)) .addCallback(self._add_nonce) .addCallback(lambda _: self._nonces.pop()) .addCallback(tap( lambda nonce: action.add_success_fields(nonce=nonce))) .addActionFinish())
def _send_request(self, method, url, *args, **kwargs): """ Send HTTP request. :param str method: The HTTP method to use. :param str url: The URL to make the request to. :return: Deferred firing with the HTTP response. """ action = LOG_JWS_REQUEST(url=url) with action.context(): headers = kwargs.setdefault('headers', Headers()) headers.setRawHeaders(b'user-agent', [self._user_agent]) kwargs.setdefault('timeout', self.timeout) return (DeferredContext( self._treq.request(method, url, *args, **kwargs)).addCallback( tap(lambda r: action.add_success_fields( code=r.code, content_type=r.headers.getRawHeaders( b'content-type', [None])[0]))).addActionFinish())
def register(self, new_reg=None): """ Create a new registration with the ACME server. :param ~acme.messages.NewRegistration new_reg: The registration message to use, or ``None`` to construct one. :return: The registration resource. :rtype: Deferred[`~acme.messages.RegistrationResource`] """ if new_reg is None: new_reg = messages.NewRegistration() action = LOG_ACME_REGISTER(registration=new_reg) with action.context(): return (DeferredContext( self.update_registration( new_reg, uri=self.directory[new_reg])).addErrback( self._maybe_registered, new_reg).addCallback( tap(lambda r: action.add_success_fields( registration=r))).addActionFinish())
def from_url( cls, reactor, url, key, alg=RS256, jws_client=None, timeout=_DEFAULT_TIMEOUT, ): """ Construct a client from an ACME directory at a given URL. At construct time, it validates the ACME directory. :param url: The ``twisted.python.url.URL`` to fetch the directory from. See `txacme.urls` for constants for various well-known public directories. :param reactor: The Twisted reactor to use. :param ~josepy.jwk.JWK key: The client key to use. :param alg: The signing algorithm to use. Needs to be compatible with the type of key used. :param JWSClient jws_client: The underlying client to use, or ``None`` to construct one. :param int timeout: Number of seconds to wait for an HTTP response during ACME server interaction. :return: The constructed client. :rtype: Deferred[`Client`] """ action = LOG_ACME_CONSUME_DIRECTORY(url=url, key_type=key.typ, alg=alg.name) with action.context(): check_directory_url_type(url) directory = url.asText() return (DeferredContext(jws_client=_default_client( jws_client, reactor, key, alg, directory, timeout )).addCallback( tap(lambda jws_client: action.add_success_fields( directory=directory))).addCallback(lambda jws_client: cls( reactor, key, jws_client)).addActionFinish())
def answer_challenge(self, challenge_body, response): """ Respond to an authorization challenge. :param ~acme.messages.ChallengeBody challenge_body: The challenge being responded to. :param ~acme.challenges.ChallengeResponse response: The response to the challenge. :return: The updated challenge resource. :rtype: Deferred[`~acme.messages.ChallengeResource`] """ action = LOG_ACME_ANSWER_CHALLENGE(challenge_body=challenge_body, response=response) with action.context(): return (DeferredContext( self._client.post(challenge_body.uri, response)).addCallback( self._parse_challenge).addCallback( self._check_challenge, challenge_body).addCallback( tap(lambda c: action.add_success_fields( challenge_resource=c))).addActionFinish())
def request_challenges(self, identifier): """ Create a new authorization. :param ~acme.messages.Identifier identifier: The identifier to authorize. :return: The new authorization resource. :rtype: Deferred[`~acme.messages.AuthorizationResource`] """ action = LOG_ACME_CREATE_AUTHORIZATION(identifier=identifier) with action.context(): message = messages.NewAuthorization(identifier=identifier) return ( DeferredContext( self._client.post(self.directory[message], message)) .addCallback(self._expect_response, http.CREATED) .addCallback(self._parse_authorization) .addCallback(self._check_authorization, identifier) .addCallback( tap(lambda a: action.add_success_fields(authorization=a))) .addActionFinish())
def register(self, new_reg=None): """ Create a new registration with the ACME server. :param ~acme.messages.NewRegistration new_reg: The registration message to use, or ``None`` to construct one. :return: The registration resource. :rtype: Deferred[`~acme.messages.RegistrationResource`] """ if new_reg is None: new_reg = messages.NewRegistration() action = LOG_ACME_REGISTER(registration=new_reg) with action.context(): return ( DeferredContext( self.update_registration( new_reg, uri=self.directory[new_reg])) .addErrback(self._maybe_registered) .addCallback( tap(lambda r: action.add_success_fields(registration=r))) .addActionFinish())
def _send_request(self, method, url, *args, **kwargs): """ Send HTTP request. :param str method: The HTTP method to use. :param str url: The URL to make the request to. :return: Deferred firing with the HTTP response. """ action = LOG_JWS_REQUEST(url=url) with action.context(): headers = kwargs.setdefault('headers', Headers()) headers.setRawHeaders(b'user-agent', [self._user_agent]) kwargs.setdefault('timeout', self.timeout) return ( DeferredContext( self._treq.request(method, url, *args, **kwargs)) .addCallback( tap(lambda r: action.add_success_fields( code=r.code, content_type=r.headers.getRawHeaders( b'content-type', [None])[0]))) .addActionFinish())
def answer_challenge(self, challenge_body, response): """ Respond to an authorization challenge. :param ~acme.messages.ChallengeBody challenge_body: The challenge being responded to. :param ~acme.challenges.ChallengeResponse response: The response to the challenge. :return: The updated challenge resource. :rtype: Deferred[`~acme.messages.ChallengeResource`] """ action = LOG_ACME_ANSWER_CHALLENGE( challenge_body=challenge_body, response=response) with action.context(): return ( DeferredContext( self._client.post(challenge_body.uri, response)) .addCallback(self._parse_challenge) .addCallback(self._check_challenge, challenge_body) .addCallback( tap(lambda c: action.add_success_fields(challenge_resource=c))) .addActionFinish())
def got_challenge(stop_responding): return ( poll_until_valid(authzr, self._clock, client) .addBoth(tap(lambda _: stop_responding())))