diff --git a/cli/cenclave/pyproject.toml b/cli/cenclave/pyproject.toml index 7bdbd74..9ddbc08 100644 --- a/cli/cenclave/pyproject.toml +++ b/cli/cenclave/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ dependencies = [ "cryptography>=43.0.3,<44.0.0", "docker>=7.1.0,<8.0.0", - "intel-sgx-ra>=2.3.1,<3.0.0", + "intel-sgx-ra>=2.3.2,<3.0.0", "jinja2>=3.1.4,<3.2.0", "cenclave-lib-crypto>=1.0.0,<2.0.0", "pydantic>=1.10.18,<2.0.0", diff --git a/examples/yaos_millionaires/README.md b/examples/yaos_millionaires/README.md index 6ba0277..d15e774 100644 --- a/examples/yaos_millionaires/README.md +++ b/examples/yaos_millionaires/README.md @@ -1,10 +1,35 @@ # Yao's Millionaires application example -Yao's Millionaires problem solved using Cosmian Enclave. -Each participant reprensented by its public key can send the value of its wealth to known who is the richest. -The result is encrypted for each public key. +[Yao's Millionaires' problem](https://en.wikipedia.org/wiki/Yao%27s_Millionaires%27_problem) solved using Cosmian Enclave. +Each participant reprensented by its public key can send the value of its wealth to know who is the richest. +The result is encrypted using participant's public key. -## Test your app before creating the enclave +## Prequisites + +### Setup + +First, each participant generates a keypair to identify with their own public/private key: + +```console +$ cenclave keygen --asymmetric --output /tmp/keypair1.bin +Public key: c12ace0ca954b883fa918561dd647cf3de79861ea5996b6d2e4e4e8abc664a26 +Public key (Base64): wSrODKlUuIP6kYVh3WR88955hh6lmWttLk5OirxmSiY= +Keypair wrote to /tmp/keypair1.bin +$ cenclave keygen --asymmetric --output /tmp/keypair2.bin +Public key: 4afc6cfc76abc1715bf7f89f451d3bdfcb76883db7d420ecf6295b9975c35a20 +Public key (Base64): Svxs/HarwXFb9/ifRR0738t2iD231CDs9ilbmXXDWiA= +Keypair wrote to /tmp/keypair2.bin +``` + +then populate `src/config.json` with participant's public key base64-encoded: + +```console +{ + "participants": ["4Eb1ZohjPg0n/9ExJydaQnjxGmOIq60EPlC73v7VowQ=", "i1p7OGmfhBTfrGzLej6g+q2+ZvdJgPjMXGtSuN0q7hI="] +} +``` + +### Test the code locally without SGX (docker required) ```console $ cenclave localtest --code src/ \ @@ -13,14 +38,82 @@ $ cenclave localtest --code src/ \ --test tests/ ``` -## Create Cosmian Enclave package with the code and the container image +### Create a package for Cosmian Enclave ```console $ cenclave package --code src/ \ --dockerfile Dockerfile \ --config config.toml \ --test tests/ \ - --output code_provider + --output tarball/ ``` -The generated package can now be sent to the SGX operator. +The generated tarball file in `tarball/` folder can now be used on the SGX machine properly configured with Cosmian Enclave. + +Optionally use `--encrypt` if you want the code to be encrypted. + +## Running the code with Cosmian Enclave + +### Spawn the configuration server + +```console +$ cenclave spawn --host --port --size --package --output sgx_operator/ --san yaos_millionaires +``` + +- `host`: usually 127.0.0.1 for localhost or 0.0.0.0 to expose externally +- `port`: network port used by your application, usually 9999 +- `size`: memory size (in MB) of the enclave (must be a power of 2 greater than 2048) +- `package`: tarball file with the code and container image +- `san`: [Subject Alternative Name](https://en.wikipedia.org/wiki/Public_key_certificate#Subject_Alternative_Name_certificate) used for routing with SSL pass-through (either domain name, external IP address or localhost) + +### Seal code secret key (optionally) + +If you choose to encrypt the code with `--encrypt` then you need to verify your enclave first. +Ask the SGX operator to communicate the `evidence.json` file to do the remote attestation and verify that your code is running in the enclave: + +```console +$ cenclave verify --evidence evidence.json --package tarball/package_<><>.tar --output ratls.pem +``` + +if successful, then include the randomly generated secret key in `secrets_to_seal.json`: + +```text +{ + "code_secret_key": "HEX_CODE_SECRET_KEY" +} +``` + +and finally seal `secrets_to_seal.json` file: + +```console +$ cenclave seal --input secrets_to_seal.json --receiver-enclave ratls.pem --output code_provider/sealed_secrets.json.enc +``` + +### Run your application + +```console +$ cenclave run yaos_millionaires +``` + +Note: if the code is encrypted, sealed secrets must be added to the command with `--sealed-secrets sealed_secrets.json.enc`. + +### Use the client + +In the `client/` directory you can use the Python client to query your enclave: + +```console +$ # Verify the remote enclave and the MRENCLAVE hash digest +$ python main.py --verify https://: +$ +$ # list participants +$ python main.py --keypair /tmp/keypair1.bin --list https://: +$ +$ # push your fortune +$ python main.py --keypair /tmp/keypair1.bin --push 1_000_000 https://: +$ python main.py --keypair /tmp/keypair1.bin --push 2_000_000 https://: +$ +$ # ask who is the richest with keypair1 (result encrypted for keypair1) +$ python main.py --keypair /tmp/keypair1.bin --result https://: +$ # ask who is the richest with keypair2 (result encrypted for keypair2) +$ python main.py --keypair /tmp/keypair2.bin --result https://: +``` diff --git a/examples/yaos_millionaires/client/main.py b/examples/yaos_millionaires/client/main.py index 0a26d87..5bc1bae 100644 --- a/examples/yaos_millionaires/client/main.py +++ b/examples/yaos_millionaires/client/main.py @@ -1,43 +1,44 @@ -"""Simple Python client for Yao's Millionaires. +"""Simple Python client for Yao's Millionaires with Cosmian Enclave. ```console -$ pip install requests intel-sgx-ra -$ python main.py http://127.0.0.1:5000 # for local testing +$ pip install requests intel-sgx-ra cenclave-lib-crypto +$ python main.py --help +usage: Client for Yao's Millionaires in Cosmian Enclave + +positional arguments: + url URL of the remote enclave + +options: + -h, --help show this help message and exit + --reset Remove participant's data from the computation + --verify Verify the enclave by doing the remote attestation + --list List participant's public key + --push NUMBER Push your wealth as number for the computation + --result Get result of the computation + --keypair PATH Path of the public/private keypair + --debug Debug information to stdout ``` """ +import argparse import base64 -import sys +import logging +import os +import struct import tempfile from pathlib import Path -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Optional, Union import requests -from cenclave_lib_crypto.seal_box import unseal -from intel_sgx_ra.maa.attest import verify_quote +from cenclave_lib_crypto.seal_box import seal, unseal +from intel_sgx_ra.attest import verify_quote +from intel_sgx_ra.log import LOGGER as INTEL_SGX_RA_LOGGER +from intel_sgx_ra.maa.attest import verify_quote as azure_verify_quote from intel_sgx_ra.quote import Quote from intel_sgx_ra.ratls import get_server_certificate, ratls_verify_from_url, url_parse -# Key generated with cenclave_lib_crypto.x2559.x25519_keygen() -PK1: bytes = bytes.fromhex( - "938b3e0e60ebbdbbd4348ba6a0468685043d540e332c32e8bc2ca40b858ad209" -) -PK1_B64: str = "k4s+DmDrvbvUNIumoEaGhQQ9VA4zLDLovCykC4WK0gk=" -SK1: bytes = bytes.fromhex( - "7957ceed56d44a384cf523619a00b2c129514daf422c0b799105fb2caa23ef97" -) - -PK2: bytes = bytes.fromhex( - "ff8b983287fec6aefcf1b55e8c1efeff984e5b8dfa8d4de62df521bd6ec57d14" -) -PK2_B64: str = "/4uYMof+xq788bVejB7+/5hOW436jU3mLfUhvW7FfRQ=" -SK2: bytes = bytes.fromhex( - "05e5aa1c56ec3d6bf707893e6a038a825d80a2802fdb565fd8fecb840735a954" -) - - def reset(session: requests.Session, url: str) -> None: response: requests.Response = session.delete(url) @@ -45,10 +46,30 @@ def reset(session: requests.Session, url: str) -> None: raise Exception(f"Bad response: {response.status_code}") -def push(session: requests.Session, url: str, pk: bytes, n: Union[int, float]) -> None: +def push( + session: requests.Session, + url: str, + pk: bytes, + n: Union[int, float], + enclave_pk: Optional[bytes] = None, +) -> None: + encoded_n: bytes = struct.pack(" str: content: Dict[str, Any] = response.json() encrypted_content: bytes = base64.b64decode(content["max"]) + logging.info("Encrypted response: %s", encrypted_content.hex()) - print(f"Encrypted content for {pk_b64}: {encrypted_content.hex()}") - + logging.info("Trying to decrypt response for pk %s...", pk_b64) pk_winner: bytes = unseal(encrypted_content, sk) + logging.info("Response successfully decrypted") return base64.b64encode(pk_winner).decode("utf-8") -def main() -> int: - url: str = sys.argv[1] +def verify(url: str, pccs_url: Optional[str] = None) -> tuple[Path, bytes, bytes]: hostname, port = url_parse(url) - session: requests.Session = requests.Session() - quote: Quote = ratls_verify_from_url(url) + + if pccs_url: + _ = verify_quote(quote, None, pccs_url) + else: # try Azure Cloud + maa_result: Dict[str, Any] = azure_verify_quote(quote) + logging.debug("Microsoft Azure Attestation response: %s", maa_result) + ratls_cert_path: Path = Path(tempfile.gettempdir()) / "ratls.pem" ratls_cert = get_server_certificate((hostname, port)) ratls_cert_path.write_bytes(ratls_cert.encode("utf-8")) + logging.info("RA-TLS certificate written to %s", ratls_cert_path) - _: Dict[str, Any] = verify_quote(quote) + mr_enclave: bytes = quote.report_body.mr_enclave + enclave_pk: bytes = quote.report_body.report_data[32:] - session.verify = f"{ratls_cert_path}" + logging.info("MRENCLAVE: %s", mr_enclave.hex()) + logging.info("Enclave's public key: %s", enclave_pk.hex()) + + return ratls_cert_path, mr_enclave, enclave_pk + + +def cli_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + usage="Client for Yao's Millionaires in Cosmian Enclave" + ) + + group_cmd = parser.add_mutually_exclusive_group(required=True) + group_cmd.add_argument( + "--reset", + action="store_true", + help="Remove participant's data from the computation", + ) + group_cmd.add_argument( + "--verify", + action="store_true", + help="Verify the enclave by doing the remote attestation", + ) + group_cmd.add_argument( + "--list", action="store_true", help="List participant's public key" + ) + group_cmd.add_argument( + "--push", + type=float, + metavar="NUMBER", + help="Push your wealth as number for the computation", + ) + group_cmd.add_argument( + "--result", action="store_true", help="Get result of the computation" + ) + parser.add_argument( + "--keypair", + type=Path, + metavar="PATH", + required=True, + help="Path to read the public/private keypair", + ) + parser.add_argument( + "--mrenclave", + type=str, + metavar="HEXDIGEST", + help="Expected MRENCLAVE hash digest of the remote enclave", + ) + parser.add_argument( + "--debug", action="store_true", help="Debug information to stdout" + ) + parser.add_argument("url", help="URL of the remote enclave") - p_1 = (PK1, 100_398) - p_2 = (PK2, 100_399) + return parser.parse_args() - reset(session, url) - for pk, n in (p_1, p_2): - push(session, url, pk, n) +def main() -> int: + args: argparse.Namespace = cli_args() + + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + INTEL_SGX_RA_LOGGER.setLevel(logging.ERROR) + + url: str = args.url + + pk: bytes + sk: bytes - print(participants(session, url)) + keypair: bytes = Path(args.keypair).read_bytes() + pk, sk = keypair[:32], keypair[32:] - p_1_result = richest(session, url, PK1, SK1) - p_2_result = richest(session, url, PK2, SK2) + session: requests.Session = requests.Session() + + pccs_url: Optional[str] = os.environ.get("PCCS_URL") + ratls_cert_path, mr_enclave, enclave_pk = verify(url, pccs_url) - assert p_1_result == p_2_result + if expected_mrenclave := args.mrenclave: + assert ( + bytes.fromhex(expected_mrenclave) == mr_enclave + ), f"Unexpected MRENCLAVE {mr_enclave.hex()}" + + session.verify = f"{ratls_cert_path}" - print(f"The richest participant is {p_1_result}") + if args.reset: + reset(session, url) + logging.info("Reset success") + elif args.verify: + logging.info( + "RA-TLS certificate:\n%s", + ratls_cert_path.read_text(), + ) + elif args.list: + logging.info(participants(session, url)) + elif number := args.push: + push(session, url, pk, float(number), enclave_pk) + logging.info( + "Pushed %s with public key %s", + float(number), + base64.b64encode(pk).decode("utf-8"), + ) + elif args.result: + pk_winner: str = richest(session, url, pk, sk) + logging.info("The richest participant is %s", pk_winner) + + if pk_winner == base64.b64encode(pk).decode("utf-8"): + logging.info("Congrats, you are the richest participant!") + else: + logging.info("Sorry, keep growing your fortune!") + else: + raise Exception("Unexpected command") return 0 diff --git a/examples/yaos_millionaires/secrets.json b/examples/yaos_millionaires/secrets.json deleted file mode 100644 index 8a44b0e..0000000 --- a/examples/yaos_millionaires/secrets.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "participants": [ - "k4s+DmDrvbvUNIumoEaGhQQ9VA4zLDLovCykC4WK0gk=", - "/4uYMof+xq788bVejB7+/5hOW436jU3mLfUhvW7FfRQ=" - ] -} diff --git a/examples/yaos_millionaires/secrets_to_seal.json b/examples/yaos_millionaires/secrets_to_seal.json index 9e26dfe..742a9d3 100644 --- a/examples/yaos_millionaires/secrets_to_seal.json +++ b/examples/yaos_millionaires/secrets_to_seal.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "code_secret_key": "HEX_CODE_SECRET_KEY" +} \ No newline at end of file diff --git a/examples/yaos_millionaires/src/app.py b/examples/yaos_millionaires/src/app.py index 3091225..717b144 100644 --- a/examples/yaos_millionaires/src/app.py +++ b/examples/yaos_millionaires/src/app.py @@ -2,104 +2,110 @@ import base64 import json -import logging import os +import struct from http import HTTPStatus from pathlib import Path from typing import Any, Optional +from cenclave_lib_crypto.seal_box import seal, unseal +from flask import Flask, Response, jsonify, request + import globs -from cenclave_lib_crypto.seal_box import seal -from flask import Flask, Response, request -from flask.logging import create_logger app = Flask(__name__) -LOG = create_logger(app) - -logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.DEBUG) +CONFIG = json.loads((Path(__file__).parent / "config.json").read_text(encoding="utf-8")) -SECRETS = json.loads(Path(os.getenv("SECRETS_PATH")).read_text(encoding="utf-8")) +ENCLAVE_SK: Optional[bytes] = ( + Path(os.environ["ENCLAVE_SK_PATH"]).read_bytes() + if "ENCLAVE_SK_PATH" in os.environ + else None +) @app.get("/health") -def health_check(): +def health_check() -> Response: """Health check of the application.""" return Response(response="OK", status=HTTPStatus.OK) @app.post("/") -def push(): +def push() -> Response: """Add a number to the pool.""" - data: Optional[Any] = request.get_json(silent=True) + content: Optional[Any] = request.get_json(silent=True) - if data is None or not isinstance(data, dict): - LOG.error("TypeError with data: '%s'", data) + if content is None or not isinstance(content, dict): + app.logger.error("TypeError with data: '%s'", content) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) - n: Optional[float] = data.get("n") - pk: Optional[str] = data.get("pk") + data: Optional[Any] = content.get("data") + pk: Optional[str] = content.get("pk") - if n is None or not isinstance(n, (float, int)): - LOG.error("TypeError with data content: '%s' (%s)", n, type(n)) + if data is None or not isinstance(data, dict): + app.logger.error("TypeError with data content: '%s' (%s)", data, type(data)) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) if pk is None or not isinstance(pk, str): - LOG.error("TypeError with data content: '%s' (%s)", pk, type(pk)) + app.logger.error("TypeError with data content: '%s' (%s)", pk, type(pk)) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) - if pk not in SECRETS["participants"]: - LOG.error( + if pk not in CONFIG["participants"]: + app.logger.error( "The public key provided is not in the participants: '%s' (%s)", pk, - SECRETS["participants"], + CONFIG["participants"], ) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) - globs.POOL.append((pk, n)) + if pk in dict(globs.POOL): + app.logger.error("Public key already pushed data") + return Response(status=HTTPStatus.CONFLICT) + + n: bytes = base64.b64decode(data["n"]) - LOG.info("Successfully added (%s, %s)", n, pk) + if data["encrypted"] and ENCLAVE_SK: + n = unseal(n, ENCLAVE_SK) + + deser_n, *_ = struct.unpack(" Response: """Get all the public keys of participants""" - if "participants" not in SECRETS: - LOG.error("no participants found") - return {"participants": None} - - return {"participants": SECRETS["participants"]} + return jsonify(CONFIG) @app.post("/richest") def richest(): """Get the current max in pool.""" if len(globs.POOL) < 1: - LOG.error("need more than 1 value to compute the max") + app.logger.error("need more than 1 value to compute the max") return {"max": None} - if "participants" not in SECRETS: - LOG.error("no participants found") - return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) - data: Optional[Any] = request.get_json(silent=True) if data is None or not isinstance(data, dict): - LOG.error("TypeError with data: '%s'", data) + app.logger.error("TypeError with data: '%s'", data) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) recipient_pk: Optional[str] = data.get("recipient_pk") if recipient_pk is None or not isinstance(recipient_pk, str): - LOG.error("TypeError with data content: '%s' (%s)", pk, type(pk)) + app.logger.error( + "TypeError with data content: '%s' (%s)", recipient_pk, type(recipient_pk) + ) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) - if recipient_pk not in SECRETS["participants"]: - LOG.error( + if recipient_pk not in CONFIG["participants"]: + app.logger.error( "The public key provided is not in the participants: '%s' (%s)", recipient_pk, - SECRETS["participants"], + CONFIG["participants"], ) return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY) @@ -107,11 +113,11 @@ def richest(): (pk, _) = max(globs.POOL, key=lambda t: t[1]) - encrypted_b64_result: bytes = base64.b64encode( + encrypted_b64_result: str = base64.b64encode( seal(base64.b64decode(pk), raw_recipient_pk) ).decode("utf-8") - return {"max": encrypted_b64_result} + return jsonify({"max": encrypted_b64_result}) @app.delete("/") @@ -119,6 +125,6 @@ def reset(): """Reset the current pool.""" globs.POOL = [] - LOG.info("Reset successfully") + app.logger.info("Reset successfully") return Response(status=HTTPStatus.OK) diff --git a/examples/yaos_millionaires/src/config.json b/examples/yaos_millionaires/src/config.json new file mode 100644 index 0000000..bed87d5 --- /dev/null +++ b/examples/yaos_millionaires/src/config.json @@ -0,0 +1,6 @@ +{ + "participants": [ + "wSrODKlUuIP6kYVh3WR88955hh6lmWttLk5OirxmSiY=", + "Svxs/HarwXFb9/ifRR0738t2iD231CDs9ilbmXXDWiA=" + ] +} \ No newline at end of file diff --git a/examples/yaos_millionaires/src/globs.py b/examples/yaos_millionaires/src/globs.py index a49502f..348b080 100644 --- a/examples/yaos_millionaires/src/globs.py +++ b/examples/yaos_millionaires/src/globs.py @@ -1,3 +1,3 @@ """globs module.""" -POOL: list = [] +POOL: list[tuple[str, float]] = [] diff --git a/examples/yaos_millionaires/tests/conftest.py b/examples/yaos_millionaires/tests/conftest.py index 2a4a6e7..01aead6 100644 --- a/examples/yaos_millionaires/tests/conftest.py +++ b/examples/yaos_millionaires/tests/conftest.py @@ -1,8 +1,9 @@ """Conftest.""" +import base64 import os from pathlib import Path -from typing import Optional, Union +from typing import Optional import pytest from intel_sgx_ra.ratls import get_server_certificate, url_parse @@ -57,44 +58,48 @@ def certificate(url, workspace) -> Optional[Path]: @pytest.fixture(scope="module") -def pk1() -> bytes: +def keypair1_path() -> bytes: + """Path of the participant 1 keypair.""" + return Path(__file__).parent / "data" / "keypair1.bin" + + +@pytest.fixture(scope="module") +def keypair2_path() -> bytes: + """Path of the participant 2 keypair.""" + return Path(__file__).parent / "data" / "keypair2.bin" + + +@pytest.fixture(scope="module") +def pk1(keypair1_path) -> bytes: """Bytes of the public key of the participant 1.""" - return bytes.fromhex( - "938b3e0e60ebbdbbd4348ba6a0468685043d540e332c32e8bc2ca40b858ad209" - ) + return keypair1_path.read_bytes()[:32] @pytest.fixture(scope="module") -def pk1_b64() -> str: +def pk1_b64(pk1) -> bytes: """Base64 encoded public key of the participant 1.""" - return "k4s+DmDrvbvUNIumoEaGhQQ9VA4zLDLovCykC4WK0gk=" + return base64.b64encode(pk1) @pytest.fixture(scope="module") -def pk2() -> bytes: +def pk2(keypair2_path) -> bytes: """Bytes of the public key of the participant 2.""" - return bytes.fromhex( - "ff8b983287fec6aefcf1b55e8c1efeff984e5b8dfa8d4de62df521bd6ec57d14" - ) + return keypair2_path.read_bytes()[:32] @pytest.fixture(scope="module") -def pk2_b64() -> str: +def pk2_b64(pk2) -> bytes: """Base64 encoded public key of the participant 2.""" - return "/4uYMof+xq788bVejB7+/5hOW436jU3mLfUhvW7FfRQ=" + return base64.b64encode(pk2) @pytest.fixture(scope="module") -def sk1() -> bytes: +def sk1(keypair1_path) -> bytes: """Bytes of the private key of the participant 1.""" - return bytes.fromhex( - "7957ceed56d44a384cf523619a00b2c129514daf422c0b799105fb2caa23ef97" - ) + return keypair1_path.read_bytes()[32:] @pytest.fixture(scope="module") -def sk2() -> bytes: +def sk2(keypair2_path) -> bytes: """Bytes of the private key of the participant 2.""" - return bytes.fromhex( - "05e5aa1c56ec3d6bf707893e6a038a825d80a2802fdb565fd8fecb840735a954" - ) + return keypair2_path.read_bytes()[32:] diff --git a/examples/yaos_millionaires/tests/data/keypair1.bin b/examples/yaos_millionaires/tests/data/keypair1.bin new file mode 100644 index 0000000..c99b8bf --- /dev/null +++ b/examples/yaos_millionaires/tests/data/keypair1.bin @@ -0,0 +1 @@ +* Tad|ykm.NNfJ&L>@R#<la \ No newline at end of file diff --git a/examples/yaos_millionaires/tests/data/keypair2.bin b/examples/yaos_millionaires/tests/data/keypair2.bin new file mode 100644 index 0000000..ccfa4f4 --- /dev/null +++ b/examples/yaos_millionaires/tests/data/keypair2.bin @@ -0,0 +1 @@ +Jlvq[E;v= )[uZ "4 fBN7y S陁acNWǙ \ No newline at end of file diff --git a/examples/yaos_millionaires/tests/test_app.py b/examples/yaos_millionaires/tests/test_app.py index 7ae2f47..ae679b2 100644 --- a/examples/yaos_millionaires/tests/test_app.py +++ b/examples/yaos_millionaires/tests/test_app.py @@ -1,6 +1,7 @@ """Unit test for our app.""" import base64 +import struct import requests from cenclave_lib_crypto.seal_box import unseal @@ -24,29 +25,31 @@ def test_participants(url, certificate, pk1, pk1_b64, pk2, pk2_b64): result = response.json() - expected_pk1_b64 = base64.b64encode(pk1).decode("utf-8") - expected_pk2_b64 = base64.b64encode(pk2).decode("utf-8") + expected_pk1_b64 = base64.b64encode(pk1) + expected_pk2_b64 = base64.b64encode(pk2) assert expected_pk1_b64 == pk1_b64 assert expected_pk2_b64 == pk2_b64 - assert pk1_b64 in result["participants"] - assert pk2_b64 in result["participants"] + assert pk1_b64.decode("utf-8") in result["participants"] + assert pk2_b64.decode("utf-8") in result["participants"] def test_richest(url, certificate, pk1_b64, sk1, pk2_b64, sk2): + n_b64 = base64.b64encode(struct.pack("