示例#1
0
文件: crypto.py 项目: Nyrio/cms
def encrypt_binary(pt, key_hex):
    """Encrypt the plaintext with the 16-bytes key.

    A random salt is added to avoid having the same input being
    encrypted to the same output.

    pt (bytes): the "plaintext" to encode.
    key_hex (str): a 16-bytes key in hex (a string of 32 hex chars).

    return (str): pt encrypted using the key, in a format URL-safe
        (more precisely, base64-encoded with alphabet "a-zA-Z0-9.-_").

    """
    key = hex_to_bin(key_hex)
    # Pad the plaintext to make its length become a multiple of the block size
    # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes
    # 0x00 as needed. If the length of the message is already a multiple of 16
    # bytes, add a new block.
    pt_pad = pt + b'\01' + b'\00' * (16 - (len(pt) + 1) % 16)
    # The IV is a random block used to differentiate messages encrypted with
    # the same key. An IV should never be used more than once in the lifetime
    # of the key. In this way encrypting the same plaintext twice will produce
    # different ciphertexts.
    iv = get_random_key()
    # Initialize the AES cipher with the given key and IV.
    aes = AES.new(key, AES.MODE_CBC, iv)
    ct = aes.encrypt(pt_pad)
    # Convert the ciphertext in a URL-safe base64 encoding
    ct_b64 = bin_to_b64(iv + ct)\
        .replace('+', '-').replace('/', '_').replace('=', '.')
    return ct_b64
示例#2
0
    def wsgi_app(self, environ, start_response):
        """Invoke the class as a WSGI application.

        environ (dict): the WSGI environment.
        start_response (function): the WSGI start_response callable.
        returns (iterable): the WSGI response data.

        """
        self._local.request = Request(environ)
        self._local.cookie = JSONSecureCookie.load_cookie(
            self._request, AWSAuthMiddleware.COOKIE,
            hex_to_bin(config.secret_key))
        self._verify_cookie()

        def my_start_response(status, headers, exc_info=None):
            """Wrapper for the server-provided start_response.

            Once called by the application, modifies the received
            parameters to add the Set-Cookie parameter (if needed)
            and then forwards everything to the server.

            """
            response = Response(status=status, headers=headers)
            self._cookie.save_cookie(response,
                                     AWSAuthMiddleware.COOKIE,
                                     httponly=True)
            return start_response(status, response.headers.to_wsgi_list(),
                                  exc_info)

        return self._app(environ, my_start_response)
示例#3
0
文件: crypto.py 项目: Nyrio/cms
def decrypt_binary(ct_b64, key_hex):
    """Decrypt a ciphertext generated by encrypt_binary.

    ct_b64 (str): the ciphertext as produced by encrypt_binary.
    key_hex (str): the 16-bytes key in hex format used to encrypt.

    return (bytes): the plaintext.

    raise (ValueError): if the ciphertext is invalid.

    """
    key = hex_to_bin(key_hex)
    try:
        # Convert the ciphertext from a URL-safe base64 encoding to a
        # bytestring, which contains both the IV (the first 16 bytes) as well
        # as the encrypted padded plaintext.
        iv_ct = b64_to_bin(
            ct_b64.replace('-', '+').replace('_', '/').replace('.', '='))
        aes = AES.new(key, AES.MODE_CBC, iv_ct[:16])
        # Get the padded plaintext.
        pt_pad = aes.decrypt(iv_ct[16:])
        # Remove the padding.
        # TODO check that the padding is correct, i.e. that it contains at most
        # 15 bytes 0x00 preceded by a byte 0x01.
        pt = pt_pad.rstrip(b'\x00')[:-1]
        return pt
    except (TypeError, binascii.Error):
        raise ValueError('Could not decode from base64.')
    except ValueError:
        raise ValueError('Wrong AES cryptogram length.')
示例#4
0
文件: crypto.py 项目: romeorizzi/cms
def encrypt_string(pt, key_hex):
    """Encrypt the plaintext (pt) with the 16-bytes key. Moreover, it
    encrypts it using a random IV, so that encrypting repeatedly the
    same string gives different outputs. This way no analisys can made
    when the same number is used in different contexts. The generated
    string uses the alphabet { 'a', ..., 'z', 'A', ..., 'Z', '0', ...,
    '9', '.', '-', '_' }, so it is safe to use in URLs.

    """
    key = hex_to_bin(key_hex)
    # Pad the plaintext to make its length become a multiple of the block size
    # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes
    # 0x00 as needed. If the length of the message is already a multiple of 16
    # bytes, add a new block.
    pt_pad = bytes(pt) + b'\01' + b'\00' * (16 - (len(pt) + 1) % 16)
    # The IV is a random block used to differentiate messages encrypted with
    # the same key. An IV should never be used more than once in the lifetime
    # of the key. In this way encrypting the same plaintext twice will produce
    # different ciphertexts.
    iv = get_random_key()
    # Initialize the AES cipher with the given key and IV.
    aes = AES.new(key, AES.MODE_CBC, iv)
    ct = aes.encrypt(pt_pad)
    # Convert the ciphertext in a URL-safe base64 encoding
    ct_b64 = bin_to_b64(iv + ct)\
        .replace('+', '-').replace('/', '_').replace('=', '.')
    return ct_b64
示例#5
0
def decrypt_binary(ct_b64, key_hex):
    """Decrypt a ciphertext generated by encrypt_binary.

    ct_b64 (str): the ciphertext as produced by encrypt_binary.
    key_hex (str): the 16-bytes key in hex format used to encrypt.

    return (bytes): the plaintext.

    raise (ValueError): if the ciphertext is invalid.

    """
    key = hex_to_bin(key_hex)
    try:
        # Convert the ciphertext from a URL-safe base64 encoding to a
        # bytestring, which contains both the IV (the first 16 bytes) as well
        # as the encrypted padded plaintext.
        iv_ct = b64_to_bin(
            ct_b64.replace('-', '+').replace('_', '/').replace('.', '='))
        aes = AES.new(key, AES.MODE_CBC, iv_ct[:16])
        # Get the padded plaintext.
        pt_pad = aes.decrypt(iv_ct[16:])
        # Remove the padding.
        # TODO check that the padding is correct, i.e. that it contains at most
        # 15 bytes 0x00 preceded by a byte 0x01.
        pt = pt_pad.rstrip(b'\x00')[:-1]
        return pt
    except (TypeError, binascii.Error):
        raise ValueError('Could not decode from base64.')
    except ValueError:
        raise ValueError('Wrong AES cryptogram length.')
示例#6
0
def encrypt_binary(pt, key_hex):
    """Encrypt the plaintext with the 16-bytes key.

    A random salt is added to avoid having the same input being
    encrypted to the same output.

    pt (bytes): the "plaintext" to encode.
    key_hex (str): a 16-bytes key in hex (a string of 32 hex chars).

    return (str): pt encrypted using the key, in a format URL-safe
        (more precisely, base64-encoded with alphabet "a-zA-Z0-9.-_").

    """
    key = hex_to_bin(key_hex)
    # Pad the plaintext to make its length become a multiple of the block size
    # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes
    # 0x00 as needed. If the length of the message is already a multiple of 16
    # bytes, add a new block.
    pt_pad = pt + b'\01' + b'\00' * (16 - (len(pt) + 1) % 16)
    # The IV is a random block used to differentiate messages encrypted with
    # the same key. An IV should never be used more than once in the lifetime
    # of the key. In this way encrypting the same plaintext twice will produce
    # different ciphertexts.
    iv = get_random_key()
    # Initialize the AES cipher with the given key and IV.
    aes = AES.new(key, AES.MODE_CBC, iv)
    ct = aes.encrypt(pt_pad)
    # Convert the ciphertext in a URL-safe base64 encoding
    ct_b64 = bin_to_b64(iv + ct)\
        .replace('+', '-').replace('/', '_').replace('=', '.')
    return ct_b64
示例#7
0
文件: server.py 项目: lio-lv/cms
    def __init__(self, shard):
        parameters = {
            "static_files": [("cms.server", "static"),
                             ("cms.server.admin", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "auth_middleware":
            AWSAuthMiddleware,
            "rpc_enabled":
            True,
            "rpc_auth":
            self.is_rpc_authorized,
            "xsrf_cookies":
            True,
        }
        super().__init__(config.admin_listen_port,
                         HANDLERS,
                         parameters,
                         shard=shard,
                         listen_address=config.admin_listen_address)

        # For printing of detailed results
        try:
            locale.setlocale(locale.LC_COLLATE, 'lv_LV.UTF-8')
            locale.setlocale(locale.LC_NUMERIC, 'lv_LV.UTF-8')
        except locale.Error:
            logger.warning("Failed to set lv_LV.UTF-8 locale")

        self.jinja2_environment = AWS_ENVIRONMENT

        # A list of pending notifications.
        self.notifications = []

        self.admin_web_server = self.connect_to(
            ServiceCoord("AdminWebServer", 0))
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        self.resource_services = []
        for i in range(get_service_shards("ResourceService")):
            self.resource_services.append(
                self.connect_to(ServiceCoord("ResourceService", i)))
        self.logservice = self.connect_to(ServiceCoord("LogService", 0))
示例#8
0
    def __init__(self, shard):
        parameters = {
            "static_files": [("cms.server", "static"),
                             ("cms.server.admin", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "num_proxies_used":
            config.admin_num_proxies_used,
            "auth_middleware":
            AWSAuthMiddleware,
            "rpc_enabled":
            True,
            "rpc_auth":
            self.is_rpc_authorized,
            "xsrf_cookies":
            True,
            "xsrf_cookie_kwargs": {
                "samesite": "Strict"
            },
        }
        super().__init__(config.admin_listen_port,
                         HANDLERS,
                         parameters,
                         shard=shard,
                         listen_address=config.admin_listen_address)

        self.jinja2_environment = AWS_ENVIRONMENT

        # A list of pending notifications.
        self.notifications = []

        self.admin_web_server = self.connect_to(
            ServiceCoord("AdminWebServer", 0))
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        self.resource_services = []
        for i in range(get_service_shards("ResourceService")):
            self.resource_services.append(
                self.connect_to(ServiceCoord("ResourceService", i)))
        self.logservice = self.connect_to(ServiceCoord("LogService", 0))
示例#9
0
    def __init__(self, shard):
        parameters = {
            "ui_modules":
            views,
            "template_path":
            pkg_resources.resource_filename("cms.server.admin", "templates"),
            "static_files": [("cms.server", "static"),
                             ("cms.server.admin", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "auth_middleware":
            AWSAuthMiddleware,
            "rpc_enabled":
            True,
            "rpc_auth":
            self.is_rpc_authorized,
            "xsrf_cookies":
            True,
        }
        super(AdminWebServer,
              self).__init__(config.admin_listen_port,
                             HANDLERS,
                             parameters,
                             shard=shard,
                             listen_address=config.admin_listen_address)

        # A list of pending notifications.
        self.notifications = []

        self.file_cacher = FileCacher(self)
        self.admin_web_server = self.connect_to(
            ServiceCoord("AdminWebServer", 0))
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        self.resource_services = []
        for i in range(get_service_shards("ResourceService")):
            self.resource_services.append(
                self.connect_to(ServiceCoord("ResourceService", i)))
        self.logservice = self.connect_to(ServiceCoord("LogService", 0))
示例#10
0
    def __init__(self, shard):
        parameters = {
            "ui_modules": views,
            "template_path": pkg_resources.resource_filename(
                "cms.server.admin", "templates"),
            "static_files": [("cms.server", "static"),
                             ("cms.server.admin", "static")],
            "cookie_secret": hex_to_bin(config.secret_key),
            "debug": config.tornado_debug,
            "auth_middleware": AWSAuthMiddleware,
            "rpc_enabled": True,
            "rpc_auth": self.is_rpc_authorized,
            "xsrf_cookies": True,
        }
        super(AdminWebServer, self).__init__(
            config.admin_listen_port,
            HANDLERS,
            parameters,
            shard=shard,
            listen_address=config.admin_listen_address)

        # A list of pending notifications.
        self.notifications = []

        self.file_cacher = FileCacher(self)
        self.admin_web_server = self.connect_to(
            ServiceCoord("AdminWebServer", 0))
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(
            ServiceCoord("ProxyService", 0),
            must_be_present=ranking_enabled)

        self.resource_services = []
        for i in range(get_service_shards("ResourceService")):
            self.resource_services.append(self.connect_to(
                ServiceCoord("ResourceService", i)))
        self.logservice = self.connect_to(ServiceCoord("LogService", 0))
示例#11
0
文件: crypto.py 项目: romeorizzi/cms
def decrypt_string(ct_b64, key_hex):
    """Decrypt a ciphertext (ct_b64) encrypted with encrypt_string and
    return the corresponding plaintext.

    """
    key = hex_to_bin(key_hex)
    try:
        # Convert the ciphertext from a URL-safe base64 encoding to a
        # bytestring, which contains both the IV (the first 16 bytes) as well
        # as the encrypted padded plaintext.
        iv_ct = b64_to_bin(
            ct_b64.replace('-', '+').replace('_', '/').replace('.', '='))
        aes = AES.new(key, AES.MODE_CBC, iv_ct[:16])
        # Get the padded plaintext.
        pt_pad = aes.decrypt(iv_ct[16:])
        # Remove the padding.
        # TODO check that the padding is correct, i.e. that it contains at most
        # 15 bytes 0x00 preceded by a byte 0x01.
        pt = pt_pad.rstrip(b'\x00')[:-1]
        return pt
    except TypeError:
        raise ValueError('Could not decode from base64.')
    except ValueError:
        raise ValueError('Wrong AES cryptogram length.')
示例#12
0
 def test_invalid_alphabet(self):
     with self.assertRaises(self.error):
         hex_to_bin("cmscms")
示例#13
0
 def test_invalid_length(self):
     with self.assertRaises(self.error):
         hex_to_bin("000")
示例#14
0
 def test_success(self):
     self.assertEqual(hex_to_bin("3200a0"), b"\x32\x00\xa0")
     self.assertEqual(hex_to_bin("ffffffff"), b"\xFF\xFF\xFF\xFF")
     self.assertEqual(hex_to_bin("0" * 2000), b"\x00" * 1000)
示例#15
0
def _get_secret_key_unhex():
    # Only import this if we need it. Otherwise, we would prefer to
    # remain independent of the rest of CMS.
    from cms import config
    return hex_to_bin(config.secret_key)
示例#16
0
文件: server.py 项目: cms-dev/cms
    def __init__(self, shard, contest_id=None):
        parameters = {
            "static_files": [("cms.server", "static"),
                             ("cms.server.contest", "static")],
            "cookie_secret": hex_to_bin(config.secret_key),
            "debug": config.tornado_debug,
            "is_proxy_used": config.is_proxy_used,
            "num_proxies_used": config.num_proxies_used,
            "xsrf_cookies": True,
        }

        try:
            listen_address = config.contest_listen_address[shard]
            listen_port = config.contest_listen_port[shard]
        except IndexError:
            raise ConfigError("Wrong shard number for %s, or missing "
                              "address/port configuration. Please check "
                              "contest_listen_address and contest_listen_port "
                              "in cms.conf." % __name__)

        self.contest_id = contest_id

        if self.contest_id is None:
            HANDLERS.append((r"", MainHandler))
            handlers = [(r'/', ContestListHandler)]
            for h in HANDLERS:
                handlers.append((r'/([^/]+)' + h[0],) + h[1:])
        else:
            HANDLERS.append((r"/", MainHandler))
            handlers = HANDLERS

        super().__init__(
            listen_port,
            handlers,
            parameters,
            shard=shard,
            listen_address=listen_address)

        self.wsgi_app = SharedDataMiddleware(
            self.wsgi_app, {"/stl": config.stl_path},
            cache=True, cache_timeout=SECONDS_IN_A_YEAR,
            fallback_mimetype="application/octet-stream")

        self.jinja2_environment = CWS_ENVIRONMENT

        # This is a dictionary (indexed by username) of pending
        # notification. Things like "Yay, your submission went
        # through.", not things like "Your question has been replied",
        # that are handled by the db. Each username points to a list
        # of tuples (timestamp, subject, text).
        self.notifications = {}

        # Retrieve the available translations.
        self.translations = get_translations()

        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(
            ServiceCoord("ProxyService", 0),
            must_be_present=ranking_enabled)

        printing_enabled = config.printer is not None
        self.printing_service = self.connect_to(
            ServiceCoord("PrintingService", 0),
            must_be_present=printing_enabled)
示例#17
0
    def __init__(self, shard, contest_id=None):
        parameters = {
            "static_files": [("cms.server", "static"),
                             ("cms.server.contest", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "is_proxy_used":
            config.is_proxy_used,
            "num_proxies_used":
            config.num_proxies_used,
            "xsrf_cookies":
            True,
        }

        try:
            listen_address = config.contest_listen_address[shard]
            listen_port = config.contest_listen_port[shard]
        except IndexError:
            raise ConfigError("Wrong shard number for %s, or missing "
                              "address/port configuration. Please check "
                              "contest_listen_address and contest_listen_port "
                              "in cms.conf." % __name__)

        self.contest_id = contest_id

        if self.contest_id is None:
            HANDLERS.append((r"", MainHandler))
            handlers = [(r'/', ContestListHandler)]
            for h in HANDLERS:
                handlers.append((r'/([^/]+)' + h[0], ) + h[1:])
        else:
            HANDLERS.append((r"/", MainHandler))
            handlers = HANDLERS

        super().__init__(listen_port,
                         handlers,
                         parameters,
                         shard=shard,
                         listen_address=listen_address)

        self.wsgi_app = SharedDataMiddleware(
            self.wsgi_app, {"/stl": config.stl_path},
            cache=True,
            cache_timeout=SECONDS_IN_A_YEAR,
            fallback_mimetype="application/octet-stream")

        self.jinja2_environment = CWS_ENVIRONMENT

        # This is a dictionary (indexed by username) of pending
        # notification. Things like "Yay, your submission went
        # through.", not things like "Your question has been replied",
        # that are handled by the db. Each username points to a list
        # of tuples (timestamp, subject, text).
        self.notifications = {}

        # Retrieve the available translations.
        self.translations = get_translations()

        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        printing_enabled = config.printer is not None
        self.printing_service = self.connect_to(
            ServiceCoord("PrintingService", 0),
            must_be_present=printing_enabled)
示例#18
0
    def __init__(self, shard, contest_id=None):
        parameters = {
            "template_path":
            pkg_resources.resource_filename("cms.server.contest", "templates"),
            "static_files": [("cms.server", "static"),
                             ("cms.server.contest", "static")],
            "cookie_secret":
            hex_to_bin(config.secret_key),
            "debug":
            config.tornado_debug,
            "is_proxy_used":
            config.is_proxy_used,
            "num_proxies_used":
            config.num_proxies_used,
            "xsrf_cookies":
            True,
        }

        try:
            listen_address = config.contest_listen_address[shard]
            listen_port = config.contest_listen_port[shard]
        except IndexError:
            raise ConfigError("Wrong shard number for %s, or missing "
                              "address/port configuration. Please check "
                              "contest_listen_address and contest_listen_port "
                              "in cms.conf." % __name__)

        self.contest_id = contest_id

        if self.contest_id is None:
            HANDLERS.append((r"", MainHandler))
            handlers = [(r'/', BaseHandler)]
            for h in HANDLERS:
                handlers.append((r'/([^/]+)' + h[0], ) + h[1:])
        else:
            HANDLERS.append((r"/", MainHandler))
            handlers = HANDLERS

        super(ContestWebServer, self).__init__(listen_port,
                                               handlers,
                                               parameters,
                                               shard=shard,
                                               listen_address=listen_address)

        # This is a dictionary (indexed by username) of pending
        # notification. Things like "Yay, your submission went
        # through.", not things like "Your question has been replied",
        # that are handled by the db. Each username points to a list
        # of tuples (timestamp, subject, text).
        self.notifications = {}

        # Retrieve the available translations.
        self.langs = {
            lang_code: wrap_translations_for_tornado(trans)
            for lang_code, trans in iteritems(get_translations())
        }

        self.file_cacher = FileCacher(self)
        self.evaluation_service = self.connect_to(
            ServiceCoord("EvaluationService", 0))
        self.scoring_service = self.connect_to(
            ServiceCoord("ScoringService", 0))

        ranking_enabled = len(config.rankings) > 0
        self.proxy_service = self.connect_to(ServiceCoord("ProxyService", 0),
                                             must_be_present=ranking_enabled)

        printing_enabled = config.printer is not None
        self.printing_service = self.connect_to(
            ServiceCoord("PrintingService", 0),
            must_be_present=printing_enabled)