Exemple #1
0
class Signature(object):
    """ Signature handler """
    def __init__(self, debug=None, srv_name=None, logger=None):
        self.debug = debug
        self.logger = logger
        self.dbstore = DBstore(self.debug, self.logger)
        self.server_name = srv_name
        self.revocation_path = '/acme/revokecert'

    def _jwk_load(self, kid):
        """ get key for a specific account id """
        self.logger.debug('Signature._jwk_load({0})'.format(kid))
        try:
            result = self.dbstore.jwk_load(kid)
        except BaseException as err_:
            print(err_)
            self.logger.critical(
                'acme2certifier database error in Signature._hwk_load(): {0}'.
                format(err_))
            result = None
        return result

    def check(self, aname, content, use_emb_key=False, protected=None):
        """ signature check """
        self.logger.debug('Signature.check({0})'.format(aname))
        result = False
        if content:
            error = None
            if aname:
                self.logger.debug('check signature against account key')
                pub_key = self._jwk_load(aname)
                if pub_key:
                    (result, error) = signature_check(self.logger, content,
                                                      pub_key)
                else:
                    error = 'urn:ietf:params:acme:error:accountDoesNotExist'
            elif use_emb_key:
                self.logger.debug(
                    'check signature against key includedn in jwk')
                if 'jwk' in protected:
                    pub_key = protected['jwk']
                    (result, error) = signature_check(self.logger, content,
                                                      pub_key)
                else:
                    error = 'urn:ietf:params:acme:error:accountDoesNotExist'
            else:
                error = 'urn:ietf:params:acme:error:accountDoesNotExist'
        else:
            error = 'urn:ietf:params:acme:error:malformed'

        self.logger.debug('Signature.check() ended with: {0}:{1}'.format(
            result, error))
        return (result, error, None)
Exemple #2
0
class Challenge(object):
    """ Challenge handler """
    def __init__(self, debug=None, srv_name=None, logger=None, expiry=3600):
        # self.debug = debug
        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 = {
            'chall_path': '/acme/chall/',
            'authz_path': '/acme/authz/'
        }
        self.expiry = expiry
        self.challenge_validation_disable = False
        self.tnauthlist_support = False
        self.dns_server_list = None

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

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

    def _challengelist_search(self,
                              key,
                              value,
                              vlist=('name', 'type', 'status__name', 'token')):
        """ get exsting challegnes for a given authorization """
        self.logger.debug('Challenge._challengelist_search()')

        try:
            challenge_list = self.dbstore.challenges_search(key, value, vlist)
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._challengelist_search(): {0}'
                .format(err_))
            challenge_list = []

        challenge_dic = {}
        for challenge in challenge_list:
            if challenge['type'] not in challenge_dic:
                challenge_dic[challenge['type']] = {}

            challenge_dic[challenge['type']]['token'] = challenge['token']
            challenge_dic[challenge['type']]['type'] = challenge['type']
            challenge_dic[challenge['type']]['url'] = challenge['name']
            challenge_dic[challenge['type']]['url'] = '{0}{1}{2}'.format(
                self.server_name, self.path_dic['chall_path'],
                challenge['name'])
            challenge_dic[challenge['type']]['name'] = challenge['name']

        challenge_list = []
        for challenge in challenge_dic:
            challenge_list.append(challenge_dic[challenge])

        self.logger.debug(
            'Challenge._challengelist_search() ended with: {0}'.format(
                challenge_list))
        return challenge_list

    def _check(self, challenge_name, payload):
        """ challenge check """
        self.logger.debug('Challenge._check({0})'.format(challenge_name))
        try:
            challenge_dic = self.dbstore.challenge_lookup(
                'name', challenge_name, [
                    'type', 'status__name', 'token', 'authorization__name',
                    'authorization__type', 'authorization__value',
                    'authorization__token',
                    'authorization__order__account__name'
                ])
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._check() lookup: {0}'
                .format(err_))
            challenge_dic = {}

        if 'type' in challenge_dic and 'authorization__value' in challenge_dic and 'token' in challenge_dic and 'authorization__order__account__name' in challenge_dic:
            try:
                pub_key = self.dbstore.jwk_load(
                    challenge_dic['authorization__order__account__name'])
            except BaseException as err_:
                self.logger.critical(
                    'acme2certifier database error in Challenge._check() jwk: {0}'
                    .format(err_))
                pub_key = None

            if pub_key:
                jwk_thumbprint = jwk_thumbprint_get(self.logger, pub_key)
                if challenge_dic['type'] == 'http-01' and jwk_thumbprint:
                    (result, invalid) = self._validate_http_challenge(
                        challenge_name, challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint)
                elif challenge_dic['type'] == 'dns-01' and jwk_thumbprint:
                    (result, invalid) = self._validate_dns_challenge(
                        challenge_name, challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint)
                elif challenge_dic['type'] == 'tls-alpn-01' and jwk_thumbprint:
                    (result, invalid) = self._validate_alpn_challenge(
                        challenge_name, challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint)
                elif challenge_dic[
                        'type'] == 'tkauth-01' and jwk_thumbprint and self.tnauthlist_support:
                    (result, invalid) = self._validate_tkauth_challenge(
                        challenge_name, challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint, payload)
                else:
                    self.logger.debug(
                        'unknown challenge type "{0}". Setting check result to False'
                        .format(challenge_dic['type']))
                    result = False
                    invalid = True
            else:
                result = False
                invalid = False
        else:
            result = False
            invalid = False
        self.logger.debug('challenge._check() ended with: {0}/{1}'.format(
            result, invalid))
        return (result, invalid)

    def _existing_challenge_validate(self, challenge_list):
        """ validate an existing challenge set """
        self.logger.debug('Challenge._existing_challenge_validate()')
        for challenge in challenge_list:
            _challenge_check = self._validate(challenge, {})

    def _info(self, challenge_name):
        """ get challenge details """
        self.logger.debug('Challenge._info({0})'.format(challenge_name))
        try:
            challenge_dic = self.dbstore.challenge_lookup(
                'name',
                challenge_name,
                vlist=('type', 'token', 'status__name', 'validated'))
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._info(): {0}'.
                format(err_))
            challenge_dic = {}

        if 'status' in challenge_dic and challenge_dic['status'] == 'valid':
            if 'validated' in challenge_dic:
                # convert validated timestamp to RFC3339 format - if it fails remove key from dictionary
                try:
                    challenge_dic['validated'] = uts_to_date_utc(
                        challenge_dic['validated'])
                except BaseException:
                    challenge_dic.pop('validated')
        else:
            if 'validated' in challenge_dic:
                challenge_dic.pop('validated')

        self.logger.debug('Challenge._info({0}) ended'.format(challenge_name))
        return challenge_dic

    def _config_load(self):
        """" load config from file """
        self.logger.debug('Challenge._config_load()')
        config_dic = load_config()
        if 'Challenge' in config_dic:
            self.challenge_validation_disable = config_dic.getboolean(
                'Challenge', 'challenge_validation_disable', fallback=False)
            if 'dns_server_list' in config_dic['Challenge']:
                try:
                    self.dns_server_list = json.loads(
                        config_dic['Challenge']['dns_server_list'])
                except BaseException as err_:
                    self.logger.warning(
                        'Challenge._config_load() failed with error: {0}'.
                        format(err_))

        if 'Order' in config_dic:
            self.tnauthlist_support = config_dic.getboolean(
                'Order', 'tnauthlist_support', fallback=False)
        self.logger.debug('Challenge._config_load() ended.')

    def _name_get(self, url):
        """ get challenge """
        self.logger.debug('Challenge.get_name({0})'.format(url))
        url_dic = parse_url(self.logger, url)
        challenge_name = url_dic['path'].replace(self.path_dic['chall_path'],
                                                 '')
        if '/' in challenge_name:
            (challenge_name, _sinin) = challenge_name.split('/', 1)
        return challenge_name

    def _new(self, authz_name, mtype, token):
        """ new challenge """
        self.logger.debug('Challenge._new({0})'.format(mtype))

        challenge_name = generate_random_string(self.logger, 12)

        data_dic = {
            'name': challenge_name,
            'expires': self.expiry,
            'type': mtype,
            'token': token,
            'authorization': authz_name,
            'status': 2
        }

        try:
            chid = self.dbstore.challenge_add(data_dic)
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._new(): {0}'.
                format(err_))
            chid = None

        challenge_dic = {}
        if chid:
            challenge_dic['type'] = mtype
            challenge_dic['url'] = '{0}{1}{2}'.format(
                self.server_name, self.path_dic['chall_path'], challenge_name)
            challenge_dic['token'] = token
            if mtype == 'tkauth-01':
                challenge_dic['tkauth-type'] = 'atc'
        return challenge_dic

    def _update(self, data_dic):
        """ update challenge """
        self.logger.debug('Challenge._update({0})'.format(data_dic))
        try:
            self.dbstore.challenge_update(data_dic)
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._update(): {0}'.
                format(err_))
        self.logger.debug('Challenge._update() ended')

    def _update_authz(self, challenge_name, data_dic):
        """ update authorizsation based on challenge_name """
        self.logger.debug(
            'Challenge._update_authz({0})'.format(challenge_name))
        try:
            # lookup autorization based on challenge_name
            authz_name = self.dbstore.challenge_lookup(
                'name', challenge_name,
                ['authorization__name'])['authorization']
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._update_authz() lookup: {0}'
                .format(err_))
            authz_name = None

        if authz_name:
            data_dic['name'] = authz_name
        try:
            # update authorization
            self.dbstore.authorization_update(data_dic)
        except BaseException as err_:
            self.logger.critical(
                'acme2certifier database error in Challenge._update_authz() upd: {0}'
                .format(err_))

        self.logger.debug('Challenge._update_authz() ended')

    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 _validate_alpn_challenge(self, challenge_name, fqdn, token,
                                 jwk_thumbprint):
        """ validate dns challenge """
        self.logger.debug(
            'Challenge._validate_alpn_challenge({0}:{1}:{2})'.format(
                challenge_name, fqdn, token))

        # resolve name
        (response, invalid) = fqdn_resolve(fqdn, self.dns_server_list)
        self.logger.debug('fqdn_resolve() ended with: {0}/{1}'.format(
            response, invalid))

        # we are expecting a certifiate extension which is the sha256 hexdigest of token in a byte structure
        # which is base 64 encoded '0420' has been taken from acme.sh sources
        sha256_digest = sha256_hash_hex(
            self.logger, '{0}.{1}'.format(token, jwk_thumbprint))
        extension_value = b64_encode(
            self.logger, bytearray.fromhex('0420{0}'.format(sha256_digest)))
        self.logger.debug('computed value: {0}'.format(extension_value))

        if not invalid:
            cert = servercert_get(self.logger, fqdn)
            if cert:
                san_list = cert_san_get(self.logger, cert, recode=False)
                fqdn_in_san = fqdn_in_san_check(self.logger, san_list, fqdn)
                if fqdn_in_san:
                    extension_list = cert_extensions_get(self.logger,
                                                         cert,
                                                         recode=False)
                    if extension_value in extension_list:
                        self.logger.debug('alpn validation successful')
                        result = True
                    else:
                        self.logger.debug('alpn validation not successful')
                        result = False
                else:
                    self.logger.debug('fqdn check against san failed')
                    result = False
            else:
                self.logger.debug('no cert returned...')
                result = False
        else:
            result = False

        self.logger.debug(
            'Challenge._validate_alpn_challenge() ended with: {0}/{1}'.format(
                result, invalid))
        return (result, invalid)

    def _validate_dns_challenge(self, challenge_name, fqdn, token,
                                jwk_thumbprint):
        """ validate dns challenge """
        self.logger.debug(
            'Challenge._validate_dns_challenge({0}:{1}:{2})'.format(
                challenge_name, fqdn, token))

        # handle wildcard domain
        fqdn = self._wcd_manipulate(fqdn)

        # rewrite fqdn
        fqdn = '_acme-challenge.{0}'.format(fqdn)

        # resolve name
        (_response, invalid) = fqdn_resolve(fqdn)

        if not invalid:
            # compute sha256 hash
            _hash = b64_url_encode(
                self.logger,
                sha256_hash(self.logger,
                            '{0}.{1}'.format(token, jwk_thumbprint)))
            # query dns
            txt = txt_get(self.logger, fqdn, self.dns_server_list)

            # compare computed hash with result from DNS query
            self.logger.debug(
                'response_got: {0} response_expected: {1}'.format(txt, _hash))
            if _hash == txt:
                self.logger.debug('validation successful')
                result = True
            else:
                self.logger.debug('validation not successful')
                result = False
        else:
            result = False

        self.logger.debug(
            'Challenge._validate_dns_challenge() ended with: {0}/{1}'.format(
                result, invalid))
        return (result, invalid)

    def _validate_http_challenge(self, challenge_name, fqdn, token,
                                 jwk_thumbprint):
        """ validate http challenge """
        self.logger.debug(
            'Challenge._validate_http_challenge({0}:{1}:{2})'.format(
                challenge_name, fqdn, token))
        # resolve name
        (response, invalid) = fqdn_resolve(fqdn, self.dns_server_list)
        self.logger.debug('fqdn_resolve() ended with: {0}/{1}'.format(
            response, invalid))
        if not invalid:
            req = url_get(
                self.logger,
                'http://{0}/.well-known/acme-challenge/{1}'.format(
                    fqdn, token), self.dns_server_list)
            # make challenge validation unsuccessful
            # req = url_get(self.logger, 'http://{0}/.well-known/acme-challenge/{1}'.format('test.test', 'foo.bar.some.not.existing.ressource'))
            if req:
                response_got = req.splitlines()[0]
                response_expected = '{0}.{1}'.format(token, jwk_thumbprint)
                self.logger.debug(
                    'response_got: {0} response_expected: {1}'.format(
                        response_got, response_expected))
                if response_got == response_expected:
                    self.logger.debug('validation successful')
                    result = True
                else:
                    self.logger.debug('validation not successful')
                    result = False
            else:
                self.logger.debug(
                    'validation not successfull.. no request object')
                result = False
        else:
            result = False

        self.logger.debug(
            'Challenge._validate_http_challenge() ended with: {0}/{1}'.format(
                result, invalid))
        return (result, invalid)

    def _validate_tkauth_challenge(self, challenge_name, tnauthlist, _token,
                                   _jwk_thumbprint, payload):
        """ validate tkauth challenge """
        self.logger.debug(
            'Challenge._validate_tkauth_challenge({0}:{1}:{2})'.format(
                challenge_name, tnauthlist, payload))

        result = True
        invalid = False
        self.logger.debug(
            'Challenge._validate_tkauth_challenge() ended with: {0}/{1}'.
            format(result, invalid))
        return (result, invalid)

    def _validate_tnauthlist_payload(self, payload, challenge_dic):
        """ check payload in cae tnauthlist option has been set """
        self.logger.debug(
            'Challenge._validate_tnauthlist_payload({0}:{1})'.format(
                payload, challenge_dic))

        code = 400
        message = None
        detail = None

        if 'type' in challenge_dic:
            if challenge_dic['type'] == 'tkauth-01':
                self.logger.debug('tkauth identifier found')
                # check if we havegot an atc claim in the challenge request
                if 'atc' in payload:
                    # check if we got a SPC token in the challenge request
                    if not bool(payload['atc']):
                        code = 400
                        message = 'urn:ietf:params:acme:error:malformed'
                        detail = 'SPC token is missing'
                    else:
                        code = 200
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'atc claim is missing'
            else:
                code = 200
        else:
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'invalid challenge: {0}'.format(challenge_dic)

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

    def _wcd_manipulate(self, fqdn):
        """ wildcard domain handling """
        self.logger.debug(
            'Challenge._wc_manipulate() for fqdn: {0}'.format(fqdn))
        if fqdn.startswith('*.'):
            fqdn = fqdn[2:]
        self.logger.debug(
            'Challenge._wc_manipulate() ended with: {0}'.format(fqdn))
        return fqdn

    def challengeset_get(self, authz_name, auth_status, token, tnauth):
        """ get the challengeset for an authorization """
        self.logger.debug(
            'Challenge.challengeset_get() for auth: {0}'.format(authz_name))
        # check database if there are exsting challenges for a particular authorization
        challenge_list = self._challengelist_search('authorization__name',
                                                    authz_name)

        if challenge_list:
            self.logger.debug('Challenges found.')
            # trigger challenge validation
            challenge_name_list = []
            for challenge in challenge_list:
                challenge_name_list.append(challenge.pop('name'))
            if auth_status == 'pending':
                self._existing_challenge_validate(challenge_name_list)

        else:
            # new challenges to be created
            self.logger.debug('Challenges not found. Create a new set.')
            challenge_list = self.new_set(authz_name, token, tnauth)

        return challenge_list

    def get(self, url):
        """ get challenge details based on get request """
        self.logger.debug('Challenge.get({0})'.format(url))
        challenge_name = self._name_get(url)
        response_dic = {}
        response_dic['code'] = 200
        response_dic['data'] = self._info(challenge_name)
        return response_dic

    def new_set(self, authz_name, token, tnauth=False):
        """ net challenge set """
        self.logger.debug('Challenge.new_set({0}, {1})'.format(
            authz_name, token))
        challenge_list = []
        if not tnauth:
            challenge_list.append(self._new(authz_name, 'http-01', token))
            challenge_list.append(self._new(authz_name, 'dns-01', token))
            challenge_list.append(self._new(authz_name, 'tls-alpn-01', token))
        else:
            challenge_list.append(self._new(authz_name, 'tkauth-01', token))
        self.logger.debug(
            'Challenge._new_set returned ({0})'.format(challenge_list))
        return challenge_list

    def parse(self, content):
        """ new oder request """
        self.logger.debug('Challenge.parse()')

        response_dic = {}
        # check message
        (code, message, detail, protected, payload,
         _account_name) = self.message.check(content)

        if code == 200:
            if 'url' in protected:
                challenge_name = self._name_get(protected['url'])
                if challenge_name:
                    challenge_dic = self._info(challenge_name)

                    if challenge_dic:
                        # check tnauthlist payload
                        if self.tnauthlist_support:
                            (code, message,
                             detail) = self._validate_tnauthlist_payload(
                                 payload, challenge_dic)

                        if code == 200:
                            # start validation
                            if 'status' in challenge_dic:
                                if challenge_dic['status'] != 'valid':
                                    _validation = self._validate(
                                        challenge_name, payload)
                                    # query challenge again (bcs. it could get updated by self._validate)
                                    challenge_dic = self._info(challenge_name)
                            else:
                                # rather unlikely that we run in this situation but you never know
                                _validation = self._validate(
                                    challenge_name, payload)
                                # query challenge again (bcs. it could get updated by self._validate)
                                challenge_dic = self._info(challenge_name)

                            response_dic['data'] = {}
                            challenge_dic['url'] = protected['url']
                            code = 200
                            response_dic['data'] = {}
                            response_dic['data'] = challenge_dic
                            response_dic['header'] = {}
                            response_dic['header'][
                                'Link'] = '<{0}{1}>;rel="up"'.format(
                                    self.server_name,
                                    self.path_dic['authz_path'])
                    else:
                        code = 400
                        message = 'urn:ietf:params:acme:error:malformed'
                        detail = 'invalid challenge: {0}'.format(
                            challenge_name)
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'could not get challenge'
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'url missing in protected header'

        # prepare/enrich response
        status_dic = {'code': code, 'message': message, 'detail': detail}
        response_dic = self.message.prepare_response(response_dic, status_dic)
        self.logger.debug('challenge.parse() returns: {0}'.format(
            json.dumps(response_dic)))
        return response_dic
Exemple #3
0
class Challenge(object):
    """ Challenge handler """
    def __init__(self, debug=None, srv_name=None, logger=None, expiry=3600):
        # self.debug = debug
        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 = {
            'chall_path': '/acme/chall/',
            'authz_path': '/acme/authz/'
        }
        self.expiry = expiry
        self.challenge_validation_disable = False
        self.tnauthlist_support = False

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

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

    def check(self, challenge_name, payload):
        """ challene check """
        self.logger.debug('challenge.check({0})'.format(challenge_name))
        challenge_dic = self.dbstore.challenge_lookup('name', challenge_name, [
            'type', 'status__name', 'token', 'authorization__name',
            'authorization__type', 'authorization__value',
            'authorization__token', 'authorization__order__account__name'
        ])
        if 'type' in challenge_dic and 'authorization__value' in challenge_dic and 'token' in challenge_dic and 'authorization__order__account__name' in challenge_dic:
            pub_key = self.dbstore.jwk_load(
                challenge_dic['authorization__order__account__name'])
            if pub_key:
                jwk_thumbprint = jwk_thumbprint_get(self.logger, pub_key)
                if challenge_dic['type'] == 'http-01' and jwk_thumbprint:
                    result = self.validate_http_challenge(
                        challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint)
                elif challenge_dic['type'] == 'dns-01' and jwk_thumbprint:
                    result = self.validate_dns_challenge(
                        challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint)
                elif challenge_dic[
                        'type'] == 'tkauth-01' and jwk_thumbprint and self.tnauthlist_support:
                    result = self.validate_tkauth_challenge(
                        challenge_dic['authorization__value'],
                        challenge_dic['token'], jwk_thumbprint, payload)
                else:
                    self.logger.debug(
                        'unknown challenge type "{0}". Setting check result to False'
                        .format(challenge_dic['type']))
                    result = False
            else:
                result = False
        else:
            result = False
        self.logger.debug('challenge.check() ended with: {0}'.format(result))
        return result

    def get(self, url):
        """ get challenge details based on get request """
        self.logger.debug('challenge.new_get({0})'.format(url))
        challenge_name = self.name_get(url)
        response_dic = {}
        response_dic['code'] = 200
        response_dic['data'] = self.info(challenge_name)
        return response_dic

    def info(self, challenge_name):
        """ get challenge details """
        self.logger.debug('Challenge.info({0})'.format(challenge_name))
        challenge_dic = self.dbstore.challenge_lookup('name', challenge_name)
        return challenge_dic

    def load_config(self):
        """" load config from file """
        self.logger.debug('Challenge.load_config()')
        config_dic = load_config()
        if 'Challenge' in config_dic:
            self.challenge_validation_disable = config_dic.getboolean(
                'Challenge', 'challenge_validation_disable', fallback=False)
        if 'Order' in config_dic:
            self.tnauthlist_support = config_dic.getboolean(
                'Order', 'tnauthlist_support', fallback=False)
        self.logger.debug('Challenge.load_config() ended.')

    def name_get(self, url):
        """ get challenge """
        self.logger.debug('Challenge.get_name({0})'.format(url))
        url_dic = parse_url(self.logger, url)
        challenge_name = url_dic['path'].replace(self.path_dic['chall_path'],
                                                 '')
        if '/' in challenge_name:
            (challenge_name, _sinin) = challenge_name.split('/', 1)
        return challenge_name

    def new(self, authz_name, mtype, token):
        """ new challenge """
        self.logger.debug('Challenge.new({0})'.format(mtype))

        challenge_name = generate_random_string(self.logger, 12)

        data_dic = {
            'name': challenge_name,
            'expires': self.expiry,
            'type': mtype,
            'token': token,
            'authorization': authz_name,
            'status': 2
        }
        chid = self.dbstore.challenge_add(data_dic)

        challenge_dic = {}
        if chid:
            challenge_dic['type'] = mtype
            challenge_dic['url'] = '{0}{1}{2}'.format(
                self.server_name, self.path_dic['chall_path'], challenge_name)
            challenge_dic['token'] = token
            if mtype == 'tkauth-01':
                challenge_dic['tkauth-type'] = 'atc'
        return challenge_dic

    def new_set(self, authz_name, token, tnauth=False):
        """ net challenge set """
        self.logger.debug('Challenge.new_set({0}, {1})'.format(
            authz_name, token))
        challenge_list = []
        if not tnauth:
            challenge_list.append(self.new(authz_name, 'http-01', token))
            challenge_list.append(self.new(authz_name, 'dns-01', token))
        else:
            challenge_list.append(self.new(authz_name, 'tkauth-01', token))
        self.logger.debug(
            'Challenge.new_set returned ({0})'.format(challenge_list))
        return challenge_list

    def parse(self, content):
        """ new oder request """
        self.logger.debug('Challenge.parse()')

        response_dic = {}
        # check message
        (code, message, detail, protected, payload,
         _account_name) = self.message.check(content)

        if code == 200:
            if 'url' in protected:
                challenge_name = self.name_get(protected['url'])
                if challenge_name:
                    challenge_dic = self.info(challenge_name)

                    # check tnauthlist payload
                    if self.tnauthlist_support:
                        (code, message,
                         detail) = self.validate_tnauthlist_payload(
                             payload, challenge_dic)

                    if code == 200:
                        # update challenge state to 'processing' - i am not so sure about this
                        # self.update({'name' : challenge_name, 'status' : 4})
                        # start validation
                        _validation = self.validate(challenge_name, payload)
                        if challenge_dic:
                            response_dic['data'] = {}
                            challenge_dic['url'] = protected['url']
                            code = 200
                            response_dic['data'] = {}
                            response_dic['data'] = challenge_dic
                            response_dic['header'] = {}
                            response_dic['header'][
                                'Link'] = '<{0}{1}>;rel="up"'.format(
                                    self.server_name,
                                    self.path_dic['authz_path'])
                        else:
                            code = 400
                            message = 'urn:ietf:params:acme:error:malformed'
                            detail = 'invalid challenge: {0}'.format(
                                challenge_name)
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'could not get challenge'
            else:
                code = 400
                message = 'urn:ietf:params:acme:error:malformed'
                detail = 'url missing in protected header'

        # prepare/enrich response
        status_dic = {'code': code, 'message': message, 'detail': detail}
        response_dic = self.message.prepare_response(response_dic, status_dic)
        self.logger.debug('challenge.parse() returns: {0}'.format(
            json.dumps(response_dic)))
        return response_dic

    def update(self, data_dic):
        """ update challenge """
        self.logger.debug('Challenge.update({0})'.format(data_dic))
        self.dbstore.challenge_update(data_dic)

    def update_authz(self, challenge_name):
        """ update authorizsation based on challenge_name """
        self.logger.debug('Challenge.update_authz({0})'.format(challenge_name))

        # lookup autorization based on challenge_name
        authz_name = self.dbstore.challenge_lookup(
            'name', challenge_name, ['authorization__name'])['authorization']
        self.dbstore.authorization_update({
            'name': authz_name,
            'status': 'valid'
        })
        # print(authz_name)

    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
        else:
            challenge_check = self.check(challenge_name, payload)

        if challenge_check:
            self.update({'name': challenge_name, 'status': 'valid'})
            # authorization update to ready state
            self.update_authz(challenge_name)

        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))

    def validate_dns_challenge(self, fqdn, token, jwk_thumbprint):
        """ validate dns challenge """
        self.logger.debug('Challenge.validate_dns_challenge()')

        # rewrite fqdn
        fqdn = '_acme-challenge.{0}'.format(fqdn)

        # compute sha256 hash
        _hash = b64_url_encode(
            self.logger,
            sha256_hash(self.logger, '{0}.{1}'.format(token, jwk_thumbprint)))
        # query dns
        txt = txt_get(self.logger, fqdn)

        # compare computed hash with result from DNS query
        if _hash == txt:
            result = True
        else:
            result = False
        self.logger.debug(
            'Challenge.validate_dns_challenge() ended with: {0}'.format(
                result))
        return result

    def validate_http_challenge(self, fqdn, token, jwk_thumbprint):
        """ validate http challenge """
        self.logger.debug('Challenge.validate_http_challenge()')

        req = url_get(
            self.logger,
            'http://{0}/.well-known/acme-challenge/{1}'.format(fqdn, token))
        if req:
            if req.splitlines()[0] == '{0}.{1}'.format(token, jwk_thumbprint):
                result = True
            else:
                result = False
        else:
            result = False
        self.logger.debug(
            'Challenge.validate_http_challenge() ended with: {0}'.format(
                result))
        return result

    def validate_tkauth_challenge(self, tnauthlist, _token, _jwk_thumbprint,
                                  payload):
        """ validate tkauth challenge """
        self.logger.debug(
            'Challenge.validate_tkauth_challenge({0}:{1})'.format(
                tnauthlist, payload))

        result = True
        self.logger.debug(
            'Challenge.validate_tkauth_challenge() ended with: {0}'.format(
                result))
        return result

    def validate_tnauthlist_payload(self, payload, challenge_dic):
        """ check payload in cae tnauthlist option has been set """
        self.logger.debug(
            'Challenge.validate_tnauthlist_payload({0}:{1})'.format(
                payload, challenge_dic))

        code = 400
        message = None
        detail = None

        if 'type' in challenge_dic:
            if challenge_dic['type'] == 'tkauth-01':
                self.logger.debug('tkauth identifier found')
                # check if we havegot an atc claim in the challenge request
                if 'atc' in payload:
                    # check if we got a SPC token in the challenge request
                    if not bool(payload['atc']):
                        code = 400
                        message = 'urn:ietf:params:acme:error:malformed'
                        detail = 'SPC token is missing'
                    else:
                        code = 200
                else:
                    code = 400
                    message = 'urn:ietf:params:acme:error:malformed'
                    detail = 'atc claim is missing'
            else:
                code = 200
        else:
            message = 'urn:ietf:params:acme:error:malformed'
            detail = 'invalid challenge: {0}'.format(challenge_dic)

        self.logger.debug(
            'Challenge.validate_tnauthlist_payload() ended with:{0}'.format(
                code))
        return (code, message, detail)
Exemple #4
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

    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, 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),
                    }
                    try:
                        (db_name, new) = self.dbstore.account_add(data_dic)
                    except BaseException as err_:
                        self.logger.critical(
                            'Database error in Account._add(): {0}'.format(
                                err_))
                        db_name = None
                        new = False
                    self.logger.debug('god 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 _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('_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 'Directory' in config_dic:
            if 'tos_url' in config_dic['Directory']:
                self.tos_url = config_dic['Directory']['tos_url']

    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)

                # 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, 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)
        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