예제 #1
0
    def decrypt_frame_header(self, data):
        """
        Decrypts frame header
        :param data: frame header data
        :return: decrypted header data
        """

        if len(data) != eth_common_constants.FRAME_HDR_TOTAL_LEN:
            raise ValueError(
                "Frame header len is expected to be {0} but was {1}".format(
                    eth_common_constants.FRAME_HDR_TOTAL_LEN, len(data)))

        if not self._is_ready:
            raise CipherNotInitializedError(
                "failed to decrypt frame header, the cipher was never initialized!"
            )

        header_cipher_text = data[:eth_common_constants.FRAME_HDR_DATA_LEN]
        header_mac = data[eth_common_constants.FRAME_HDR_DATA_LEN:
                          eth_common_constants.FRAME_HDR_TOTAL_LEN]

        # ingress-mac.update(aes(mac-secret,ingress-mac) ^ header-ciphertext).digest
        mac1 = self.mac_ingress()[:eth_common_constants.FRAME_MAC_LEN]
        mac_enc = self._mac_enc(mac1)
        mac_sxor = crypto_utils.string_xor(mac_enc, header_cipher_text)

        expected_header_mac = self.mac_ingress(
            mac_sxor)[:eth_common_constants.FRAME_MAC_LEN]

        # expected_header_mac = self.updateMAC(self.ingress_mac, header_ciphertext)
        if not expected_header_mac == header_mac:
            raise AuthenticationError("Invalid header mac")

        return self.aes_decode(header_cipher_text)
예제 #2
0
    def test_string_xor(self):
        str1 = helpers.generate_bytearray(111)
        str2 = helpers.generate_bytearray(111)

        sxor = crypto_utils.string_xor(str1, str2)

        self.assertIsNotNone(sxor)

        self.assertEqual(len(sxor), len(str1))
예제 #3
0
    def encrypt_frame(self, frame):
        """
        Encrypts frame
        :param frame: frame data
        :return: encrypted frame
        """

        if not isinstance(frame, Frame):
            raise TypeError("frame must be of type Frame but was {0}".format(
                type(frame)))

        if not self._is_ready:
            raise CipherNotInitializedError(
                f"failed to encrypt frame {frame}, the cipher was never initialized!"
            )

        header = frame.get_header()
        body = frame.get_body()

        # header
        header_ciphertext = self.aes_encode(header)

        # egress-mac.update(aes(mac-secret,egress-mac) ^ header-ciphertext).digest
        header_mac = self.mac_egress(
            crypto_utils.string_xor(
                self._mac_enc(
                    self.mac_egress()[:eth_common_constants.FRAME_MAC_LEN]),
                header_ciphertext))[:eth_common_constants.FRAME_MAC_LEN]

        # frame
        frame_ciphertext = self.aes_encode(body)
        assert len(frame_ciphertext) == len(body)
        # egress-mac.update(aes(mac-secret,egress-mac) ^
        # left128(egress-mac.update(frame-ciphertext).digest))
        fmac_seed = self.mac_egress(frame_ciphertext)
        frame_mac = self.mac_egress(
            crypto_utils.string_xor(
                self._mac_enc(
                    self.mac_egress()[:eth_common_constants.FRAME_MAC_LEN]),
                fmac_seed[:eth_common_constants.FRAME_MAC_LEN])
        )[:eth_common_constants.FRAME_MAC_LEN]

        return bytearray(header_ciphertext + header_mac + frame_ciphertext +
                         frame_mac)
예제 #4
0
    def create_auth_message(self):
        """
        Generates authentication message bytes

        1. initiator generates ecdhe-random and nonce and creates auth
        2. initiator connects to remote and sends auth

        New:
        E(remote-pubk,
            S(ephemeral-privk, ecdh-shared-secret ^ nonce) ||
            H(ephemeral-pubk) || pubk || nonce || 0x0
        )
        Known:
        E(remote-pubk,
            S(ephemeral-privk, token ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x1)

        :param remote_pubkey: public key of remote node
        :param nonce: nonce
        :return: authentication message bytes
        """

        assert self._is_initiator

        ecdh_shared_secret = self._ecc.get_ecdh_key(self._remote_pubkey)
        token = ecdh_shared_secret
        flag = 0x0
        self._initiator_nonce = eth_common_utils.keccak_hash(
            rlp_utils.int_to_big_endian(random.randint(0, sys.maxsize)))
        assert len(
            self._initiator_nonce) == eth_common_constants.SHA3_LEN_BYTES

        token_xor_nonce = crypto_utils.string_xor(token, self._initiator_nonce)
        assert len(token_xor_nonce) == eth_common_constants.SHA3_LEN_BYTES

        ephemeral_pubkey = self._ephemeral_ecc.get_raw_public_key()
        assert len(ephemeral_pubkey) == eth_common_constants.PUBLIC_KEY_LEN
        if not self._ecc.is_valid_key(ephemeral_pubkey):
            raise InvalidKeyError("Invalid ephemeral pubkey")

        # S(ephemeral-privk, ecdh-shared-secret ^ nonce)
        S = self._ephemeral_ecc.sign(token_xor_nonce)
        assert len(S) == eth_common_constants.SIGNATURE_LEN

        # S || H(ephemeral-pubk) || pubk || nonce || 0x0
        auth_message = S + eth_common_utils.keccak_hash(ephemeral_pubkey) + self._ecc.get_raw_public_key() + \
                       self._initiator_nonce + rlp_utils.ascii_chr(flag)
        return auth_message
예제 #5
0
    def parse_auth_message(self, message):
        assert not self._is_initiator

        if self._is_eip8_auth:
            (signature, pubkey, nonce,
             version) = self.parse_eip8_auth_message(message)
        else:
            (signature, pubkey, nonce,
             version) = self.parse_plain_auth_message(message)

        token = self._ecc.get_ecdh_key(pubkey)
        remote_ephemeral_pubkey = crypto_utils.recover_public_key(
            crypto_utils.string_xor(token, nonce), signature)
        if not self._ecc.is_valid_key(remote_ephemeral_pubkey):
            raise InvalidKeyError("Invalid remote ephemeral pubkey")

        self._remote_ephemeral_pubkey = remote_ephemeral_pubkey
        self._initiator_nonce = nonce
        self._remote_pubkey = pubkey
        self._remote_version = eth_common_constants.P2P_PROTOCOL_VERSION
예제 #6
0
    def decrypt_frame_body(self, data, body_size):
        """
        Decrypts frame body
        :param data: frame data
        :param body_size: body size
        :return: decrypted frame body
        """

        if not self._is_ready:
            raise CipherNotInitializedError(
                "failed to decrypt frame body, the cipher was never initialized!"
            )

        # frame-size: 3-byte integer size of frame, big endian encoded (excludes padding)
        # frame relates to body w/o padding w/o mac

        read_size = crypto_utils.get_padded_len_16(body_size)

        if not len(data) >= read_size + eth_common_constants.FRAME_MAC_LEN:
            raise ParseError("Insufficient body length")

        frame_cipher_text = data[:read_size]
        frame_mac = data[read_size:read_size +
                         eth_common_constants.FRAME_MAC_LEN]

        # ingres-mac.update(aes(mac-secret,ingres-mac) ^
        # left128(ingres-mac.update(frame-ciphertext).digest))
        frame_mac_seed = self.mac_ingress(frame_cipher_text)
        expected_frame_mac = self.mac_ingress(
            crypto_utils.string_xor(
                self._mac_enc(
                    self.mac_ingress()[:eth_common_constants.FRAME_MAC_LEN]),
                frame_mac_seed[:eth_common_constants.FRAME_MAC_LEN])
        )[:eth_common_constants.FRAME_MAC_LEN]

        if not frame_mac == expected_frame_mac:
            raise AuthenticationError("Invalid frame mac")

        return self.aes_decode(frame_cipher_text)[:body_size]
예제 #7
0
    def setup_cipher(self):
        """
        Sets up cipher parameters.
        Needs to be called after initial authentication handshake
        """

        assert self._responder_nonce
        assert self._initiator_nonce
        assert self._auth_init
        assert self._auth_ack
        assert self._remote_ephemeral_pubkey
        if not self._ecc.is_valid_key(self._remote_ephemeral_pubkey):
            raise InvalidKeyError("Invalid remote ephemeral pubkey")

        # derive base secrets from ephemeral key agreement
        # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
        ecdhe_shared_secret = self._ephemeral_ecc.get_ecdh_key(
            self._remote_ephemeral_pubkey)

        # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce))
        shared_secret = eth_common_utils.keccak_hash(
            ecdhe_shared_secret +
            eth_common_utils.keccak_hash(self._responder_nonce +
                                         self._initiator_nonce))

        self.ecdhe_shared_secret = ecdhe_shared_secret  # used in tests
        self.shared_secret = shared_secret  # used in tests

        # token = sha3(shared-secret)
        self._token = eth_common_utils.keccak_hash(shared_secret)

        # aes-secret = sha3(ecdhe-shared-secret || shared-secret)
        self._aes_secret = eth_common_utils.keccak_hash(ecdhe_shared_secret +
                                                        shared_secret)

        # mac-secret = sha3(ecdhe-shared-secret || aes-secret)
        self.mac_secret = eth_common_utils.keccak_hash(ecdhe_shared_secret +
                                                       self._aes_secret)

        # setup sha3 instances for the MACs
        # egress-mac = sha3.update(mac-secret ^ recipient-nonce || auth-sent-init)
        mac1 = crypto_utils.get_sha3_calculator(
            crypto_utils.string_xor(self.mac_secret, self._responder_nonce) +
            self._auth_init)
        # ingress-mac = sha3.update(mac-secret ^ initiator-nonce || auth-recvd-ack)
        mac2 = crypto_utils.get_sha3_calculator(
            crypto_utils.string_xor(self.mac_secret, self._initiator_nonce) +
            self._auth_ack)

        if self._is_initiator:
            self._egress_mac, self._ingress_mac = mac1, mac2
        else:
            self._egress_mac, self._ingress_mac = mac2, mac1

        iv = "\x00" * eth_common_constants.IV_LEN
        self._aes_enc = pyelliptic.Cipher(
            self._aes_secret,
            iv,
            eth_common_constants.CIPHER_ENCRYPT_DO,
            ciphername=eth_common_constants.RLPX_CIPHER_NAME)
        self._aes_dec = pyelliptic.Cipher(
            self._aes_secret,
            iv,
            eth_common_constants.CIPHER_DECRYPT_DO,
            ciphername=eth_common_constants.RLPX_CIPHER_NAME)
        self._mac_enc = AES.new(self.mac_secret, AES.MODE_ECB).encrypt

        self._is_ready = True