예제 #1
0
class Account(object):
    """ ACME server class """

    def __init__(self, debug=None, srv_name=None, logger=None):
        self.server_name = srv_name
        self.logger = logger
        self.dbstore = DBstore(debug, self.logger)
        self.message = Message(debug, self.server_name, self.logger)
        self.path_dic = {'acct_path' : '/acme/acct/'}
        self.ecc_only = False
        self.contact_check_disable = False
        self.tos_check_disable = False
        self.inner_header_nonce_allow = False
        self.tos_url = None
        self.eab_check = False
        self.eab_handler = None

    def __enter__(self):
        """ Makes ACMEHandler a Context Manager """
        self._config_load()
        return self

    def __exit__(self, *args):
        """ cose the connection at the end of the context """

    def _add(self, content, payload, contact):
        """ prepare db insert and call DBstore helper """
        self.logger.debug('Account.account._add()')
        account_name = generate_random_string(self.logger, 12)

        # check request
        if 'alg' in content and 'jwk' in content:

            if not self.contact_check_disable and not contact:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'incomplete protected payload'
            else:
                # ecc_only check
                if self.ecc_only and not content['alg'].startswith('ES'):
                    code = 403
                    message = 'urn:ietf:params:acme:error:badPublicKey'
                    detail = 'Only ECC keys are supported'
                else:
                    # check jwk
                    data_dic = {
                        'name': account_name,
                        'alg': content['alg'],
                        'jwk': json.dumps(content['jwk']),
                        'contact': json.dumps(contact),
                    }
                    # add eab_kid to data_dic if eab_check is enabled and kid is part of the request
                    if self.eab_check:
                        if payload and 'externalaccountbinding' in payload and payload['externalaccountbinding']:
                            if 'protected' in payload['externalaccountbinding']:
                                eab_kid = self._eab_kid_get(payload['externalaccountbinding']['protected'])
                                self.logger.info('add eab_kid: {0} to data_dic'.format(eab_kid))
                                if eab_kid:
                                    data_dic['eab_kid'] = eab_kid
                    try:
                        (db_name, new) = self.dbstore.account_add(data_dic)
                    except BaseException as err_:
                        self.logger.critical('Account.account._add(): Database error: {0}'.format(err_))
                        db_name = None
                        new = False
                    self.logger.debug('got account_name:{0} new:{1}'.format(db_name, new))
                    if new:
                        code = 201
                        message = account_name
                    else:
                        code = 200
                        message = db_name
                    detail = None
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'incomplete protected payload'

        self.logger.debug('Account.account._add() ended with:{0}'.format(code))
        return(code, message, detail)

    def _contact_check(self, content):
        """ check contact information from payload"""
        self.logger.debug('Account._contact_check()')
        code = 200
        message = None
        detail = None
        if 'contact' in content:
            contact_check = validate_email(self.logger, content['contact'])
            if not contact_check:
                # invalidcontact message
                code = 400
                message = 'urn:ietf:params:acme:error:invalidContact'
                detail = ', '.join(content['contact'])
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:invalidContact'
            detail = 'no contacts specified'

        self.logger.debug('Account._contact_check() ended with:{0}'.format(code))
        return(code, message, detail)

    def _contacts_update(self, aname, payload):
        """ update account """
        self.logger.debug('Account.update()')
        (code, message, detail) = self._contact_check(payload)
        if code == 200:
            data_dic = {'name' : aname, 'contact' : json.dumps(payload['contact'])}
            try:
                result = self.dbstore.account_update(data_dic)
            except BaseException as err_:
                self.logger.critical('acme2certifier database error in Account._contacts_update(): {0}'.format(err_))
                result = None

            if result:
                code = 200
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:accountDoesNotExist'
                detail = 'update failed'

        return(code, message, detail)

    def _delete(self, aname):
        """ delete account """
        self.logger.debug('Account._delete({0})'.format(aname))
        try:
            result = self.dbstore.account_delete(aname)
        except BaseException as err_:
            self.logger.critical('acme2certifier database error in Account._delete(): {0}'.format(err_))
            result = None

        if result:
            code = 200
            message = None
            detail = None
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:accountDoesNotExist'
            detail = 'deletion failed'

        self.logger.debug('Account._delete() ended with:{0}'.format(code))
        return(code, message, detail)

    def _eab_jwk_compare(self, protected, payload):
        """ compare jwk from outer header with jwk in eab playload """
        self.logger.debug('_eab_jwk_compare()')
        result = False
        if 'jwk' in protected:
            # convert outer jwk into string for better comparison
            if isinstance(protected, dict):
                jwk_outer = json.dumps(protected['jwk'])
                # decode inner jwk
                jwk_inner = b64decode_pad(self.logger, payload)
                jwk_inner = json.dumps(json.loads(jwk_inner))
                if jwk_outer == jwk_inner:
                    result = True

        self.logger.debug('_eab_jwk_compare() ended with: {0}'.format(result))
        return result

    def _eab_kid_get(self, protected):
        """ get key identifier for eab validation """
        self.logger.debug('_eab_kid_get()')
        # load protected into json format
        protected_dic = json.loads(b64decode_pad(self.logger, protected))
        # extract kid
        if isinstance(protected_dic, dict):
            eab_key_id = protected_dic.get('kid', None)
        else:
            eab_key_id = None

        self.logger.debug('_eab_kid_get() ended with: {0}'.format(eab_key_id))
        return eab_key_id

    def _eab_check(self, protected, payload):
        """" check for external account binding """
        self.logger.debug('_eab_check()')

        if self.eab_handler and protected and payload and 'externalaccountbinding' in payload and payload['externalaccountbinding']:
            # compare JWK from protected (outer) header if jwk included in payload of external account binding
            jwk_compare = self._eab_jwk_compare(protected, payload['externalaccountbinding']['payload'])

            if jwk_compare and 'protected' in payload['externalaccountbinding']:
                # get key identifier
                eab_kid = self._eab_kid_get(payload['externalaccountbinding']['protected'])
                if eab_kid:
                    # get eab_mac_key
                    with self.eab_handler(self.logger) as eab_handler:
                        eab_mac_key = eab_handler.mac_key_get(eab_kid)
                else:
                    eab_mac_key = None

                if eab_mac_key:
                    (result, error) = self._eab_signature_verify(payload['externalaccountbinding'], eab_mac_key)
                    if result:
                        code = 200
                        message = None
                        detail = None
                    else:
                        code = 403
                        message = 'urn:ietf:params:acme:error:unauthorized'
                        detail = 'eab signature verification failed'
                        self.logger.error('Account._eab_check() returned error: {0}'.format(error))
                else:
                    code = 403
                    message = 'urn:ietf:params:acme:error:unauthorized'
                    detail = 'eab kid lookup failed'
            else:
                code = 403
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'Malformed request'
        else:
            # no external account binding key in payload - error
            code = 403
            message = 'urn:ietf:params:acme:error:externalAccountRequired'
            detail = 'external account binding required'

        self.logger.debug('Account._eab_check() ended with:{0}'.format(code))
        return (code, message, detail)

    def _eab_signature_verify(self, content, mac_key):
        """ verify inner signature """
        self.logger.debug('Account._eab_signature_verify()')

        if content and mac_key:
            signature = Signature(None, self.server_name, self.logger)
            jwk_ = json.dumps({'k': mac_key, 'kty': 'oct'})
            (sig_check, error) = signature.eab_check(json.dumps(content), jwk_)
        else:
            sig_check = False
            error = None
        self.logger.debug('Account._eab_signature_verify() ended with: {0}'.format(sig_check))
        return (sig_check, error)

    def _inner_jws_check(self, outer_protected, inner_protected):
        """ RFC8655 7.3.5 checs of inner JWS """
        self.logger.debug('Account._inner_jws_check()')

        # check for jwk header
        if 'jwk' in inner_protected:
            if 'url' in outer_protected and 'url' in inner_protected:
                # inner and outer JWS must have the same "url" header parameter
                if outer_protected['url'] == inner_protected['url']:
                    if self.inner_header_nonce_allow:
                        code = 200
                        message = None
                        detail = None
                    else:
                        # inner JWS must omit nonce header
                        if 'nonce' not in inner_protected:
                            code = 200
                            message = None
                            detail = None
                        else:
                            code = 400
                            message = 'urn:ietf:params:acme:error:malformed'
                            detail = 'inner jws must omit nonce header'
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'url parameter differ in inner and outer jws'
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'inner or outer jws is missing url header parameter'
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'inner jws is missing jwk'

        self.logger.debug('Account._inner_jws_check() ended with: {0}:{1}'.format(code, detail))
        return(code, message, detail)

    def _inner_payload_check(self, aname, outer_protected, inner_payload):
        """ RFC8655 7.3.5 checs of inner payload """
        self.logger.debug('Account._inner_payload_check()')

        if 'kid' in outer_protected:
            if 'account' in inner_payload:
                if outer_protected['kid'] == inner_payload['account']:
                    if 'oldkey' in inner_payload:
                        # compare oldkey with database
                        (code, message, detail) = self._key_compare(aname, inner_payload['oldkey'])
                    else:
                        code = 400
                        message = 'urn:ietf:params:acme:error:malformed'
                        detail = 'old key is missing'
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'kid and account objects do not match'
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'account object is missing on inner payload'
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'kid is missing in outer header'

        self.logger.debug('Account._inner_payload_check() ended with: {0}:{1}'.format(code, detail))
        return(code, message, detail)

    def _key_change_validate(self, aname, outer_protected, inner_protected, inner_payload):
        """ validate key_change before exectution """
        self.logger.debug('Account._key_change_validate({0})'.format(aname))
        if 'jwk' in inner_protected:
            # check if we already have the key stored in DB
            key_exists = self._lookup(json.dumps(inner_protected['jwk']), 'jwk')
            if not key_exists:
                (code, message, detail) = self._inner_jws_check(outer_protected, inner_protected)

                if code == 200:
                    (code, message, detail) = self._inner_payload_check(aname, outer_protected, inner_payload)
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:badPublicKey'
                detail = 'public key does already exists'
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'inner jws is missing jwk'

        self.logger.debug('Account._key_change_validate() ended with: {0}:{1}'.format(code, detail))
        return(code, message, detail)

    def _key_change(self, aname, payload, protected):
        """ key change for a given account """
        self.logger.debug('Account._key_change({0})'.format(aname))

        if 'url' in protected:
            if 'key-change' in protected['url']:
                # check message
                (code, message, detail, inner_protected, inner_payload, _account_name) = self.message.check(json.dumps(payload), use_emb_key=True, skip_nonce_check=True)
                if code == 200:
                    (code, message, detail) = self._key_change_validate(aname, protected, inner_protected, inner_payload)
                    if code == 200:
                        data_dic = {'name' : aname, 'jwk' : json.dumps(inner_protected['jwk'])}
                        try:
                            result = self.dbstore.account_update(data_dic)
                        except BaseException as err_:
                            self.logger.critical('acme2certifier database error in Account._key_change(): {0}'.format(err_))
                            result = None
                        if result:
                            code = 200
                            message = None
                            detail = None
                        else:
                            code = 500
                            message = 'urn:ietf:params:acme:error:serverInternal'
                            detail = 'key rollover failed'
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'malformed request. not a key-change'
        else:
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'malformed request'

        return(code, message, detail)

    def _key_compare(self, aname, old_key):
        """ compare key with the one stored in database """
        self.logger.debug('Account._key_compare({0})'.format(aname))

        # load current public key from database
        try:
            pub_key = self.dbstore.jwk_load(aname)
        except BaseException as err_:
            self.logger.critical('acme2certifier database error in Account._key_compare(): {0}'.format(err_))
            pub_key = None

        if old_key and pub_key:
            # rewrite alg statement in pubkey statement
            if 'alg' in pub_key and 'alg' in old_key:
                if pub_key['alg'].startswith('ES') and old_key['alg'] == 'ECDSA':
                    pub_key['alg'] = 'ECDSA'

            if old_key == pub_key:
                code = 200
                message = None
                detail = None
            else:
                code = 401
                message = 'urn:ietf:params:acme:error:unauthorized'
                detail = 'wrong public key'
        else:
            code = 401
            message = 'urn:ietf:params:acme:error:unauthorized'
            detail = 'wrong public key'

        self.logger.debug('Account._key_compare() ended with: {0}'.format(code))
        return(code, message, detail)

    def _config_load(self):
        """" load config from file """
        self.logger.debug('Account._config_load()')
        config_dic = load_config()
        if 'Account' in config_dic:
            self.inner_header_nonce_allow = config_dic.getboolean('Account', 'inner_header_nonce_allow', fallback=False)
            self.ecc_only = config_dic.getboolean('Account', 'ecc_only', fallback=False)
            self.tos_check_disable = config_dic.getboolean('Account', 'tos_check_disable', fallback=False)
            self.contact_check_disable = config_dic.getboolean('Account', 'contact_check_disable', fallback=False)

        if 'EABhandler' in config_dic:
            self.logger.debug('Account._config.load(): loading eab_handler')
            if 'eab_handler_file' in config_dic['EABhandler']:
                # mandate eab check regardless if handler could get loaded or not
                self.eab_check = True
                try:
                    eab_handler_module = importlib.import_module(ca_handler_get(self.logger, config_dic['EABhandler']['eab_handler_file']))
                except BaseException as err_:
                    self.logger.critical('Account._config_load(): loading EABHandler configured in cfg failed with err: {0}'.format(err_))
                    try:
                        eab_handler_module = importlib.import_module('acme_srv.eab_handler')
                    except BaseException as err_:
                        eab_handler_module = None
                        self.logger.critical('Account._config_load(): loading default EABHandler failed with err: {0}'.format(err_))

                if eab_handler_module:
                    # store handler in variable
                    self.eab_handler = eab_handler_module.EABhandler
                else:
                    self.logger.critical('Account._config_load(): EABHandler configuration is missing in config file')

        if 'Directory' in config_dic:
            if 'tos_url' in config_dic['Directory']:
                self.tos_url = config_dic['Directory']['tos_url']
            if 'url_prefix' in config_dic['Directory']:
                self.path_dic = {k: config_dic['Directory']['url_prefix'] + v for k, v in self.path_dic.items()}
        self.logger.debug('Account._config_load() ended')


    def _lookup(self, value, field='name'):
        """ lookup account """
        self.logger.debug('Account._lookup({0}:{1})'.format(field, value))
        try:
            result = self.dbstore.account_lookup(field, value)
        except BaseException as err_:
            self.logger.critical('acme2certifier database error in Account._lookup(): {0}'.format(err_))
            result = None
        return result

    # pylint: disable=W0212
    def _name_get(self, content):
        """ get id for account depricated"""
        self.logger.debug('Account._name_get()')
        _deprecated = True
        return self.message._name_get(content)

    def _onlyreturnexisting(self, protected, payload):
        """ check onlyreturnexisting """
        self.logger.debug('Account._onlyreturnexisting(}')
        if 'onlyreturnexisting' in payload:
            if payload['onlyreturnexisting']:
                code = None
                message = None
                detail = None

                if 'jwk' in protected:
                    try:
                        result = self.dbstore.account_lookup('jwk', json.dumps(protected['jwk']))
                    except BaseException as err_:
                        self.logger.critical('acme2certifier database error in Account._onlyreturnexisting(): {0}'.format(err_))
                        result = None

                    if result:
                        code = 200
                        message = result['name']
                        detail = None
                    else:
                        code = 400
                        message = 'urn:ietf:params:acme:error:accountDoesNotExist'
                        detail = None
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'jwk structure missing'

            else:
                code = 400
                message = 'urn:ietf:params:acme:error:userActionRequired'
                detail = 'onlyReturnExisting must be true'
        else:
            code = 500
            message = 'urn:ietf:params:acme:error:serverInternal'
            detail = 'onlyReturnExisting without payload'

        self.logger.debug('Account.onlyreturnexisting() ended with:{0}'.format(code))
        return(code, message, detail)

    def _tos_check(self, content):
        """ check terms of service """
        self.logger.debug('Account._tos_check()')
        if 'termsofserviceagreed' in content:
            self.logger.debug('tos:{0}'.format(content['termsofserviceagreed']))
            if content['termsofserviceagreed']:
                code = 200
                message = None
                detail = None
            else:
                code = 403
                message = 'urn:ietf:params:acme:error:userActionRequired'
                detail = 'tosfalse'
        else:
            self.logger.debug('no tos statement found.')
            code = 403
            message = 'urn:ietf:params:acme:error:userActionRequired'
            detail = 'tosfalse'

        self.logger.debug('Account._tos_check() ended with:{0}'.format(code))
        return(code, message, detail)

    def new(self, content):
        """ generate a new account """
        self.logger.debug('Account.account_new()')

        response_dic = {}
        # check message but skip signature check as this is a new account (True)
        (code, message, detail, protected, payload, _account_name) = self.message.check(content, True)
        if code == 200:
            # onlyReturnExisting check
            if 'onlyreturnexisting' in payload:
                (code, message, detail) = self._onlyreturnexisting(protected, payload)
            else:
                # tos check
                if self.tos_url and not self.tos_check_disable:
                    (code, message, detail) = self._tos_check(payload)

                # check for external account binding
                if code == 200 and self.eab_check:
                    (code, message, detail) = self._eab_check(protected, payload)

                # contact check
                if code == 200 and not self.contact_check_disable:
                    (code, message, detail) = self._contact_check(payload)

                # add account to database
                if code == 200:
                    if 'contact' in payload:
                        contact_list = payload['contact']
                    else:
                        contact_list = []
                    (code, message, detail) = self._add(protected, payload, contact_list)

        if code in (200, 201):
            response_dic['data'] = {}
            if code == 201:
                response_dic['data'] = {
                    'status': 'valid',
                    'orders': '{0}{1}{2}/orders'.format(self.server_name, self.path_dic['acct_path'], message),
                }
                if 'contact' in payload:
                    response_dic['data']['contact'] = payload['contact']

            response_dic['header'] = {}
            response_dic['header']['Location'] = '{0}{1}{2}'.format(self.server_name, self.path_dic['acct_path'], message)

            # add exernal account binding
            if self.eab_check and 'externalaccountbinding' in payload:
                response_dic['data']['externalaccountbinding'] = payload['externalaccountbinding']

        else:
            if detail == 'tosfalse':
                detail = 'Terms of service must be accepted'

        # prepare/enrich response
        status_dic = {'code': code, 'message' : message, 'detail' : detail}
        response_dic = self.message.prepare_response(response_dic, status_dic)

        self.logger.debug('Account.account_new() returns: {0}'.format(json.dumps(response_dic)))
        return response_dic

    def parse(self, content):
        """ parse message """
        self.logger.debug('Account.parse()')

        response_dic = {}
        # check message
        (code, message, detail, protected, payload, account_name) = self.message.check(content)
        if code == 200:
            if 'status' in payload:
                # account deactivation
                if payload['status'].lower() == 'deactivated':
                    # account_name = self.message.name_get(protected)
                    (code, message, detail) = self._delete(account_name)
                    if code == 200:
                        response_dic['data'] = payload
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'status attribute without sense'
            elif 'contact' in payload:
                (code, message, detail) = self._contacts_update(account_name, payload)
                if code == 200:
                    account_obj = self._lookup(account_name)
                    response_dic['data'] = {}
                    response_dic['data']['status'] = 'valid'
                    response_dic['data']['key'] = json.loads(account_obj['jwk'])
                    response_dic['data']['contact'] = json.loads(account_obj['contact'])
                    response_dic['data']['createdAt'] = date_to_datestr(account_obj['created_at'])
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:accountDoesNotExist'
                    detail = 'update failed'
            elif 'payload' in payload:
                # this could be a key-change
                (code, message, detail) = self._key_change(account_name, payload, protected)
                if code == 200:
                    response_dic['data'] = {}
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'dont know what to do with this request'
        # prepare/enrich response
        status_dic = {'code': code, 'message' : message, 'detail' : detail}
        response_dic = self.message.prepare_response(response_dic, status_dic)

        self.logger.debug('Account.account_parse() returns: {0}'.format(json.dumps(response_dic)))
        return response_dic
예제 #2
0
class TestACMEHandler(unittest.TestCase):
    """ test class for ACMEHandler """
    acme = None
    def setUp(self):
        """ setup unittest """
        models_mock = MagicMock()
        models_mock.acme_srv.db_handler.DBstore.return_value = FakeDBStore
        modules = {'acme_srv.db_handler': models_mock}
        patch.dict('sys.modules', modules).start()
        import logging
        logging.basicConfig(level=logging.CRITICAL)
        self.logger = logging.getLogger('test_a2c')
        from acme_srv.message import Message
        self.message = Message(False, 'http://tester.local', self.logger)

    @patch('acme_srv.message.decode_message')
    def test_001_message_check(self, mock_decode):
        """ message_check failed bcs of decoding error """
        message = '{"foo" : "bar"}'
        mock_decode.return_value = (False, 'detail', None, None, None)
        self.assertEqual((400, 'urn:ietf:params:acme:error:malformed', 'detail', None, None, None), self.message.check(message))

    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_002_message_check(self, mock_decode, mock_nonce_check):
        """ message_check nonce check failed """
        message = '{"foo" : "bar"}'
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (400, 'badnonce', None)
        self.assertEqual((400, 'badnonce', None, 'protected', 'payload', None), self.message.check(message))

    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_003_message_check(self, mock_decode, mock_nonce_check):
        """ message check failed bcs account id lookup failed """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (200, None, None)
        message = '{"foo" : "bar"}'
        self.assertEqual((403, 'urn:ietf:params:acme:error:accountDoesNotExist', None, 'protected', 'payload', None), self.message.check(message))

    @patch('acme_srv.signature.Signature.check')
    @patch('acme_srv.message.Message._name_get')
    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_004_message_check(self, mock_decode, mock_nonce_check, mock_aname, mock_sig):
        """ message check failed bcs signature_check_failed """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (200, None, None)
        mock_aname.return_value = 'account_name'
        mock_sig.return_value = (False, 'error', 'detail')
        message = '{"foo" : "bar"}'
        self.assertEqual((403, 'error', 'detail', 'protected', 'payload', 'account_name'), self.message.check(message))

    @patch('acme_srv.signature.Signature.check')
    @patch('acme_srv.message.Message._name_get')
    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_005_message_check(self, mock_decode, mock_nonce_check, mock_aname, mock_sig):
        """ message check successful """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (200, None, None)
        mock_aname.return_value = 'account_name'
        mock_sig.return_value = (True, None, None)
        message = '{"foo" : "bar"}'
        self.assertEqual((200, None, None, 'protected', 'payload', 'account_name'), self.message.check(message))

    @patch('acme_srv.signature.Signature.check')
    @patch('acme_srv.message.Message._name_get')
    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_006_message_check(self, mock_decode, mock_nonce_check, mock_aname, mock_sig):
        """ message check successful as nonce check is disabled """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (400, 'badnonce', None)
        mock_aname.return_value = 'account_name'
        mock_sig.return_value = (True, None, None)
        message = '{"foo" : "bar"}'
        self.message.disable_dic = {'nonce_check_disable': True, 'signature_check_disable': False}
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.assertEqual((200, None, None, 'protected', 'payload', 'account_name'), self.message.check(message, skip_nonce_check=True))
        self.assertIn('ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****', lcm.output)

    @patch('acme_srv.signature.Signature.check')
    @patch('acme_srv.message.Message._name_get')
    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_007_message_check(self, mock_decode, mock_nonce_check, mock_aname, mock_sig):
        """ message check successful as nonce check is disabled """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (400, 'badnonce', None)
        mock_aname.return_value = 'account_name'
        mock_sig.return_value = (True, None, None)
        message = '{"foo" : "bar"}'
        self.message.disable_dic = {'nonce_check_disable': True, 'signature_check_disable': True}
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.assertEqual((200, None, None, 'protected', 'payload', None), self.message.check(message, skip_nonce_check=True))
        self.assertIn('ERROR:test_a2c:**** SIGNATURE_CHECK_DISABLE!!! Severe security issue ****', lcm.output)
        self.assertIn('ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****', lcm.output)

    @patch('acme_srv.signature.Signature.check')
    @patch('acme_srv.message.Message._name_get')
    @patch('acme_srv.nonce.Nonce.check')
    @patch('acme_srv.message.decode_message')
    def test_008_message_check(self, mock_decode, mock_nonce_check, mock_aname, mock_sig):
        """ message check successful as nonce check is disabled """
        mock_decode.return_value = (True, None, 'protected', 'payload', 'signature')
        mock_nonce_check.return_value = (400, 'badnonce', None)
        mock_aname.return_value = 'account_name'
        mock_sig.return_value = (True, None, None)
        message = '{"foo" : "bar"}'
        self.message.disable_dic = {'nonce_check_disable': False, 'signature_check_disable': False}
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.assertEqual((200, None, None, 'protected', 'payload', 'account_name'), self.message.check(message, skip_nonce_check=True))
        self.assertIn('INFO:test_a2c:skip nonce check of inner payload during keyrollover', lcm.output)

    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_009_message_prepare_response(self, mock_nnonce):
        """ Message.prepare_respons for code 200 and complete data """
        data_dic = {'data' : {'foo_data' : 'bar_bar'}, 'header': {'foo_header' : 'bar_header'}}
        mock_nnonce.return_value = 'new_nonce'
        config_dic = {'code' : 200, 'message' : 'message', 'detail' : 'detail'}
        self.assertEqual({'header': {'foo_header': 'bar_header', 'Replay-Nonce': 'new_nonce'}, 'code': 200, 'data': {'foo_data': 'bar_bar'}}, self.message.prepare_response(data_dic, config_dic))

    @patch('acme_srv.error.Error.enrich_error')
    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_010_message_prepare_response(self, mock_nnonce, mock_error):
        """ Message.prepare_respons for code 200 without header tag in response_dic """
        data_dic = {'data' : {'foo_data' : 'bar_bar'},}
        mock_nnonce.return_value = 'new_nonce'
        mock_error.return_value = 'mock_error'
        config_dic = {'code' : 200, 'message' : 'message', 'detail' : 'detail'}
        self.assertEqual({'header': {'Replay-Nonce': 'new_nonce'}, 'code': 200, 'data': {'foo_data': 'bar_bar'}}, self.message.prepare_response(data_dic, config_dic))

    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_011_message_prepare_response(self, mock_nnonce):
        """ Message.prepare_response for config_dic without code key """
        data_dic = {'data' : {'foo_data' : 'bar_bar'}, 'header': {'foo_header' : 'bar_header'}}
        mock_nnonce.return_value = 'new_nonce'
        # mock_error.return_value = 'mock_error'
        config_dic = {'message' : 'message', 'detail' : 'detail'}
        self.assertEqual({'header': {'foo_header': 'bar_header'}, 'code': 400, 'data': {'detail': 'http status code missing', 'message': 'urn:ietf:params:acme:error:serverInternal', 'status': 400}}, self.message.prepare_response(data_dic, config_dic))

    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_012_message_prepare_response(self, mock_nnonce):
        """ Message.prepare_response for config_dic without message key """
        data_dic = {'data' : {'foo_data' : 'bar_bar'}, 'header': {'foo_header' : 'bar_header'}}
        mock_nnonce.return_value = 'new_nonce'
        # mock_error.return_value = 'mock_error'
        config_dic = {'code' : 400, 'detail' : 'detail'}
        self.assertEqual({'header': {'foo_header': 'bar_header'}, 'code': 400, 'data': {'detail': 'detail', 'message': 'urn:ietf:params:acme:error:serverInternal', 'status': 400}}, self.message.prepare_response(data_dic, config_dic))

    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_013_message_prepare_response(self, mock_nnonce):
        """ Message.repare_response for config_dic without detail key """
        data_dic = {'data' : {'foo_data' : 'bar_bar'}, 'header': {'foo_header' : 'bar_header'}}
        mock_nnonce.return_value = 'new_nonce'
        config_dic = {'code' : 400, 'message': 'message'}
        self.assertEqual({'header': {'foo_header': 'bar_header'}, 'code': 400, 'data': {'message': 'message', 'status': 400}}, self.message.prepare_response(data_dic, config_dic))

    @patch('acme_srv.error.Error.enrich_error')
    @patch('acme_srv.nonce.Nonce.generate_and_add')
    def test_014_message_prepare_response(self, mock_nnonce, mock_error):
        """ Message.prepare_response for response_dic without data key """
        data_dic = {'header': {'foo_header' : 'bar_header'}}
        mock_nnonce.return_value = 'new_nonce'
        mock_error.return_value = 'mock_error'
        config_dic = {'code' : 400, 'message': 'message', 'detail' : 'detail'}
        self.assertEqual({'header': {'foo_header': 'bar_header'}, 'code': 400, 'data': {'detail': 'mock_error', 'message': 'message', 'status': 400}}, self.message.prepare_response(data_dic, config_dic))

    def test_015_message__name_get(self):
        """ test Message.name_get() with empty content"""
        protected = {}
        self.assertFalse(self.message._name_get(protected))

    def test_016_message__name_get(self):
        """ test Message.name_get() with kid with nonsens in content"""
        protected = {'kid' : 'foo'}
        self.assertEqual('foo', self.message._name_get(protected))

    def test_017_message__name_get(self):
        """ test Message.name_get() with wrong kid in content"""
        protected = {'kid' : 'http://tester.local/acme/account/account_name'}
        self.assertEqual(None, self.message._name_get(protected))

    def test_018_message__name_get(self):
        """ test Message.name_get() with correct kid in content"""
        protected = {'kid' : 'http://tester.local/acme/acct/account_name'}
        self.assertEqual('account_name', self.message._name_get(protected))

    def test_019_message__name_get(self):
        """ test Message.name_get() with 'jwk' in content but without URL"""
        protected = {'jwk' : 'jwk'}
        self.assertEqual(None, self.message._name_get(protected))

    def test_020_message__name_get(self):
        """ test Message.name_get() with 'jwk' and 'url' in content but url is wrong"""
        protected = {'jwk' : 'jwk', 'url' : 'url'}
        self.assertEqual(None, self.message._name_get(protected))

    def test_021_message__name_get(self):
        """ test Message.name_get() with 'jwk' and correct 'url' in content but no 'n' in jwk """
        protected = {'jwk' : 'jwk', 'url' : 'http://tester.local/acme/revokecert'}
        self.assertEqual(None, self.message._name_get(protected))

    def test_022_message__name_get(self):
        """ test Message.name_get() with 'jwk' and correct 'url' but account lookup failed """
        protected = {'jwk' : {'n' : 'n'}, 'url' : 'http://tester.local/acme/revokecert'}
        self.message.dbstore.account_lookup.return_value = {}
        self.assertEqual(None, self.message._name_get(protected))

    def test_023_message__name_get(self):
        """ test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data"""
        protected = {'jwk' : {'n' : 'n'}, 'url' : 'http://tester.local/acme/revokecert'}
        self.message.dbstore.account_lookup.return_value = {'bar' : 'foo'}
        self.assertEqual(None, self.message._name_get(protected))

    def test_024_message__name_get(self):
        """ test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data"""
        protected = {'jwk' : {'n' : 'n'}, 'url' : 'http://tester.local/acme/revokecert'}
        self.message.dbstore.account_lookup.return_value = {'name' : 'foo'}
        self.assertEqual('foo', self.message._name_get(protected))

    def test_025_message__name_get(self):
        """ test Message.name_get() - dbstore.account_lookup raises an exception """
        protected = {'jwk' : {'n' : 'n'}, 'url' : 'http://tester.local/acme/revokecert'}
        self.message.dbstore.account_lookup.side_effect = Exception('exc_mess__name_get')
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.message._name_get(protected)
        self.assertIn('CRITICAL:test_a2c:acme2certifier database error in Message._name_get(): exc_mess__name_get', lcm.output)

    def test_026__enter__(self):
        """ test enter """
        self.message.__enter__()

    @patch('acme_srv.message.load_config')
    def test_027_config_load(self, mock_load_cfg):
        """ test _config_load empty config """
        parser = configparser.ConfigParser()
        # parser['Account'] = {'foo': 'bar'}
        mock_load_cfg.return_value = parser
        self.message._config_load()
        self.assertFalse(self.message.disable_dic['nonce_check_disable'])
        self.assertFalse(self.message.disable_dic['signature_check_disable'])

    @patch('acme_srv.message.load_config')
    def test_028_config_load(self, mock_load_cfg):
        """ test _config_load """
        parser = configparser.ConfigParser()
        parser['Nonce'] = {'nonce_check_disable': False, 'signature_check_disable': False}
        mock_load_cfg.return_value = parser
        self.message._config_load()
        self.assertFalse(self.message.disable_dic['nonce_check_disable'])
        self.assertFalse(self.message.disable_dic['signature_check_disable'])

    @patch('acme_srv.message.load_config')
    def test_029_config_load(self, mock_load_cfg):
        """ test _config_load """
        parser = configparser.ConfigParser()
        parser['Nonce'] = {'nonce_check_disable': True, 'signature_check_disable': False}
        mock_load_cfg.return_value = parser
        self.message._config_load()
        self.assertTrue(self.message.disable_dic['nonce_check_disable'])
        self.assertFalse(self.message.disable_dic['signature_check_disable'])

    @patch('acme_srv.message.load_config')
    def test_030_config_load(self, mock_load_cfg):
        """ test _config_load """
        parser = configparser.ConfigParser()
        parser['Nonce'] = {'nonce_check_disable': False, 'signature_check_disable': True}
        mock_load_cfg.return_value = parser
        self.message._config_load()
        self.assertFalse(self.message.disable_dic['nonce_check_disable'])
        self.assertTrue(self.message.disable_dic['signature_check_disable'])

    @patch('acme_srv.message.load_config')
    def test_031_config_load(self, mock_load_cfg):
        """ test _config_load """
        parser = configparser.ConfigParser()
        parser['Directory'] = {'url_prefix': 'url_prefix', 'foo': 'bar'}
        mock_load_cfg.return_value = parser
        self.message._config_load()
        self.assertFalse(self.message.disable_dic['nonce_check_disable'])
        self.assertFalse(self.message.disable_dic['signature_check_disable'])
        self.assertEqual({'acct_path': 'url_prefix/acme/acct/', 'revocation_path': 'url_prefix/acme/revokecert'}, self.message.path_dic)