def _add_nonce(self, response): """ Store a nonce from a response we received. :param twisted.web.iweb.IResponse response: The HTTP response. :return: The response, unmodified. """ nonce = response.headers.getRawHeaders(REPLAY_NONCE_HEADER, [None])[0] with LOG_JWS_ADD_NONCE(raw_nonce=nonce) as action: if nonce is None: return _fail_and_consume( response, errors.ClientError(str(errors.MissingNonce(response))), ) else: try: decoded_nonce = Header._fields['nonce'].decode( nonce.decode('ascii')) action.add_success_fields(nonce=decoded_nonce) except DeserializationError as error: return _fail_and_consume(response, errors.BadNonce(nonce, error)) self._nonces.add(decoded_nonce) return response
def answer_challenge(self, challb, response): """Answer challenge. :param challb: Challenge Resource body. :type challb: `.ChallengeBody` :param response: Corresponding Challenge response :type response: `.challenges.ChallengeResponse` :returns: Challenge Resource with updated body. :rtype: `.ChallengeResource` :raises .UnexpectedUpdate: """ response = self._post(challb.uri, response) try: authzr_uri = response.links['up']['url'] except KeyError: raise errors.ClientError('"up" Link header missing') challr = messages.ChallengeResource( authzr_uri=authzr_uri, body=messages.ChallengeBody.from_json(response.json())) # TODO: check that challr.uri == response.headers['Location']? if challr.uri != challb.uri: raise errors.UnexpectedUpdate(challr.uri) return challr
def fetch_chain(self, certr, max_length=10): """ Fetch the intermediary chain for a certificate. :param acme.messages.CertificateResource certr: The certificate to fetch the chain for. :param int max_length: The maximum length of the chain that will be fetched. :rtype: Deferred[List[`acme.messages.CertificateResource`]] :return: The issuer certificate chain, ordered with the trust anchor last. """ action = LOG_ACME_FETCH_CHAIN() with action.context(): if certr.cert_chain_uri is None: return succeed([]) elif max_length < 1: raise errors.ClientError('chain too long') return ( DeferredContext( self._client.get( certr.cert_chain_uri, content_type=DER_CONTENT_TYPE, headers=Headers({b'Accept': [DER_CONTENT_TYPE]}))) .addCallback(self._parse_certificate) .addCallback( lambda issuer: self.fetch_chain(issuer, max_length=max_length - 1) .addCallback(lambda chain: [issuer] + chain)) .addActionFinish())
def cb_extract_new_nonce(directory): try: self._new_nonce = directory.newNonce except AttributeError: raise errors.ClientError('Directory has no newNonce URL', directory) return directory
def _expect_response(cls, response, code): """ Ensure we got the expected response code. """ if response.code != code: raise errors.ClientError( 'Expected {!r} response but got {!r}'.format( code, response.code)) return response
def _got_json(jobj): if 400 <= response.code < 600: if (response_ct.lower().startswith(JSON_ERROR_CONTENT_TYPE) and jobj is not None): raise ServerError(messages.Error.from_json(jobj), response) else: # response is not JSON object return _fail_and_consume( response, errors.ClientError('Response is not JSON.')) elif content_type not in response_ct.lower(): return _fail_and_consume( response, errors.ClientError( 'Unexpected response Content-Type: {0!r}. ' 'Expecting {1!r}.'.format(response_ct, content_type))) elif JSON_CONTENT_TYPE in content_type.lower() and jobj is None: return _fail_and_consume( response, errors.ClientError('Missing JSON body.')) return response
def _expect_response(cls, response, codes): """ Ensure we got one of the expected response codes`. """ if response.code not in codes: return _fail_and_consume( response, errors.ClientError( 'Expected {!r} response but got {!r}'.format( codes, response.code))) return response
def _parse_challenge(cls, response): """ Parse a challenge resource. """ links = _parse_header_links(response) try: authzr_uri = links['up']['url'] except KeyError: raise errors.ClientError('"up" link missing') return (response.json().addCallback( lambda body: messages.ChallengeResource(authzr_uri=authzr_uri, body=messages.ChallengeBody .from_json(body))))
def revoke(self, cert): """Revoke certificate. :param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` :raises .ClientError: If revocation is unsuccessful. """ response = self.net.post(messages.Revocation.url(self.new_reg_uri), messages.Revocation(certificate=cert)) if response.status_code != http_client.OK: raise errors.ClientError( 'Successful revocation must return HTTP OK status')
def _parse_authorization(cls, response, uri=None): """ Parse an authorization resource. """ links = _parse_header_links(response) try: new_cert_uri = links[u'next'][u'url'] except KeyError: raise errors.ClientError('"next" link missing') return (response.json().addCallback( lambda body: messages.AuthorizationResource( body=messages.Authorization.from_json(body), uri=cls._maybe_location(response, uri=uri), new_cert_uri=new_cert_uri)))
def request_issuance(self, csr, authzrs): print("Se ejecuta request issuance") """Request issuance. :param csr: CSR :type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509` :param authzrs: `list` of `.AuthorizationResource` :returns: Issued certificate :rtype: `.messages.CertificateResource` """ assert authzrs, "Authorizations list is empty" logger.debug("Requesting issuance...") # TODO: assert len(authzrs) == number of SANs #Checks if STARValidityCertbot file containing recurrent_cert_validity exists if os.path.isfile("../../STARValidityCertbot"): print "CLIENT.PY: STARValidityCertbot does exist" fileSTAR = open("../../STARValidityCertbot", "r") recurrent = True recurrent_cert_validity = int(float(fileSTAR.read())) req = messages.CertificateRequest(csr=csr, recurrent=recurrent,recurrent_cert_validity=recurrent_cert_validity) else: print "CLIENT.PY: STARValidityCertbot does NOT exist" req = messages.CertificateRequest(csr=csr) print "CSR sent to server is %s" % req content_type = DER_CONTENT_TYPE # TODO: add 'cert_type 'argument response = self.net.post( self.directory.new_cert, req, content_type=content_type, headers={'Accept': content_type}) cert_chain_uri = response.links.get('up', {}).get('url') try: uri = response.headers['Location'] file=open('/root/certId','wr+') file.write(uri) print(uri) except KeyError: raise errors.ClientError('"Location" Header missing') return messages.CertificateResource( uri=uri, authzrs=authzrs, cert_chain_uri=cert_chain_uri, body=jose.ComparableX509(OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def _regr_from_response(cls, response, uri=None, new_authzr_uri=None, terms_of_service=None): if 'terms-of-service' in response.links: terms_of_service = response.links['terms-of-service']['url'] if 'next' in response.links: new_authzr_uri = response.links['next']['url'] if new_authzr_uri is None: raise errors.ClientError('"next" link missing') return messages.RegistrationResource( body=messages.Registration.from_json(response.json()), uri=response.headers.get('Location', uri), new_authzr_uri=new_authzr_uri, terms_of_service=terms_of_service)
def _authzr_from_response(self, response, identifier, uri=None, new_cert_uri=None): # pylint: disable=no-self-use if new_cert_uri is None: try: new_cert_uri = response.links['next']['url'] except KeyError: raise errors.ClientError('"next" link missing') authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), uri=response.headers.get('Location', uri), new_cert_uri=new_cert_uri) if authzr.body.identifier != identifier: raise errors.UnexpectedUpdate(authzr) return authzr
def _parse_challenge(cls, response): """ Parse a challenge resource. """ links = _parse_header_links(response) try: authzr_uri = links['up']['url'] except KeyError: yield _fail_and_consume(response, errors.ClientError('"up" link missing')) body = yield response.json() defer.returnValue( messages.ChallengeResource( authzr_uri=authzr_uri, body=messages.ChallengeBody.from_json(body), ))
def _get(self, uri, content_type=JSON_CONTENT_TYPE, **kwargs): """Send GET request. :raises .ClientError: :returns: HTTP Response :rtype: `requests.Response` """ logger.debug('Sending GET request to %s', uri) kwargs.setdefault('verify', self.verify_ssl) try: response = requests.get(uri, **kwargs) except requests.exceptions.RequestException as error: raise errors.ClientError(error) self._check_response(response, content_type=content_type) return response
def answer_challenge(self, challb, response): """Answer challenge. :param challb: Challenge Resource body. :type challb: `.ChallengeBody` :param response: Corresponding Challenge response :type response: `.challenges.ChallengeResponse` :returns: Challenge Resource with updated body. :rtype: `.ChallengeResource` :raises .UnexpectedUpdate: """ # Because sending keyAuthorization in a response challenge has been removed from the ACME # spec, it is not included in the KeyAuthorizationResponseChallenge JSON by default. # However as a migration path, we temporarily expect a malformed error from the server, # and fallback by resending the challenge response with the keyAuthorization field. # TODO: Remove this fallback for Certbot 0.34.0 try: response = self._post(challb.uri, response) except messages.Error as error: if (error.code == 'malformed' and isinstance( response, challenges.KeyAuthorizationChallengeResponse)): logger.debug( 'Error while responding to a challenge without keyAuthorization ' 'in the JWS, your ACME CA server may not support it:\n%s', error) logger.debug('Retrying request with keyAuthorization set.') response._dump_authorization_key(True) # pylint: disable=protected-access response = self._post(challb.uri, response) else: raise try: authzr_uri = response.links['up']['url'] except KeyError: raise errors.ClientError('"up" Link header missing') challr = messages.ChallengeResource( authzr_uri=authzr_uri, body=messages.ChallengeBody.from_json(response.json())) # TODO: check that challr.uri == response.headers['Location']? if challr.uri != challb.uri: raise errors.UnexpectedUpdate(challr.uri) return challr
def check_cert(self, certr): """Check for new cert. :param certr: Certificate Resource :type certr: `.CertificateResource` :returns: Updated Certificate Resource. :rtype: `.CertificateResource` """ # TODO: acme-spec 5.1 table action should be renamed to # "refresh cert", and this method integrated with self.refresh response, cert = self._get_cert(certr.uri) if 'Location' not in response.headers: raise errors.ClientError('Location header missing') if response.headers['Location'] != certr.uri: raise errors.UnexpectedUpdate(response.text) return certr.update(body=cert)
def _revoke(self, cert, rsn, url): """Revoke certificate. :param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` :param int rsn: Reason code for certificate revocation. :param str url: ACME URL to post to :raises .ClientError: If revocation is unsuccessful. """ response = self._post( url, messages.Revocation(certificate=cert, reason=rsn)) if response.status_code != http_client.OK: raise errors.ClientError( 'Successful revocation must return HTTP OK status')
def revoke(self, cert, rsn): """Revoke certificate. :param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` :param int rsn: Reason code for certificate revocation. :raises .ClientError: If revocation is unsuccessful. """ response = self.net.post(self.directory[messages.Revocation], messages.Revocation(certificate=cert, reason=rsn), content_type=None) if response.status_code != http_client.OK: raise errors.ClientError( 'Successful revocation must return HTTP OK status')
def _parse_regr_response(self, response, uri=None, new_authzr_uri=None, terms_of_service=None): """ Parse a registration response from the server. """ links = _parse_header_links(response) if u'terms-of-service' in links: terms_of_service = links[u'terms-of-service'][u'url'] if u'next' in links: new_authzr_uri = links[u'next'][u'url'] if new_authzr_uri is None: raise errors.ClientError('"next" link missing') return (response.json().addCallback( lambda body: messages.RegistrationResource( body=messages.Registration.from_json(body), uri=self._maybe_location(response, uri=uri), new_authzr_uri=new_authzr_uri, terms_of_service=terms_of_service)))
def request_issuance(self, csr, authzrs): """Request issuance. :param csr: CSR :type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509` :param authzrs: `list` of `.AuthorizationResource` :returns: Issued certificate :rtype: `.messages.CertificateResource` """ assert authzrs, "Authorizations list is empty" logger.debug("Requesting issuance...") # TODO: assert len(authzrs) == number of SANs req = messages.CertificateRequest( csr=csr, authorizations=tuple(authzr.uri for authzr in authzrs)) content_type = self.DER_CONTENT_TYPE # TODO: add 'cert_type 'argument response = self.net.post( authzrs[0].new_cert_uri, # TODO: acme-spec #90 req, content_type=content_type, headers={'Accept': content_type}) cert_chain_uri = response.links.get('up', {}).get('url') try: uri = response.headers['Location'] except KeyError: raise errors.ClientError('"Location" Header missing') return messages.CertificateResource( uri=uri, authzrs=authzrs, cert_chain_uri=cert_chain_uri, body=jose.ComparableX509( OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def _post(self, uri, obj, content_type=JSON_CONTENT_TYPE, **kwargs): """Send POST data. :param JSONDeSerializable obj: Will be wrapped in JWS. :param str content_type: Expected ``Content-Type``, fails if not set. :raises acme.messages.ClientError: :returns: HTTP Response :rtype: `requests.Response` """ data = self._wrap_in_jws(obj, self._get_nonce(uri)) logger.debug('Sending POST data to %s: %s', uri, data) kwargs.setdefault('verify', self.verify_ssl) try: response = requests.post(uri, data=data, **kwargs) except requests.exceptions.RequestException as error: raise errors.ClientError(error) self._add_nonce(response) self._check_response(response, content_type=content_type) return response
def test_revocation_error(self): from acme import errors as acme_errors self.mock_acme_client.side_effect = acme_errors.ClientError() self.assertRaises(acme_errors.ClientError, self._call) self.mock_success_revoke.assert_not_called()