def _new_order_v2(self): ''' Start a new certificate order (ACME v2 protocol). https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 ''' identifiers = [] for domain in self.domains: identifiers.append({ 'type': 'dns', 'value': domain, }) new_order = {"identifiers": identifiers} result, info = self.account.send_signed_request( self.directory['newOrder'], new_order) if info['status'] not in [201]: raise ModuleFailException( "Error new order: CODE: {0} RESULT: {1}".format( info['status'], result)) 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.order_uri = info['location'] self.finalize_uri = result['finalize']
def _finalize_cert(self): ''' Create a new certificate based on the csr. Return the certificate object as dict https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 ''' csr = pem_to_der(self.csr) new_cert = { "csr": nopad_b64(csr), } result, info = self.account.send_signed_request( self.finalize_uri, new_cert) if info['status'] not in [200]: raise ModuleFailException( "Error new cert: CODE: {0} RESULT: {1}".format( info['status'], result)) order = info['location'] status = result['status'] while status not in ['valid', 'invalid']: time.sleep(2) result = simple_get(self.module, order) status = result['status'] if status != 'valid': raise ModuleFailException( "Error new cert: CODE: {0} STATUS: {1} RESULT: {2}".format( info['status'], status, result)) return result['certificate']
def _finalize_cert(self): ''' Create a new certificate based on the csr. Return the certificate object as dict https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.4 ''' openssl_csr_cmd = [self._openssl_bin, "req", "-in", self.csr, "-outform", "DER"] dummy, out, dummy = self.module.run_command(openssl_csr_cmd, check_rc=True) new_cert = { "csr": nopad_b64(to_bytes(out)), } result, info = self.account.send_signed_request(self.finalize_uri, new_cert) if info['status'] not in [200]: raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result)) order = info['location'] status = result['status'] while status not in ['valid', 'invalid']: time.sleep(2) result = simple_get(self.module, order) status = result['status'] if status != 'valid': raise ModuleFailException("Error new cert: CODE: {0} STATUS: {1} RESULT: {2}".format(info['status'], status, result)) return result['certificate']
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)
def _validate_challenges(self, domain, auth): ''' Validate the authorization provided in the auth dict. Returns True when the validation was successful and False when it was not. ''' for challenge in auth['challenges']: if self.challenge != challenge['type']: continue uri = challenge['uri'] if self.version == 1 else challenge['url'] challenge_response = {} if self.version == 1: token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) keyauthorization = self.account.get_keyauthorization(token) challenge_response["resource"] = "challenge" challenge_response["keyAuthorization"] = keyauthorization result, info = self.account.send_signed_request( uri, challenge_response) if info['status'] not in [200, 202]: raise ModuleFailException( "Error validating challenge: CODE: {0} RESULT: {1}".format( info['status'], result)) status = '' while status not in ['valid', 'invalid', 'revoked']: result = simple_get(self.module, auth['uri']) result['uri'] = auth['uri'] if self._add_or_update_auth(domain, result): self.changed = True # https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1.2 # "status (required, string): ... # If this field is missing, then the default value is "pending"." if self.version == 1 and 'status' not in result: status = 'pending' else: status = result['status'] time.sleep(2) if status == 'invalid': self._fail_challenge(domain, result, 'Authorization for {0} returned invalid') return status == 'valid'