def orders_invalidate(self, uts=uts_now(), report_format='csv', report_name=None): """ orders cleanup based on expiry date""" self.logger.debug('Housekeeping.orders_invalidate({0})'.format(uts)) with Order(self.debug, None, self.logger) as order: # get expired orders (field_list, order_list) = order.invalidate(timestamp=uts) # normalize lists (field_list, order_list) = self._lists_normalize(field_list, order_list, 'order') # convert dates into human readable format order_list = self._convert_data(order_list) if report_name: if order_list: # dump report to file if report_format == 'csv': self.logger.debug('Housekeeping.orders_invalidate(): Dump in csv-format') csv_list = self._to_list(field_list, order_list) self._csv_dump('{0}.{1}'.format(report_name, report_format), csv_list) elif report_format == 'json': self.logger.debug('Housekeeping.orders_invalidate(): Dump in json-format') self._json_dump('{0}.{1}'.format(report_name, report_format), order_list) else: self.logger.debug('Housekeeping.orders_invalidate(): No dump just return report') else: self.logger.debug('Housekeeping.orders_invalidate(): No orders to dump') return order_list
def _store_cert(self, ca_id, cert_name, serial, cert, name_hash, issuer_hash): """ store certificate to database """ self.logger.debug('CAhandler._store_cert()') # insert certificate into item table insert_date = uts_to_date_utc(uts_now(), '%Y%m%d%H%M%SZ') item_dic = { 'type': 3, 'comment': 'from acme2certifier', 'source': 2, 'date': insert_date, 'name': cert_name } row_id = self._item_insert(item_dic) # insert certificate to cert table cert_dic = { 'item': row_id, 'serial': serial, 'issuer': ca_id, 'ca': 0, 'cert': cert, 'iss_hash': issuer_hash, 'hash': name_hash } _row_id = self._cert_insert( cert_dic) # lgtm [py/unused-local-variable] self.logger.debug('CAhandler._store_cert() ended')
def certificates_cleanup(self, uts=None, purge=False, report_format='csv', report_name=None): """ database cleanuip certificate-table """ self.logger.debug('Housekeeping.certificates_cleanup()') if not uts: uts = uts_now() with Certificate(self.debug, None, self.logger) as certificate: (field_list, cert_list) = certificate.cleanup(timestamp=uts, purge=purge) # normalize lists # (field_list, cert_list) = self._lists_normalize(field_list, cert_list, 'certificate') if report_name: if cert_list: # dump report to file if report_format == 'csv': self.logger.debug('Housekeeping.certificates_cleanup(): Dump in csv-format') csv_list = self._to_list(field_list, cert_list) self._csv_dump('{0}.{1}'.format(report_name, report_format), csv_list) elif report_format == 'json': self.logger.debug('Housekeeping.certificates_cleanup(): Dump in json-format') self._json_dump('{0}.{1}'.format(report_name, report_format), cert_list) else: self.logger.debug('Housekeeping.certificates_cleanup(): No dump just return report') else: self.logger.debug('Housekeeping.certificates_cleanup(): No certificates to dump') return cert_list
def _csr_import(self, csr, request_name): """ check existance of csr and load into db """ self.logger.debug('CAhandler._csr_insert()') csr_info = self._csr_search('request', csr) if not csr_info: # csr does not exist in db - lets import it insert_date = uts_to_date_utc(uts_now(), '%Y%m%d%H%M%SZ') item_dic = { 'type': 2, 'comment': 'from acme2certifier', 'source': 2, 'date': insert_date, 'name': request_name } row_id = self._item_insert(item_dic) # insert csr csr_info = {'item': row_id, 'signed': 1, 'request': csr} self._csr_insert(csr_info) self.logger.debug('CAhandler._csr_insert()') return csr_info
def _validate(self, challenge_name, payload): """ validate challenge""" self.logger.debug('Challenge._validate({0}: {1})'.format(challenge_name, payload)) if self.challenge_validation_disable: self.logger.debug('CHALLENGE VALIDATION DISABLED. SETTING challenge status to valid') challenge_check = True invalid = False else: (challenge_check, invalid) = self._check(challenge_name, payload) if invalid: self._update({'name' : challenge_name, 'status' : 'invalid'}) # authorization update to valid state self._update_authz(challenge_name, {'status' : 'invalid'}) elif challenge_check: self._update({'name' : challenge_name, 'status' : 'valid', 'validated': uts_now()}) # authorization update to valid state self._update_authz(challenge_name, {'status' : 'valid'}) if payload: if 'keyAuthorization' in payload: # update challenge to ready state data_dic = {'name' : challenge_name, 'keyauthorization' : payload['keyAuthorization']} self._update(data_dic) self.logger.debug('Challenge._validate() ended with:{0}'.format(challenge_check)) return challenge_check
def revoke(self, cert, rev_reason='unspecified', rev_date=uts_to_date_utc(uts_now())): """ revoke certificate """ self.logger.debug('CAhandler.revoke({0}: {1})'.format( rev_reason, rev_date)) # lookup REST-PATH of issuing CA ca_dic = self._ca_get_properties('name', self.ca_name) if 'href' in ca_dic: # get serial from pem file serial = cert_serial_get(self.logger, cert) if serial: # get certificate information via rest by search for ca+ serial cert_dic = self._cert_get_properties(serial, ca_dic['href']) if 'certificates' in cert_dic: if len(cert_dic['certificates'] ) > 0 and 'href' in cert_dic['certificates'][0]: # revoke the cert data = { 'newStatus': 'revoked', 'crlReason': rev_reason, 'invalidityDate': rev_date } cert_dic = self._api_post( cert_dic['certificates'][0]['href'] + '/status', data) if 'status' in cert_dic: # code = cert_dic['status'] code = 400 message = 'urn:ietf:params:acme:error:alreadyRevoked' if 'message' in cert_dic: detail = cert_dic['message'] else: detail = 'no details' else: code = 200 message = None detail = None else: code = 404 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'Cert path could not be found' else: code = 404 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'Cert could not be found' else: code = 404 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'failed to get serial number from cert' else: code = 404 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'CA could not be found' return (code, message, detail)
def _csr_id_lookup(self, csr_cn, csr_san_list, csr=None): """ lookup CSR based on CN """ self.logger.debug('CAhandler._csr_id_lookup()') uts_n = uts_now() # get unused requests from NCLM unused_request_list = self._unusedrequests_get() # get last 50 requests if not csr_cn: last_request_list = self._lastrequests_get() else: last_request_list = [] req_id = None try: # check every CSR for req in sorted(unused_request_list, key=lambda i: i['requestID'], reverse=True): req_cn = None # check the import date and consider only csr which are less then 5min old csr_uts = date_to_uts_utc(req['addedAt'][:25], '%Y-%m-%dT%H:%M:%S.%f') if uts_n - csr_uts < self.request_delta_treshold: if 'subjectName' in req: # split the subject and filter CN subject_list = req['subjectName'].split(',') for field in subject_list: field = field.strip() if field.startswith('CN='): req_cn = field.lower().replace('cn=', '') break if csr_cn: if req_cn == csr_cn.lower() and 'requestID' in req: req_id = req['requestID'] break else: for _req in sorted(last_request_list, key=lambda i: i['requestId'], reverse=True): if 'pkcs10' in _req: if _req['pkcs10'] == csr: req_id = _req['requestId'] break except Exception as err_: self.logger.error( '_csr_id_lookup(): response incomplete: {0}'.format(err_)) self.logger.debug( 'CAhandler._csr_id_lookup() ended with: {0}'.format(req_id)) return req_id
def _csr_id_lookup(self, csr_cn, csr_san_list): """ lookup CSR based on CN """ self.logger.debug('CAhandler._csr_id_lookup()') uts_n = uts_now() # get unused requests from NCLM request_list = self._unusedrequests_get() req_id = None # check every CSR for req in request_list: req_cn = None # check the import date and consider only csr which are less then 5min old csr_uts = date_to_uts_utc(req['addedAt'][:25], '%Y-%m-%dT%H:%M:%S.%f') if uts_n - csr_uts < 300: if 'subjectName' in req: # split the subject and filter CN subject_list = req['subjectName'].split(',') for field in subject_list: field = field.strip() if field.startswith('CN='): req_cn = field.lower().replace('cn=', '') break # compare csr cn with request cn if csr_cn: if req_cn == csr_cn.lower() and 'requestID' in req: req_id = req['requestID'] break elif not req_cn: # special certbot scenario (no CN in CSR). No better idea how to handle this, take first request try: req_all = requests.get(self.api_host + '/requests', headers=self.headers, verify=self.ca_bundle, proxies=self.proxy).json() except BaseException as err_: self.logger.error( 'CAhandler._csr_id_lookup() returned error: {0}'. format(str(err_))) req_all = [] for _req in reversed(req_all): if 'pkcs10' in _req: req_san_list = csr_san_get(self.logger, _req['pkcs10']) if sorted(csr_san_list) == sorted( req_san_list) and 'requestID' in _req: req_id = _req['requestID'] break self.logger.debug( 'CAhandler._csr_id_lookup() ended with: {0}'.format(req_id)) return req_id
def revoke(self, cert, rev_reason='unspecified', rev_date=None): """ revoke certificate """ self.logger.debug('CAhandler.revoke()') # overwrite revocation date - we ignore what has been submitted rev_date = uts_to_date_utc(uts_now(), '%Y%m%d%H%M%SZ') rev_reason = 0 if self.xdb_file: # load ca cert and key (_ca_key, _ca_cert, ca_id) = self._ca_load() serial = cert_serial_get(self.logger, cert) if serial: serial = '{:X}'.format(serial) if ca_id and serial: # check if certificate has alreay been revoked: if not self._revocation_search('serial', serial): rev_dic = { 'caID': ca_id, 'serial': serial, 'date': rev_date, 'invaldate': rev_date, 'reasonBit': rev_reason } row_id = self._revocation_insert(rev_dic) if row_id: code = 200 message = None detail = None else: code = 500 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'database update failed' else: code = 400 message = 'urn:ietf:params:acme:error:alreadyRevoked' detail = 'Certificate has already been revoked' else: code = 500 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'certificate lookup failed' else: code = 500 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'configuration error' self.logger.debug('Certificate.revoke() ended') return (code, message, detail)
def _cert_reusage_check(self, csr): """ check if an existing certificate an be reused """ self.logger.debug('Certificate._cert_reusage_check({0})'.format( self.cert_reusage_timeframe)) try: result_dic = self.dbstore.certificates_search( 'csr', csr, ('cert', 'cert_raw', 'expire_uts', 'issue_uts', 'created_at', 'id')) except Exception as err_: self.logger.critical( 'acme2certifier database error in Certificate._cert_reusage_check(): {0}' .format(err_)) result_dic = None cert = None cert_raw = None message = None if result_dic: uts = uts_now() # sort certificates by creation date for certificate in sorted(result_dic, key=lambda i: i['issue_uts'], reverse=True): try: uts_create = date_to_uts_utc(certificate['created_at']) except Exception as _err: self.logger.error( 'acme2certifier date_to_uts_utc() error in Certificate._cert_reusage_check(): id:{0}/created_at:{1}' .format(certificate['id'], certificate['created_at'])) uts_create = 0 # check if there certificates within reusage timeframe if certificate['cert_raw'] and certificate[ 'cert'] and uts - self.cert_reusage_timeframe <= uts_create: # exclude expired certificates if uts <= certificate['expire_uts']: cert = certificate['cert'] cert_raw = certificate['cert_raw'] message = 'reused certificate from id: {0}'.format( certificate['id']) break self.logger.debug( 'Certificate._cert_reusage_check() ended with {0}'.format(message)) return (None, cert, cert_raw, message)
def invalidate(self, timestamp=None): """ invalidate authorizations """ self.logger.debug('Authorization.invalidate({0})'.format(timestamp)) if not timestamp: timestamp = uts_now() self.logger.debug( 'Authorization.invalidate(): set timestamp to {0}'.format( timestamp)) field_list = [ 'id', 'name', 'expires', 'value', 'created_at', 'token', 'status__id', 'status__name', 'order__id', 'order__name' ] try: authz_list = self.dbstore.authorizations_expired_search( 'expires', timestamp, vlist=field_list, operant='<=') except BaseException as err_: self.logger.critical( 'acme2certifier database error in Authorization.invalidate(): {0}' .format(err_)) authz_list = [] output_list = [] for authz in authz_list: # select all authz which are not invalid if 'name' in authz and 'status__name' in authz and authz[ 'status__name'] != 'expired': # skip corner cases where authz expiry is set to 0 if 'expires' not in authz or authz['expires'] > 0: # change status and add to output list output_list.append(authz) data_dic = {'name': authz['name'], 'status': 'expired'} try: self.dbstore.authorization_update(data_dic) except BaseException as err_: self.logger.critical( 'acme2certifier database error in Authorization.invalidate(): {0}' .format(err_)) self.logger.debug( 'Authorization.invalidate() ended: {0} authorizations identified'. format(len(output_list))) return (field_list, output_list)
def invalidate(self, timestamp=None): """ invalidate orders """ self.logger.debug('Order.invalidate({0})'.format(timestamp)) if not timestamp: timestamp = uts_now() self.logger.debug( 'Order.invalidate(): set timestamp to {0}'.format(timestamp)) field_list = [ 'id', 'name', 'expires', 'identifiers', 'created_at', 'status__id', 'status__name', 'account__id', 'account__name', 'account__contact' ] try: order_list = self.dbstore.orders_invalid_search('expires', timestamp, vlist=field_list, operant='<=') except BaseException as err_: self.logger.critical( 'acme2certifier database error in Order._invalidate() search: {0}' .format(err_)) order_list = [] output_list = [] for order in order_list: # print(order['id']) # select all orders which are not invalid if 'name' in order and 'status__name' in order and order[ 'status__name'] != 'invalid': # change status and add to output list output_list.append(order) data_dic = {'name': order['name'], 'status': 'invalid'} try: self.dbstore.order_update(data_dic) except BaseException as err_: self.logger.critical( 'acme2certifier database error in Order._invalidate() upd: {0}' .format(err_)) self.logger.debug( 'Order.invalidate() ended: {0} orders identified'.format( len(output_list))) return (field_list, output_list)
def revoke(self, content): """ revoke request """ self.logger.debug('Certificate.revoke()') response_dic = {} # check message (code, message, detail, _protected, payload, account_name) = self.message.check(content) if code == 200: if 'certificate' in payload: (code, error) = self._revocation_request_validate( account_name, payload) if code == 200: # revocation starts here # revocation reason is stored in error variable rev_date = uts_to_date_utc(uts_now()) with self.cahandler(self.debug, self.logger) as ca_handler: (code, message, detail) = ca_handler.revoke(payload['certificate'], error, rev_date) else: message = error detail = None else: # message could not get decoded code = 400 message = 'urn:ietf:params:acme:error:malformed' detail = 'certificate not found' # prepare/enrich response status_dic = {'code': code, 'message': message, 'detail': detail} response_dic = self.message.prepare_response(response_dic, status_dic) self.logger.debug( 'Certificate.revoke() ended with: {0}'.format(response_dic)) return response_dic
def revoke(self, cert, rev_reason='unspecified', rev_date=None): """ revoke certificate """ self.logger.debug('CAhandler.revoke({0}: {1})'.format(rev_reason, rev_date)) code = None message = None detail = None # overwrite revocation date - we ignore what has been submitted rev_date = uts_to_date_utc(uts_now(), '%y%m%d%H%M%SZ') if 'issuing_ca_crl' in self.issuer_dict and self.issuer_dict['issuing_ca_crl']: # load ca cert and key (ca_key, ca_cert) = self._ca_load() # turn of chain_check due to issues in pyopenssl (check is not working if key-usage is set) # result = self._certificate_chain_verify(cert, ca_cert) # proceed if the cert and ca-cert belong together # if not result: serial = cert_serial_get(self.logger, cert) # serial = serial.replace('0x', '') if ca_key and ca_cert and serial: serial = hex(serial).replace('0x', '') if os.path.exists(self.issuer_dict['issuing_ca_crl']): # existing CRL with open(self.issuer_dict['issuing_ca_crl'], 'r') as fso: crl = crypto.load_crl(crypto.FILETYPE_PEM, fso.read()) # check CRL already contains serial sn_match = self._crl_check(crl, serial) else: # new CRL crl = crypto.CRL() sn_match = None # this is the revocation operation if not sn_match: revoked = crypto.Revoked() revoked.set_reason(convert_string_to_byte(rev_reason)) revoked.set_serial(convert_string_to_byte(serial)) revoked.set_rev_date(convert_string_to_byte(rev_date)) crl.add_revoked(revoked) # save CRL crl_text = crl.export(ca_cert, ca_key, crypto.FILETYPE_PEM, 7, convert_string_to_byte('sha256')) with open(self.issuer_dict['issuing_ca_crl'], 'wb') as fso: fso.write(crl_text) code = 200 else: code = 400 message = 'urn:ietf:params:acme:error:alreadyRevoked' detail = 'Certificate has already been revoked' else: code = 400 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'configuration error' #else: # code = 400 # message = 'urn:ietf:params:acme:error:serverInternal' # detail = result else: code = 400 message = 'urn:ietf:params:acme:error:serverInternal' detail = 'Unsupported operation' self.logger.debug('CAhandler.revoke() ended') return(code, message, detail)
def _authz_info(self, url): """ return authzs information """ self.logger.debug('Authorization._authz_info({0})'.format(url)) authz_name = url.replace( '{0}{1}'.format(self.server_name, self.path_dic['authz_path']), '') expires = uts_now() + self.validity token = generate_random_string(self.logger, 32) authz_info_dic = {} # lookup authorization based on name try: authz = self.dbstore.authorization_lookup('name', authz_name) except BaseException as err_: self.logger.critical( 'acme2certifier database error in Authorization._authz_info(): {0}' .format(err_)) authz = None if authz: # update authorization with expiry date and token (just to be sure) try: self.dbstore.authorization_update({ 'name': authz_name, 'token': token, 'expires': expires }) except BaseException as err_: self.logger.error( 'acme2certifier database error in Authorization._authz_info(): {0}' .format(err_)) authz_info_dic['expires'] = uts_to_date_utc(expires) # get authorization information from db to be inserted in message tnauth = None try: auth_info = self.dbstore.authorization_lookup( 'name', authz_name, ['status__name', 'type', 'value']) except BaseException as err_: self.logger.error( 'acme2certifier database error in Authorization._authz_info(): {0}' .format(err_)) auth_info = {} if auth_info: if 'status__name' in auth_info[0]: authz_info_dic['status'] = auth_info[0]['status__name'] else: authz_info_dic['status'] = 'pending' if 'type' in auth_info[0] and 'value' in auth_info[0]: authz_info_dic['identifier'] = { 'type': auth_info[0]['type'], 'value': auth_info[0]['value'] } if auth_info[0]['type'] == 'TNAuthList': tnauth = True else: authz_info_dic['status'] = 'pending' with Challenge(self.debug, self.server_name, self.logger, expires) as challenge: # get challenge data (either existing or new ones) authz_info_dic['challenges'] = challenge.challengeset_get( authz_name, authz_info_dic['status'], token, tnauth) self.logger.debug('Authorization._authz_info() returns: {0}'.format( json.dumps(authz_info_dic))) return authz_info_dic
def _add(self, payload, aname): """ add order request to database """ self.logger.debug('Order._add({0})'.format(aname)) error = None auth_dic = {} order_name = generate_random_string(self.logger, 12) expires = uts_now() + self.validity if 'identifiers' in payload: data_dic = {'status': 2, 'expires': expires, 'account': aname} data_dic['name'] = order_name data_dic['identifiers'] = json.dumps(payload['identifiers']) #if 'notBefore' in payload: # data_dic['notbefore'] = payload['notBefore'] #if 'notAfter' in payload: # data_dic['notafter'] = payload['notAfter'] # check identifiers error = self._identifiers_check(payload['identifiers']) # change order status if needed if error: data_dic['status'] = 1 try: # add order to db oid = self.dbstore.order_add(data_dic) except BaseException as err_: self.logger.critical( 'acme2certifier database error in Order._add() order: {0}'. format(err_)) oid = None if not error: if oid: error = None for auth in payload['identifiers']: # generate name auth_name = generate_random_string(self.logger, 12) # store to return to upper func auth_dic[auth_name] = auth.copy() auth['name'] = auth_name auth['order'] = oid auth['status'] = 'pending' auth['expires'] = uts_now() + self.authz_validity try: self.dbstore.authorization_add(auth) except BaseException as err_: self.logger.critical( 'acme2certifier database error in Order._add() authz: {0}' .format(err_)) else: error = 'urn:ietf:params:acme:error:malformed' else: error = 'urn:ietf:params:acme:error:unsupportedIdentifier' self.logger.debug('Order._add() ended') return (error, order_name, auth_dic, uts_to_date_utc(expires))