Beispiel #1
0
def newnonce(environ, start_response):
    """ generate a new nonce """
    if environ['REQUEST_METHOD'] in ['HEAD', 'GET']:
        nonce = Nonce(DEBUG, LOGGER)
        headers = [('Content-Type', 'text/plain'), ('Replay-Nonce', '{0}'.format(nonce.generate_and_add()))]
        status = '200 OK' if environ['REQUEST_METHOD'] == 'HEAD' else '204 No content'
        start_response(status, headers)
        return []
    else:
        start_response('405 {0}'.format(HTTP_CODE_DIC[405]), [('Content-Type', 'application/json')])
        return [json.dumps({'status':405, 'message':HTTP_CODE_DIC[405], 'detail': 'Wrong request type. Expected HEAD or GET.'}).encode('utf-8')]
Beispiel #2
0
 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.nonce import Nonce
     self.nonce = Nonce(False, self.logger)
Beispiel #3
0
 def __init__(self, debug=None, srv_name=None, logger=None):
     self.debug = debug
     self.logger = logger
     self.nonce = Nonce(self.debug, self.logger)
     self.dbstore = DBstore(self.debug, self.logger)
     self.server_name = srv_name
     self.path_dic = {
         'acct_path': '/acme/acct/',
         'revocation_path': '/acme/revokecert'
     }
     self.disable_dic = {
         'signature_check_disable': False,
         'nonce_check_disable': False
     }
     self._config_load()
Beispiel #4
0
def newnonce(request):
    """ new nonce """
    if request.method in ['HEAD', 'GET']:
        with Nonce(DEBUG, LOGGER) as nonce:
            if request.method == 'HEAD':
                response = HttpResponse('')
            else:
                response = HttpResponse(status=204)
            # generate nonce
            response['Replay-Nonce'] = nonce.generate_and_add()

            # logging
            logger_info(LOGGER, request.META['REMOTE_ADDR'],
                        request.META['PATH_INFO'],
                        {'header': {
                            'Replay-Nonce': response['Replay-Nonce']
                        }})
            # send response
            return response
    else:
        return JsonResponse(status=400,
                            data={
                                'status':
                                405,
                                'message':
                                'Method Not Allowed',
                                'detail':
                                'Wrong request type. Expected HEAD or GET.'
                            })
 def __init__(self, debug=None, srv_name=None, logger=None):
     self.server_name = srv_name
     self.debug = debug
     self.logger = logger
     self.dbstore = DBstore(debug, self.logger)
     self.message = Message(debug, self.server_name, self.logger)
     self.nonce = Nonce(debug, self.logger)
     self.validity = 86400
     self.expiry_check_disable = False
     self.path_dic = {'authz_path': '/acme/authz/'}
Beispiel #6
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.nonce import Nonce
        self.nonce = Nonce(False, self.logger)

    def test_001_nonce__new(self):
        """ test Nonce.new() and check if we get something back """
        self.assertIsNotNone(self.nonce._new())

    def test_002_nonce_generate_and_add(self):
        """ test Nonce.nonce_generate_and_add() and check if we get something back """
        self.assertIsNotNone(self.nonce.generate_and_add())

    def test_003_nonce_check(self):
        """ test Nonce.nonce_check_and_delete """
        self.assertEqual((400, 'urn:ietf:params:acme:error:badNonce', 'NONE'),
                         self.nonce.check({'foo': 'bar'}))

    def test_004_nonce_check(self):
        """ test Nonce.nonce_check_and_delete """
        self.assertEqual((200, None, None), self.nonce.check({'nonce': 'aaa'}))

    def test_005_nonce__check_and_delete(self):
        """ test Nonce.nonce_check_and_delete """
        self.assertEqual((200, None, None),
                         self.nonce._check_and_delete('aaa'))

    def test_006_nonce_generate_and_add(self):
        """ test Nonce._add() if dbstore.nonce_add raises an exception """
        self.nonce.dbstore.nonce_add.side_effect = Exception('exc_nonce_add')
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.nonce.generate_and_add()
        self.assertIn(
            'CRITICAL:test_a2c:acme2certifier database error in Nonce.generate_and_add(): exc_nonce_add',
            lcm.output)

    def test_007_nonce__check_and_delete(self):
        """ test Nonce._add() if dbstore.nonce_add raises an exception """
        self.nonce.dbstore.nonce_check.return_value = True
        self.nonce.dbstore.nonce_delete.side_effect = Exception(
            'exc_nonce_delete')
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.nonce._check_and_delete('nonce')
        self.assertIn(
            'CRITICAL:test_a2c:acme2certifier database error in Nonce._check_and_delete(): exc_nonce_delete',
            lcm.output)

    def test_008_nonce__check_and_delete(self):
        """ test Nonce._add() if dbstore.nonce_add raises an exception """
        self.nonce.dbstore.nonce_check.side_effect = Exception(
            'exc_nonce_check')
        with self.assertLogs('test_a2c', level='INFO') as lcm:
            self.nonce._check_and_delete('nonce')
        self.assertIn(
            'CRITICAL:test_a2c:acme2certifier database error in Nonce._check_and_delete(): exc_nonce_check',
            lcm.output)

    def test_009__enter_(self):
        """ test enter """
        self.nonce.__enter__()
Beispiel #7
0
class Message(object):
    """ Message  handler """
    def __init__(self, debug=None, srv_name=None, logger=None):
        self.debug = debug
        self.logger = logger
        self.nonce = Nonce(self.debug, self.logger)
        self.dbstore = DBstore(self.debug, self.logger)
        self.server_name = srv_name
        self.path_dic = {
            'acct_path': '/acme/acct/',
            'revocation_path': '/acme/revokecert'
        }
        self.disable_dic = {
            'signature_check_disable': False,
            'nonce_check_disable': False
        }
        self._config_load()

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

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

    def _config_load(self):
        """" load config from file """
        self.logger.debug('_config_load()')
        config_dic = load_config()
        if 'Nonce' in config_dic:
            self.disable_dic['nonce_check_disable'] = config_dic.getboolean(
                'Nonce', 'nonce_check_disable', fallback=False)
            self.disable_dic[
                'signature_check_disable'] = config_dic.getboolean(
                    'Nonce', 'signature_check_disable', fallback=False)

        if 'Directory' in config_dic:
            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()
                }

    def _name_get(self, content):
        """ get name for account """
        self.logger.debug('Message._name_get()')

        if 'kid' in content:
            self.logger.debug('kid: {0}'.format(content['kid']))
            kid = content['kid'].replace(
                '{0}{1}'.format(self.server_name, self.path_dic['acct_path']),
                '')
            if '/' in kid:
                kid = None
        elif 'jwk' in content and 'url' in content:
            if content['url'] == '{0}{1}'.format(
                    self.server_name, self.path_dic['revocation_path']):
                # this is needed for cases where we get a revocation message signed with account key but account name is missing)
                try:
                    account_list = self.dbstore.account_lookup(
                        'jwk', json.dumps(content['jwk']))
                except BaseException as err_:
                    self.logger.critical(
                        'acme2certifier database error in Message._name_get(): {0}'
                        .format(err_))
                    account_list = []
                if account_list:
                    if 'name' in account_list:
                        kid = account_list['name']
                    else:
                        kid = None
                else:
                    kid = None
            else:
                kid = None
        else:
            kid = None
        self.logger.debug('Message._name_get() returns: {0}'.format(kid))
        return kid

    def check(self, content, use_emb_key=False, skip_nonce_check=False):
        """ validate message """
        self.logger.debug('Message.check()')
        # disable signature check if paramter has been set
        if self.disable_dic['signature_check_disable']:
            self.logger.error(
                '**** SIGNATURE_CHECK_DISABLE!!! Severe security issue ****')
            skip_signature_check = True
        else:
            skip_signature_check = False

        # decode message
        (result, error_detail, protected, payload,
         _signature) = decode_message(self.logger, content)
        account_name = None
        if result:
            # decoding successful - check nonce for anti replay protection
            if skip_nonce_check or self.disable_dic['nonce_check_disable']:
                # nonce check can be skipped by configuration and in case of key-rollover
                if self.disable_dic['nonce_check_disable']:
                    self.logger.error(
                        '**** NONCE CHECK DISABLED!!! Severe security issue ****'
                    )
                else:
                    self.logger.info(
                        'skip nonce check of inner payload during keyrollover')
                code = 200
                message = None
                detail = None
            else:
                (code, message, detail) = self.nonce.check(protected)

            if code == 200 and not skip_signature_check:
                # nonce check successful - check signature
                account_name = self._name_get(protected)
                signature = Signature(self.debug, self.server_name,
                                      self.logger)
                # we need the decoded protected header to grab a key to verify signature
                (sig_check, error,
                 error_detail) = signature.check(account_name, content,
                                                 use_emb_key, protected)
                if sig_check:
                    code = 200
                    message = None
                    detail = None
                else:
                    code = 403
                    message = error
                    detail = error_detail
        else:
            # message could not get decoded
            code = 400
            message = 'urn:ietf:params:acme:error:malformed'
            detail = error_detail

        self.logger.debug('Message.check() ended with:{0}'.format(code))
        return (code, message, detail, protected, payload, account_name)

    def prepare_response(self, response_dic, status_dic):
        """ prepare response_dic """
        self.logger.debug('Message.prepare_response()')
        if 'code' not in status_dic:
            status_dic['code'] = 400
            status_dic['message'] = 'urn:ietf:params:acme:error:serverInternal'
            status_dic['detail'] = 'http status code missing'

        if 'message' not in status_dic:
            status_dic['message'] = 'urn:ietf:params:acme:error:serverInternal'

        if 'detail' not in status_dic:
            status_dic['detail'] = None

        # create response
        response_dic['code'] = status_dic['code']

        # create header if not existing
        if 'header' not in response_dic:
            response_dic['header'] = {}

        if status_dic['code'] >= 400:
            if status_dic['detail']:
                # some error occured get details
                error_message = Error(self.debug, self.logger)
                status_dic['detail'] = error_message.enrich_error(
                    status_dic['message'], status_dic['detail'])
                response_dic['data'] = {
                    'status': status_dic['code'],
                    'message': status_dic['message'],
                    'detail': status_dic['detail']
                }
            else:
                response_dic['data'] = {
                    'status': status_dic['code'],
                    'message': status_dic['message']
                }
                # response_dic['data'] = {'status': status_dic['code'], 'message': status_dic['message'], 'detail': None}
        else:
            # add nonce to header
            response_dic['header'][
                'Replay-Nonce'] = self.nonce.generate_and_add()

        return response_dic