def _download_cert(self, url): ''' Download and parse the certificate chain. https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2 ''' resp, info = fetch_url( self.module, url, headers={'Accept': 'application/pem-certificate-chain'}) try: content = resp.read() except AttributeError: content = info.get('body') if not content or not info['content-type'].startswith( 'application/pem-certificate-chain'): raise ModuleFailException( "Cannot download certificate chain from {0}: {1} (headers: {2})" .format(url, content, info)) cert = None chain = [] # Parse data lines = content.decode('utf-8').splitlines(True) current = [] for line in lines: if line.strip(): current.append(line) if line.startswith('-----END CERTIFICATE-----'): if cert is None: cert = ''.join(current) else: chain.append(''.join(current)) current = [] # Process link-up headers if there was no chain in reply if not chain and 'link' in info: link = info['link'] parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link) if parsed_link and parsed_link.group(2) == "up": chain_link = parsed_link.group(1) chain_result, chain_info = fetch_url(self.module, chain_link, method='GET') if chain_info['status'] in [200, 201]: chain.append(self._der_to_pem(chain_result.read())) if cert is None or current: raise ModuleFailException( "Failed to parse certificate chain download from {0}: {1} (headers: {2})" .format(url, content, info)) return {'cert': cert, 'chain': chain}
def _new_cert_v1(self): ''' Create a new certificate based on the CSR (ACME v1 protocol). Return the certificate object as dict https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5 ''' csr = pem_to_der(self.csr) new_cert = { "resource": "new-cert", "csr": nopad_b64(csr), } result, info = self.account.send_signed_request(self.directory['new-cert'], new_cert) chain = [] if 'link' in info: link = info['link'] parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link) if parsed_link and parsed_link.group(2) == "up": chain_link = parsed_link.group(1) chain_result, chain_info = fetch_url(self.module, chain_link, method='GET') if chain_info['status'] in [200, 201]: chain = [self._der_to_pem(chain_result.read())] if info['status'] not in [200, 201]: raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result)) else: return {'cert': self._der_to_pem(result), 'uri': info['location'], 'chain': chain}
def finish_challenges(self): ''' Verify challenges for all domains of the CSR. ''' self.authorizations = {} # Step 1: obtain challenge information if self.version == 1: # For ACME v1, we attempt to create new authzs. Existing ones # will be returned instead. for domain in self.domains: new_auth = self._new_authz_v1(domain) self._add_or_update_auth(domain, new_auth) else: # For ACME v2, we obtain the order object by fetching the # order URI, and extract the information from there. resp, info = fetch_url(self.module, self.order_uri) try: result = resp.read() except AttributeError: result = info.get('body') if not result: raise ModuleFailException( "Cannot download order from {0}: {1} (headers: {2})". format(self.order_uri, result, info)) if info['status'] not in [200]: raise ModuleFailException( "Error on downloading order: CODE: {0} RESULT: {1}".format( info['status'], result)) result = self.module.from_json(result.decode('utf8')) for auth_uri in result['authorizations']: auth_data = simple_get(self.module, auth_uri) auth_data['uri'] = auth_uri domain = auth_data['identifier']['value'] if auth_data.get('wildcard', False): domain = '*.{0}'.format(domain) self.authorizations[domain] = auth_data self.finalize_uri = result['finalize'] # Step 2: validate challenges for domain, auth in self.authorizations.items(): if auth['status'] == 'pending': self._validate_challenges(domain, auth)