From 4578d2743358209761666e64c7ab594eb0b7490c Mon Sep 17 00:00:00 2001 From: "Morten V. Christiansen" Date: Wed, 11 Mar 2026 07:12:00 +0100 Subject: [PATCH] local zenroom integration --- PROJECT_CONTEXT.md | 328 ------------- PROJECT_CONTEXT.rtf | 361 +++++++++++++++ __pycache__/test.cpython-313.pyc | Bin 0 -> 638 bytes __pycache__/test2.cpython-313.pyc | Bin 0 -> 491 bytes __pycache__/test3.cpython-313.pyc | Bin 0 -> 814 bytes .../zenroom_service_client.cpython-313.pyc | Bin 19850 -> 20790 bytes ca_core/crypto/zenroom_client.py | 157 ------- ca_core/crypto/zenroom_service_client.py | 431 ++++++++++-------- test.py | 18 + test2.py | 9 + test3.py | 20 + ...est_zenroom_service_client.cpython-313.pyc | Bin 13582 -> 8351 bytes tests/create_testdata.sql | 18 - tests/integration/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 141 -> 0 bytes ...integration_zenroom_docker.cpython-313.pyc | Bin 4319 -> 0 bytes .../test_zenroom_live.cpython-313.pyc | Bin 4090 -> 0 bytes ...service_client_integration.cpython-313.pyc | Bin 4174 -> 0 bytes .../test_integration_zenroom_docker.py | 88 ---- tests/integration/test_zenroom_live.py | 71 --- ...test_zenroom_service_client_integration.py | 89 ---- tests/test_zenroom_client.py | 90 ---- tests/test_zenroom_service_client.py | 328 ++++++------- tests/test_zenroom_service_client_clean.py | 55 --- 24 files changed, 785 insertions(+), 1278 deletions(-) delete mode 100644 PROJECT_CONTEXT.md create mode 100644 PROJECT_CONTEXT.rtf create mode 100644 __pycache__/test.cpython-313.pyc create mode 100644 __pycache__/test2.cpython-313.pyc create mode 100644 __pycache__/test3.cpython-313.pyc delete mode 100644 ca_core/crypto/zenroom_client.py create mode 100644 test.py create mode 100644 test2.py create mode 100644 test3.py delete mode 100644 tests/create_testdata.sql delete mode 100644 tests/integration/__init__.py delete mode 100644 tests/integration/__pycache__/__init__.cpython-313.pyc delete mode 100644 tests/integration/__pycache__/test_integration_zenroom_docker.cpython-313.pyc delete mode 100644 tests/integration/__pycache__/test_zenroom_live.cpython-313.pyc delete mode 100644 tests/integration/__pycache__/test_zenroom_service_client_integration.cpython-313.pyc delete mode 100644 tests/integration/test_integration_zenroom_docker.py delete mode 100644 tests/integration/test_zenroom_live.py delete mode 100644 tests/integration/test_zenroom_service_client_integration.py delete mode 100644 tests/test_zenroom_client.py delete mode 100644 tests/test_zenroom_service_client_clean.py diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md deleted file mode 100644 index 995e8d4..0000000 --- a/PROJECT_CONTEXT.md +++ /dev/null @@ -1,328 +0,0 @@ -CA/PKI Backend Project Context (Updated) -Stack - -Python 3 + psycopg (dict_row cursors) - -PostgreSQL database: ca - -Unit tests: unittest - -Run via: python3 -m unittest discover - -Current test count: 17 - -Database Schema (current assumptions) -entity - -id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY - -creation_ts TIMESTAMPTZ DEFAULT now() - -creator INT FK → entity(id) (nullable) - -name VARCHAR(100) NOT NULL - -type VARCHAR(...) NOT NULL - -Allowed types: person, group, device - -public_key VARCHAR(300) NOT NULL - -symmetrical_key VARCHAR(100) NULL - -status VARCHAR(...) NOT NULL DEFAULT 'active' - -Values: 'active', 'revoked' - -expiration DATE NULL - -ca_reference VARCHAR(100) NULL - -Constraint -CHECK ( - (type = 'group' AND ca_reference IS NOT NULL) - OR - (type <> 'group' AND ca_reference IS NULL) -) - -Rule: - -Groups MUST have a ca_reference - -All other entity types MUST have ca_reference IS NULL - -Indexes: - -Index on entity(name) - -Other indexes as needed - -group_member - -group_id INT FK → entity(id) ON DELETE CASCADE - -member_id INT FK → entity(id) ON DELETE CASCADE - -role VARCHAR(10) - -PRIMARY KEY (group_id, member_id) - -Index (member_id, group_id) - -Groups can contain: - -persons - -devices - -other groups - -property - -id INT FK → entity(id) ON DELETE CASCADE - -property_name VARCHAR(100) NOT NULL - -validation_policy CHAR(19) NOT NULL DEFAULT 'default' - -source VARCHAR(150) NULL - -PRIMARY KEY (id, property_name) - -Notes: - -validation_policy is CHAR(19) and padded by PostgreSQL. - -Used for flags/roles such as "creator". - -metadata - -Singleton row table (enforced at application level). - -Columns: - -name - -comment - -private_key - -public_key - -defense_p BOOLEAN NOT NULL DEFAULT false - -defense_p: - -Global system flag. - -Logged on change. - -log - -id SERIAL PRIMARY KEY - -ts TIMESTAMPTZ DEFAULT now() - -entry TEXT NOT NULL - -Every API mutation must insert exactly one row here. - -Core Business Rules -Creators are NOT an entity type - -"creator" is a property (property_name='creator') on a person. - -insert_creator(): - -Creates a person - -Adds "creator" property - -Revoked entities are immutable - -All entity mutations must call: - -ensure_entity_active(cursor, entity_id) - -Revoked entities CANNOT: - -Join groups - -Accept members - -Add/delete properties - -Change public_key - -Change symmetrical_key - -Change status again - -Group CA Reference Rule - -group entities must include a non-null ca_reference. - -person and device must not define ca_reference. - -Enforced at: - -Database level (CHECK constraint) - -Python validation level - -Property Metadata - -Each property includes: - -validation_policy - -source - -Defaults: - -validation_policy = 'default' - -source = NULL - -Property mutations: - -Require entity to be active - -Must log changes - -Metadata Defense Flag - -defense_p: - -Boolean system-wide flag - -Default: false - -Must be logged when changed - -Logging Rules - -All changes to: - -entity - -group_member - -property - -metadata - -Must call: - -log_change(cursor, "...") - -Logging: - -Happens inside same transaction - -No extra commits - -Exactly one log row per mutation - -Python Modules -ca_core/entity.py - -Provides: - -ensure_entity_active(cursor, entity_id) - -insert_creator(cursor, name, public_key) - -enroll_person(cursor, name, public_key, creator_id) - -create_group(cursor, name, public_key, creator_id, ca_reference) - -get_entity(cursor, entity_id) - -set_entity_status(cursor, entity_id, status, changed_by) - -set_entity_keys(cursor, entity_id, public_key, changed_by) - -set_symmetrical_key(cursor, entity_id, key, changed_by) - -get_symmetrical_key(cursor, entity_id) - -ca_core/group_member.py - -Uses member_id - -Prevents adding revoked groups/members - -Logs membership add/remove - -ca_core/property.py - -Table: - -property(id, property_name, validation_policy, source) - -Rules: - -Reject mutations if entity revoked - -Logs set/delete - -Default policy 'default' - -validation_policy is CHAR(19) - -ca_core/metadata.py - -Updates metadata fields - -Manages defense_p - -Logs changes - -ca_core/db_logging.py -log_change(cursor, message: str) - -Inserts into log(entry). - -Tests - -tests/test_entity.py - -tests/test_group.py - -tests/test_property.py - -tests/test_metadata.py - -Tests verify: - -Creation and enrollment - -Group membership - -Revocation immutability - -CA reference enforcement - -Property metadata fields - -defense_p behavior - -Log entry creation (case-insensitive substring checks) - -Run via: - -python3 -m unittest discover -Known Gotchas - -Do NOT name module logging.py (conflicts with stdlib) - -Schema and code must stay aligned: - -property.id (NOT entity_id) - -group_member.member_id - -entity.ca_reference constraint - -CHAR(19) pads values with spaces diff --git a/PROJECT_CONTEXT.rtf b/PROJECT_CONTEXT.rtf new file mode 100644 index 0000000..e57b979 --- /dev/null +++ b/PROJECT_CONTEXT.rtf @@ -0,0 +1,361 @@ +{\rtf1\ansi\deff3\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\froman\fprq2\fcharset0 Helvetica{\*\falt Arial};}{\f6\froman\fprq2\fcharset0 Courier{\*\falt Courier New};}{\f7\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f8\fnil\fprq2\fcharset0 Noto Sans Devanagari;}{\f9\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;} +{\stylesheet{\s0\snext0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052 Normal;} +{\s15\sbasedon0\snext16\rtlch\af8\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af7 Heading;} +{\s16\sbasedon0\snext16\loch\sl276\slmult1\sb0\sa140 Body Text;} +{\s17\sbasedon16\snext17\rtlch\af9 \ltrch List;} +{\s18\sbasedon0\snext18\rtlch\af9\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i caption;} +{\s19\sbasedon0\snext19\rtlch\af9 \ltrch\loch\noline Index;} +}{\*\generator LibreOffice/25.2.7.2$Linux_X86_64 LibreOffice_project/520$Build-2}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab720 +\hyphauto1\viewscale100\formshade\paperh15840\paperw12240\margl1440\margr1440\margt1440\margb1440\sectd\sbknone\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ftnbj\ftnstart1\ftnrstcont\ftnnar\fet\aftnrstcont\aftnstart1\aftnnrlc +{\*\ftnsep\chftnsep}\pgndec\pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +CA/PKI Backend Project Context} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +Stack} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab Python 3.13} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab FastAPI (web API)} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab psycopg (PostgreSQL driver)} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab PostgreSQL database: }{\hich\af6\loch\f6\loch +ca} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab Unit tests: }{\hich\af6\loch\f6\loch +unittest} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab HTTP testing: }{\hich\af6\loch\f6\loch +fastapi.testclient} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab Zenroom cryptographic runtime (Docker container)} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Run tests:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +python3 -m unittest discover} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Integration tests require:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +export DATABASE_URL="postgresql:///ca"} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Project Structure} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +pki/\line \u9500\'3f\u9472\'3f\u9472\'3f ca_core/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f entity.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f group_member.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f property.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f metadata.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f db_logging.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f crypto/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f zenroom_client.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f zenroom_service_client.py\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f ca_api/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f app.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f db.py\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f tests/\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_entity.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_group.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_property.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_metadata.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_smoke.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_unit.py\line \u9474\'3f \u9500\'3f\u9472\'3f\u9472\'3f test_api_integration.py\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f integration/\line \u9474\'3f \u9492\'3f\u9472\'3f\u9472\'3f zenroom tests\line \u9474\'3f\line \u9500\'3f\u9472\'3f\u9472\'3f create_tables.sql\line \u9492\'3f\u9472\'3f\u9472\'3f PROJECT_CONTEXT.md} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Architecture} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +HTTP API (FastAPI)\line \u9474\'3f\line \u9660\'3f\line ca_api (thin HTTP adapter)\line \u9474\'3f\line \u9660\'3f\line ca_core (business logic)\line \u9474\'3f\line \u9660\'3f\line PostgreSQL} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +The system is layered to keep business logic independent from the HTTP interface.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Database Overview} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Database: }{\hich\af5\loch\b\f5\loch +ca} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Core tables:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab entity} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab group_member} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab property} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab metadata} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab log} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +Entity Rules} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Entity types:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab person} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab group} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab device} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Status values:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab active} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab revoked} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Groups must include }{\hich\af6\loch\f6\loch +ca_reference}{\hich\af5\loch\f5\loch +.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Persons and devices must }{\hich\af5\loch\b\f5\loch +not}{\hich\af5\loch\f5\loch + include }{\hich\af6\loch\f6\loch +ca_reference}{\hich\af5\loch\f5\loch +.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Revoked entities are immutable.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Logging} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +All mutations must call:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +log_change(cursor, message)} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Exactly }{\hich\af5\loch\b\f5\loch +one log entry must be produced per mutation}{\hich\af5\loch\f5\loch +.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Logging occurs inside the same transaction.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Core Modules} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +entity.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Provides:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab insert_creator} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab enroll_person} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab create_group} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_entity} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab set_entity_status} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +group_member.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Provides:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab add_group_member} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab remove_group_member} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_members_of_group} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +property.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Provides:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab set_property} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab delete_property} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_properties} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +metadata.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Provides:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_name} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_comment} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_public_key} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab get_defense_p} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab set_name} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab set_defense_p} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Cryptographic Layer} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +The system integrates }{\hich\af5\loch\b\f5\loch +Zenroom}{\hich\af5\loch\f5\loch + for cryptographic operations.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Zenroom runs in an isolated environment, typically a }{\hich\af5\loch\b\f5\loch +Docker container}{\hich\af5\loch\f5\loch +.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +ca_core\line \u9474\'3f\line \u9660\'3f\line crypto clients\line \u9474\'3f\line \u9660\'3f\line Zenroom runtime} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +zenroom_client.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Local execution interface.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Responsibilities:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab execute Zenroom scripts} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab parse output} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab raise errors} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Used for development and some integration tests.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +zenroom_service_client.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +HTTP client for a Zenroom service container.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Responsibilities:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab send scripts} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab pass JSON input} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab return parsed result} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Typical flow:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +Python\line \u8595\'3f\line ZenroomServiceClient\line \u8595\'3f\line HTTP request\line \u8595\'3f\line Zenroom container\line \u8595\'3f\line JSON result} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +API Layer (ca_api)} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +FastAPI provides the HTTP interface.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Responsibilities:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab routing} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab validation} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab DB connection handling} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab error mapping} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Flow:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +HTTP request\line \u8595\'3f\line FastAPI route\line \u8595\'3f\line db_cursor()\line \u8595\'3f\line ca_core function\line \u8595\'3f\line commit / rollback} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Testing Strategy} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Three layers of tests exist.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +Core Unit Tests} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Test domain logic directly.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Examples:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_entity.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_group.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_property.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_metadata.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +API Unit Tests} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Test HTTP behaviour without DB.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Mocked components:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab db_cursor} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab ca_core functions} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +File:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_api_unit.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +API Integration Tests} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Full stack tests:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +FastAPI\line \u8595\'3f\line ca_core\line \u8595\'3f\line PostgreSQL} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +File:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab test_api_integration.py} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Requires:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +export DATABASE_URL="postgresql:///ca"} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs32\b\f5\loch +Zenroom Integration Tests} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Located in:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +tests/integration/} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Verify:} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab Zenroom script execution} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab service communication} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi-360\li360\lin360\sb0\sa0\ltrpar{\hich\af5\loch\f5 +\u8226\'95}{\hich\af5\loch\f5\loch +\tab error handling} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +Some tests require a running Zenroom container.} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\qc\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5 +\u8212\'97\u8212\'97\u8212\'97\u8212\'97\u8212\'97} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\fs36\b\f5\loch +Runtime Deployment Model} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af6\loch\f6\loch +FastAPI API\line \u9474\'3f\line \u9660\'3f\line ca_core\line \u9474\'3f\line \u9660\'3f\line crypto client\line \u9474\'3f\line \u9660\'3f\line Zenroom container\line \u9474\'3f\line \u9660\'3f\line PostgreSQL} +\par \pard\plain \s0\rtlch\af8\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052\ql\fi0\li0\lin0\sb0\sa180\ltrpar{\hich\af5\loch\f5\loch +This design keeps cryptographic execution isolated while keeping the Python service lightweight.} +\par } \ No newline at end of file diff --git a/__pycache__/test.cpython-313.pyc b/__pycache__/test.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6797c135b964a892b92452a76506b228f6ad81f2 GIT binary patch literal 638 zcmY+B&x_MQ6vtmonzpfBRF?H13&UPQFZSf6h_L9otS;`-Y4K8#kUpD`CQ~Mp{gG1_ z1pflTf5Dr7hzHS#IN(7%c`Nkj#hKO^`VRBn_r1@1Gs9c9+YTuE_~%3T*Z}yYMQP^0 zU|Fc(El{AwQ+NU#2+(WX7(w4E(aP_^$hfB_Y2)U|+5>8qnt;|Lv%k7?&6MXRL#<7% zdRiq}`Avq_Hnr-pE4GX(W@2~hzjeA}$a;)qLq^aa2R-tTqbHDfD@h`} zbPmHaOo#|PL5_$QlPF>53Gq_m?oUG>UFUhA>PN(1cZvY3-hnR7c$f$^0Ew&SDKT-K zynZqqoDK)QymowYNPGF#>3*LcJRu&r~G c*IVmUbQKkFOQk;9I|bbS&wYh$eqG)07l9g*OaK4? literal 0 HcmV?d00001 diff --git a/__pycache__/test2.cpython-313.pyc b/__pycache__/test2.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99850d5f392295327d6578714219889db2b7d8b7 GIT binary patch literal 491 zcmey&%ge<81Tl;oGv5H|#~=<2us|7~dw`6o48aV+jNS}hj75wJK=n!trcmi3rWmFm zb|?pl3T858gm8;kVpx?Jf|+AjOqn1eKogjPc##xCnK3NEEUZ8_TqiqNCuCAU}|bAX&G4sX}U07uUtt6S{Zsg*^U zdFfT`0ijMlp3YW&njE)OlM~~U^NUjTl8Y(}O7iupAV$R(L(Ga#1{+qy47BMM8-#O9 zAU!oNwJ5P9H6CbUL1JdnEvD>(TdV~@!%J=n!zBw!lYlM&sky}rWUplS407)+as7<^ z+*JKipbh#3*_rw!sl_EmdIgoYIBatBQ%ZAE?TQ3|>Oi4Z>;WV`Ff%eT-equm$SHf7 zQ@(-i0W0@)R+)>eGBd(2v#K^Q-DF^G$eN&Zg+cTI8_#t%g^O$o3xpPUE|k5@rr*E} QlFghTe3e18h!1EO0M%T2mH+?% literal 0 HcmV?d00001 diff --git a/__pycache__/test3.cpython-313.pyc b/__pycache__/test3.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dbe2f15f197d4ce6398bbdab262186ac8e0e481 GIT binary patch literal 814 zcmZuvO^ee&7@ka$Y0{?MwcYA2+hSQLY!zEU_OPIW2!e_pHU~ihp_@*-v1t<~X{9~o zpm#keJ+4QO{*x^|Gz;8 zpy~~%^04DJdJcX}_8E?U75^*SW}+YnKf$wKYH_084$-BYn#zT-To}EX$mI}5NSmV4 z7?sYm=g+?tC#V*Rm+ID3EsWJdl-?cPyU-4!T>g{&`O!=U$>I!%Nf@Fl1qdi?en_tc rqiiwC?vD1y*>aTGxyaucXYNKf_hu?Uj3gr%!YhrFPCu$-k6HT%%j?^e literal 0 HcmV?d00001 diff --git a/ca_core/crypto/__pycache__/zenroom_service_client.cpython-313.pyc b/ca_core/crypto/__pycache__/zenroom_service_client.cpython-313.pyc index 414e9689352762b9928f28016a2858c9f6d1b5d2..9067c0461d256148fddf041a32de1ec3a8ce767c 100644 GIT binary patch literal 20790 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 z z+mzDtWhwCtEq{aa$Hh?QkH4wgI48b#L3DSC?u(-BqP+G1MlJ}JDAdkm?NLuXo&YR; z0DiQEdV4nwWH64wvKRJ2z6))E41a_-<#+@6NH%Fmq;G;x=#9wENS6|_lX#15n8t3PlqU_&=%^v1%EM-^b!TEIz>EV=V4q@o%wcfPI-P2L@FZ+g^An=3)xSv+}z#azA4<|(*sddY3e`w-tA z8r9E`8Ad>(xpwVXo`Tzkk=!;-o9tG{=H#cYhghpLfG5y#a)Z5*V|Tq<$3IQT@AnAC)3i7-iAYY<8kW=LfInDR|wAklQ_=0lbN4@t7HSePZx(`t0kEurA-b z)9ghW!L2}}NBEjQPCMb-(57*d-L%~~oTuP*EAj|$`SxbBePb|B!EFnc0&e#@$W0tS zC!W73UJ8llL+SB19^t3_OLQfmAKEf*v0JuJ0TaC56UYsKAK`1hvljp@)Q+~i6`yQW zW9u5b-mx}$%ayOd8aq{8lXu{glk(K&EAdHBhDUC!d7W-D_XD1mop~!h*{J57Yr^`m zwd=PA@)cMEcxrF3YaKTxZ@Tghta0jy`KS_W4(~F1*OME^HoY6ifAH-*1+RCL_l|*^ ze((5a^1FEPbulz8MrXy)Z2IEdBZSG%(Ou?OY3RP8Id3J8Hhiogk9Ih+zh;Ef$+nw^ z^A1ww#Bo-VN1VzdH+h_(Y4gBc;3K%*n}Bo6NYU|&pTm+tP2NJAr|2#6d5XR_h+tDR zEUWS`JtNLu5$6-)Y$83qh)t$oL ze-B!r@eul^sgz$uc?avK?O2Y;D1rgk1RA@vDOdspeuf3TeQ;MUsG6;5&l0z Csj%Dt literal 19850 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 diff --git a/ca_core/crypto/zenroom_client.py b/ca_core/crypto/zenroom_client.py deleted file mode 100644 index 9f3eb7d..0000000 --- a/ca_core/crypto/zenroom_client.py +++ /dev/null @@ -1,157 +0,0 @@ -import json -import subprocess -import tempfile -from pathlib import Path -from typing import Any, Dict, Optional, Sequence, Union - - -JsonLike = Union[Dict[str, Any], Sequence[Any], str, int, float, bool, None] - - -class ZenroomError(RuntimeError): - """Raised when Zenroom execution fails.""" - - -class ZenroomDockerClient: - """Run Zenroom via Docker. - - This wrapper is intentionally small and testable. It focuses on: - - Writing inputs (script/data/keys/conf) to a temp workdir - - Running a docker container that executes Zenroom - - Returning parsed JSON output when possible - - Assumed container interface (common Zenroom CLI pattern): - zenroom -z -a -k -c - - If your docker image/entrypoint differs, pass `zenroom_args` accordingly. - - Note: This module is named *zenroom_client.py* (not zenroom.py) to avoid - potential import shadowing with future packages/modules. - """ - - def __init__( - self, - image: str = "zenroom/zenroom:latest", - docker_bin: str = "docker", - work_mount_path: str = "/work", - zenroom_args: Optional[Sequence[str]] = None, - timeout_s: int = 30, - ) -> None: - self.image = image - self.docker_bin = docker_bin - self.work_mount_path = work_mount_path - self.zenroom_args = list(zenroom_args) if zenroom_args is not None else ["zenroom", "-z"] - self.timeout_s = timeout_s - - def run( - self, - script: str, - *, - data: Optional[JsonLike] = None, - keys: Optional[JsonLike] = None, - conf: Optional[JsonLike] = None, - extra_docker_args: Optional[Sequence[str]] = None, - extra_zenroom_args: Optional[Sequence[str]] = None, - ) -> Union[dict, str]: - """Execute a Zenroom script. - - Args: - script: The Zenroom script (text). - data/keys/conf: Optional JSON-like payloads written to files. - extra_docker_args: Optional extra args inserted after `docker run`. - extra_zenroom_args: Optional extra args appended before the script path. - - Returns: - Parsed JSON dict if stdout is valid JSON, otherwise raw stdout string. - - Raises: - ZenroomError on non-zero exit. - """ - if not isinstance(script, str) or not script.strip(): - raise ValueError("script must be a non-empty string") - - with tempfile.TemporaryDirectory(prefix="zenroom_") as tmpdir: - workdir = Path(tmpdir) - - # Defensive: TemporaryDirectory normally creates the dir, but tests may mock it. - workdir.mkdir(parents=True, exist_ok=True) - - script_path = workdir / "script.zen" - script_path.write_text(script, encoding="utf-8") - - data_path = None - keys_path = None - conf_path = None - - if data is not None: - data_path = workdir / "data.json" - data_path.write_text(json.dumps(data), encoding="utf-8") - if keys is not None: - keys_path = workdir / "keys.json" - keys_path.write_text(json.dumps(keys), encoding="utf-8") - if conf is not None: - conf_path = workdir / "conf.json" - conf_path.write_text(json.dumps(conf), encoding="utf-8") - - cmd = [ - self.docker_bin, - "run", - "--rm", - "-i", - ] - - if extra_docker_args: - cmd.extend(list(extra_docker_args)) - - # Mount temp dir into container - cmd.extend( - [ - "-v", - f"{workdir}:{self.work_mount_path}", - "-w", - self.work_mount_path, - self.image, - ] - ) - - # Build zenroom command - cmd.extend(list(self.zenroom_args)) - - if data_path is not None: - cmd.extend(["-a", str(Path(self.work_mount_path) / data_path.name)]) - if keys_path is not None: - cmd.extend(["-k", str(Path(self.work_mount_path) / keys_path.name)]) - if conf_path is not None: - cmd.extend(["-c", str(Path(self.work_mount_path) / conf_path.name)]) - - if extra_zenroom_args: - cmd.extend(list(extra_zenroom_args)) - - cmd.append(str(Path(self.work_mount_path) / script_path.name)) - - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=self.timeout_s, - check=False, - ) - - if result.returncode != 0: - stderr = (result.stderr or "").strip() - stdout = (result.stdout or "").strip() - msg = stderr or stdout or f"Zenroom failed with exit code {result.returncode}" - raise ZenroomError(msg) - - out = (result.stdout or "").strip() - if not out: - return "" - - try: - parsed = json.loads(out) - if isinstance(parsed, dict): - return parsed - # Zenroom can output arrays too; keep compatibility. - return {"result": parsed} - except json.JSONDecodeError: - return out diff --git a/ca_core/crypto/zenroom_service_client.py b/ca_core/crypto/zenroom_service_client.py index 09300e8..d5c424d 100644 --- a/ca_core/crypto/zenroom_service_client.py +++ b/ca_core/crypto/zenroom_service_client.py @@ -1,99 +1,122 @@ import json -import urllib.request -import urllib.error +import re from typing import Any, Dict, Optional, Tuple +from zenroom import zenroom + class ZenroomServiceError(RuntimeError): pass class ZenroomServiceClient: + """ + Local Zenroom client using the installed `zenroom` Python wrapper. - def __init__( - self, - base_url: str = "http://localhost:3300", - *, - api_prefix: str = "/api", - timeout_s: int = 10, - ) -> None: - self.base_url = base_url.rstrip("/") - self.api_prefix = api_prefix.strip() + This preserves the public API of the old HTTP/Docker-backed client as much + as possible, so callers should not need changes. + """ - if self.api_prefix in {"", "/"}: - self.api_prefix = "" - elif not self.api_prefix.startswith("/"): - self.api_prefix = "/" + self.api_prefix + SCRIPT_GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair from a name passed from data/keys - self.timeout_s = timeout_s +# Here we load the identity of the executor +Given my name is in a 'string' named 'myName' - def _make_url(self, path: str) -> str: - path = "/" + path.lstrip("/") - return f"{self.base_url}{self.api_prefix}{path}" +# Here we generate and print the keypair +When I create the ecdh key +Then print my 'keyring' +""" - def _request_json( - self, - method: str, - path: str, - payload: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: + SCRIPT_GENERATE_PUBLIC_KEY = """# Loading scenarios +Scenario 'ecdh': Create the public key - url = self._make_url(path) - data = None - headers = {"Accept": "application/json"} +# Loading the private keys +Given I have the 'keyring' - if payload is not None: - data = json.dumps(payload).encode("utf-8") - headers["Content-Type"] = "application/json" +# Generating the public keys +When I create the ecdh public key - req = urllib.request.Request(url, data=data, headers=headers, method=method.upper()) +# Here we print all the output +Then print the 'ecdh public key' +""" - try: - with urllib.request.urlopen(req, timeout=self.timeout_s) as resp: - raw = resp.read() - text = raw.decode("utf-8") - except urllib.error.HTTPError as e: - body = "" - try: - body = e.read().decode("utf-8") - except Exception: - pass - raise ZenroomServiceError(f"HTTP {e.code} from {url}: {body or e.reason}") from e - except urllib.error.URLError as e: - raise ZenroomServiceError(f"Failed to reach {url}: {e.reason}") from e + SCRIPT_SYMMETRIC_ENCRYPT = """Scenario 'ecdh': Encrypt a message with the password +Given that I have a 'string' named 'password' +Given that I have a 'string' named 'header' +Given that I have a 'string' named 'message' +When I encrypt the secret message 'message' with 'password' +Then print the 'secret message' +""" - text = text.strip() - if not text: - raise ZenroomServiceError(f"Empty response from {url}") + SCRIPT_SYMMETRIC_DECRYPT = """Scenario 'ecdh': Decrypt the message with the password +Given that I have a valid 'secret message' +Given that I have a 'string' named 'password' +When I decrypt the text of 'secret message' with 'password' +When I rename the 'text' to 'textDecrypted' +Then print the 'textDecrypted' as 'string' +""" - try: - parsed = json.loads(text) - except json.JSONDecodeError as e: - raise ZenroomServiceError(f"Non-JSON response from {url}: {text[:200]}") from e + SCRIPT_ASYMMETRIC_ENCRYPT = """Scenario 'ecdh': Alice encrypts a message for Bob - if not isinstance(parsed, dict): - raise ZenroomServiceError(f"Expected JSON object from {url}") +Given that I am known as 'sender' +Given that I have my valid 'keyring' +Given that I have a valid 'public key' from 'reciever' +Given that I have a 'string' named 'message' +Given that I have a 'string' named 'header' - return parsed +When I encrypt the secret message of 'message' for 'reciever' +When I rename the 'secret message' to 'secret' - def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]: - return self._request_json("POST", path, payload) +Then print the 'secret' +""" - def _post_data(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]: - # Per your rule: if "keys" is empty, omit it entirely. - # All your services in this round only need {"data": ...} - res = self._post(path, {"data": data}) + SCRIPT_ASYMMETRIC_DECRYPT = """Scenario 'ecdh': Bob decrypts the message from Alice +Given that I am known as 'reciever' +Given I have my 'keyring' +Given I have a 'public key' from 'sender' +Given I have a 'secret message' named 'secret' +When I decrypt the text of 'secret' from 'sender' +Then print the 'text' as 'string' +Then print the 'header' from 'secret' as 'string' +""" - # RESTroom convention: on failure you get zenroom_errors and/or exception - if "zenroom_errors" in res or "exception" in res: - exc = res.get("exception", "") - ze = res.get("zenroom_errors") - logs = "" - if isinstance(ze, dict): - logs = str(ze.get("logs", ""))[:800] - raise ZenroomServiceError(f"Zenroom error from {path}: {exc or logs or 'unknown error'}") + # Used as a template so sign_objects() can sign any single string field. + SCRIPT_SIGN_TEMPLATE = """Scenario 'ecdh': create the signature of an object +Given I am 'signer' +Given I have my 'keyring' +Given that I have a 'string' named '{field_name}' inside 'mySecretStuff' - return res +When I create the ecdh signature of '{field_name}' +When I rename the 'ecdh signature' to '{field_name}.signature' + +Then print the '{field_name}' +Then print the '{field_name}.signature' +""" + + # Used as a template so verify_signature() can verify any single string field. + SCRIPT_VERIFY_TEMPLATE = """rule check version 3.0.0 +Scenario 'ecdh': Bob verifies the signature from Alice + +# Here we load the pubkey we'll verify the signature against +Given I have a 'public key' from 'signer' + +# Here we load the objects to be verified +Given I have a 'string' named '{field_name}' + +# Here we load the objects's signatures +Given I have a 'signature' named '{field_name}.signature' + +# Here we perform the verifications +When I verify the '{field_name}' has a ecdh signature in '{field_name}.signature' by 'signer' + +# Here we print out the result: if the verifications succeeded, a string will be printed out +# if the verifications failed, Zenroom will throw an error +Then print the string 'Zenroom certifies that signature is correct!' +Then print the '{field_name}' +""" + + def __init__(self) -> None: + pass @staticmethod def _require_non_empty_str(name: str, value: str) -> str: @@ -116,6 +139,68 @@ class ZenroomServiceClient: if missing: raise ZenroomServiceError(f"Missing {missing} in {ctx}: {d!r}") + @staticmethod + def _require_safe_field_name(field_name: str) -> str: + """ + Restrict dynamic field names used inside generated Zencode to avoid script injection. + """ + if not isinstance(field_name, str): + raise TypeError("field_name must be a string") + if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", field_name): + raise ValueError( + "field_name must match [A-Za-z_][A-Za-z0-9_]* for safe Zencode generation" + ) + return field_name + + def _run_script( + self, + script_name: str, + script_text: str, + *, + data: Optional[Dict[str, Any]] = None, + keys: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """ + Execute a local Zencode script and return parsed JSON result as a dict. + """ + try: + result = zenroom.zencode_exec( + script_text, + data=json.dumps(data or {}), + keys=json.dumps(keys) if keys is not None else None, + ) + except Exception as e: + raise ZenroomServiceError(f"Failed to execute Zenroom script {script_name}: {e}") from e + + logs = getattr(result, "logs", None) + output = getattr(result, "output", None) + parsed = getattr(result, "result", None) + + if isinstance(parsed, dict): + return parsed + + # Fallback: sometimes output may still be JSON text even if .result is None. + if isinstance(output, str): + try: + parsed_output = json.loads(output) + except json.JSONDecodeError: + parsed_output = None + if isinstance(parsed_output, dict): + return parsed_output + + log_text = logs + if isinstance(log_text, list): + log_text = "\n".join(str(x) for x in log_text) + elif log_text is None: + log_text = "" + + out_preview = output if isinstance(output, str) else repr(output) + raise ZenroomServiceError( + f"Zenroom script {script_name} did not return a JSON object.\n" + f"Output: {out_preview[:800]}\n" + f"Logs: {str(log_text)[:2000]}" + ) + def _pick_owner_block(self, res: Dict[str, Any], my_name: str, ctx: str) -> Dict[str, Any]: """ Zenroom often returns: { "": { ... } } @@ -126,7 +211,9 @@ class ZenroomServiceClient: elif len(res) == 1: owner = next(iter(res.values())) else: - raise ZenroomServiceError(f"Ambiguous {ctx} response (no key '{my_name}', len={len(res)}): {res!r}") + raise ZenroomServiceError( + f"Ambiguous {ctx} response (no key '{my_name}', len={len(res)}): {res!r}" + ) if not isinstance(owner, dict): raise ZenroomServiceError(f"Invalid {ctx} response structure: {res!r}") @@ -136,26 +223,12 @@ class ZenroomServiceClient: # Service 1: Generate-a-keypair,-reading-identity-from-data # ------------------------------------------------------------------------- def generate_keypair(self, my_name: str) -> Dict[str, Any]: - """ - POST Generate-a-keypair,-reading-identity-from-data - body: {"data": {"myName": ""}} - - Observed response: - { "": { "keyring": { "ecdh": "" } } } - - Return (normalized, plus backward-compatible fields): - { - "my_name": "", - "keyring": {"ecdh": ""}, - "private_key": "", - # "public_key": "" only if the service variant returned it - } - """ my_name = self._require_non_empty_str("my_name", my_name) - res = self._post_data( + res = self._run_script( "Generate-a-keypair,-reading-identity-from-data", - {"myName": my_name}, + self.SCRIPT_GENERATE_KEYPAIR, + data={"myName": my_name}, ) owner = self._pick_owner_block(res, my_name, "keypair") @@ -166,15 +239,16 @@ class ZenroomServiceClient: private_key = keyring.get("ecdh") if not isinstance(private_key, str) or not private_key.strip(): - raise ZenroomServiceError(f"Invalid keypair response (missing keyring.ecdh): {res!r}") + raise ZenroomServiceError( + f"Invalid keypair response (missing keyring.ecdh): {res!r}" + ) out: Dict[str, Any] = { "my_name": my_name, "keyring": keyring, - "private_key": private_key, # convenience alias + "private_key": private_key, } - # Some variants might include this (but your current one does not) public_key = owner.get("ecdh_public_key") if isinstance(public_key, str) and public_key.strip(): out["public_key"] = public_key @@ -185,20 +259,12 @@ class ZenroomServiceClient: # Service 2: Generate-public-key # ------------------------------------------------------------------------- def generate_public_key(self, keyring: Dict[str, Any]) -> str: - """ - POST Generate-public-key - body: {"data": {"keyring": {"ecdh": "..."} }} - - Response: - {"ecdh_public_key": ""} - - Returns the public key string. - """ keyring = self._require_dict("keyring", keyring) - res = self._post_data( + res = self._run_script( "Generate-public-key", - {"keyring": keyring}, + self.SCRIPT_GENERATE_PUBLIC_KEY, + data={"keyring": keyring}, ) pub = res.get("ecdh_public_key") @@ -207,30 +273,24 @@ class ZenroomServiceClient: return pub # ------------------------------------------------------------------------- - # Service 3: Encrypt-a-message-with-the-password (symmetric) + # Service 3: Encrypt-a-message-with-the-password # ------------------------------------------------------------------------- def symmetric_encrypt(self, *, header: str, message: str, shared_key: str) -> Dict[str, str]: - """ - POST Encrypt-a-message-with-the-password - body: {"data": {"header": "...", "message": "...", "password": "..."}} - - Response: - {"secret_message": {"checksum": "...", "header": "...", "iv": "...", "text": "..."}} - - Returns the inner secret_message dict. - """ header = self._require_non_empty_str("header", header) message = self._require_non_empty_str("message", message) shared_key = self._require_non_empty_str("shared_key", shared_key) - res = self._post_data( + res = self._run_script( "Encrypt-a-message-with-the-password", - {"header": header, "message": message, "password": shared_key}, + self.SCRIPT_SYMMETRIC_ENCRYPT, + data={"header": header, "message": message, "password": shared_key}, ) sm = res.get("secret_message") if not isinstance(sm, dict): - raise ZenroomServiceError(f"Invalid encrypt response (missing secret_message): {res!r}") + raise ZenroomServiceError( + f"Invalid encrypt response (missing secret_message): {res!r}" + ) self._require_keys(sm, required=("checksum", "header", "iv", "text"), ctx="secret_message") for k in ("checksum", "header", "iv", "text"): @@ -245,24 +305,16 @@ class ZenroomServiceClient: } # ------------------------------------------------------------------------- - # Service 4: Decrypt-the-message-with-the-password (symmetric) + # Service 4: Decrypt-the-message-with-the-password # ------------------------------------------------------------------------- def symmetric_decrypt(self, *, secret_message: Dict[str, Any], shared_key: str) -> str: - """ - POST Decrypt-the-message-with-the-password - body: {"data": {"secret_message": {...}, "password": "..."}} - - Response: - {"textDecrypted": ""} - - Returns decrypted plaintext. - """ secret_message = self._require_dict("secret_message", secret_message) shared_key = self._require_non_empty_str("shared_key", shared_key) - res = self._post_data( + res = self._run_script( "Decrypt-the-message-with-the-password", - {"secret_message": secret_message, "password": shared_key}, + self.SCRIPT_SYMMETRIC_DECRYPT, + data={"secret_message": secret_message, "password": shared_key}, ) txt = res.get("textDecrypted") @@ -281,21 +333,17 @@ class ZenroomServiceClient: header: str, message: str, ) -> Dict[str, str]: - """ - POST Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography - - Note: service expects 'reciever' spelling. - - Returns inner 'secret' dict with checksum/header/iv/text. - """ - receiver_public_key = self._require_non_empty_str("receiver_public_key", receiver_public_key) + receiver_public_key = self._require_non_empty_str( + "receiver_public_key", receiver_public_key + ) sender_keyring = self._require_dict("sender_keyring", sender_keyring) header = self._require_non_empty_str("header", header) message = self._require_non_empty_str("message", message) - res = self._post_data( + res = self._run_script( "Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography", - { + self.SCRIPT_ASYMMETRIC_ENCRYPT, + data={ "reciever": {"public_key": receiver_public_key}, "sender": {"keyring": sender_keyring}, "header": header, @@ -305,7 +353,9 @@ class ZenroomServiceClient: sec = res.get("secret") if not isinstance(sec, dict): - raise ZenroomServiceError(f"Invalid asymmetric encrypt response (missing secret): {res!r}") + raise ZenroomServiceError( + f"Invalid asymmetric encrypt response (missing secret): {res!r}" + ) self._require_keys(sec, required=("checksum", "header", "iv", "text"), ctx="secret") for k in ("checksum", "header", "iv", "text"): @@ -329,20 +379,14 @@ class ZenroomServiceClient: receiver_keyring: Dict[str, Any], secret: Dict[str, Any], ) -> Dict[str, str]: - """ - POST Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography - - Note: service expects 'reciever' spelling. - - Returns {"header": "...", "text": "..."}. - """ sender_public_key = self._require_non_empty_str("sender_public_key", sender_public_key) receiver_keyring = self._require_dict("receiver_keyring", receiver_keyring) secret = self._require_dict("secret", secret) - res = self._post_data( + res = self._run_script( "Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography", - { + self.SCRIPT_ASYMMETRIC_DECRYPT, + data={ "sender": {"public_key": sender_public_key}, "reciever": {"keyring": receiver_keyring}, "secret": secret, @@ -361,31 +405,50 @@ class ZenroomServiceClient: # ------------------------------------------------------------------------- def sign_objects(self, *, objects: Dict[str, Any], signer_keyring: Dict[str, Any]) -> Dict[str, Any]: """ - POST Sign-objects-using-asymmetric-cryptography - body: {"data": {"mySecretStuff": {...}, "signer": {"keyring": {...}}}} + Signs exactly one string field from `objects`. - Response echoes fields and adds "<field>.signature": {"r": "...", "s": "..."}. - Returns response as-is (validated to contain at least one signature). + Example: + sign_objects(objects={"myMessage": "hello"}, signer_keyring=...) + + Returns e.g.: + { + "myMessage": "hello", + "myMessage.signature": {"r": "...", "s": "..."} + } """ objects = self._require_dict("objects", objects) signer_keyring = self._require_dict("signer_keyring", signer_keyring) - res = self._post_data( + if len(objects) != 1: + raise ZenroomServiceError( + f"sign_objects currently supports exactly one field, got keys={list(objects.keys())!r}" + ) + + field_name, field_value = next(iter(objects.items())) + field_name = self._require_safe_field_name(field_name) + field_value = self._require_non_empty_str(field_name, field_value) + + script = self.SCRIPT_SIGN_TEMPLATE.format(field_name=field_name) + + res = self._run_script( "Sign-objects-using-asymmetric-cryptography", - {"mySecretStuff": objects, "signer": {"keyring": signer_keyring}}, + script, + data={ + "mySecretStuff": {field_name: field_value}, + "signer": {"keyring": signer_keyring}, + }, ) - # Validate at least one "*.signature" and that each has r/s. - sig_keys = [k for k in res.keys() if isinstance(k, str) and k.endswith(".signature")] - if not sig_keys: - raise ZenroomServiceError(f"No signatures found in sign response: {res!r}") + sig_key = f"{field_name}.signature" + sig = res.get(sig_key) - for k in sig_keys: - sig = res.get(k) - if not isinstance(sig, dict): - raise ZenroomServiceError(f"Invalid signature object for {k}: {res!r}") - if not isinstance(sig.get("r"), str) or not isinstance(sig.get("s"), str): - raise ZenroomServiceError(f"Invalid signature fields for {k}: {sig!r}") + if not isinstance(sig, dict): + raise ZenroomServiceError(f"Invalid signature object for {sig_key}: {res!r}") + if not isinstance(sig.get("r"), str) or not isinstance(sig.get("s"), str): + raise ZenroomServiceError(f"Invalid signature fields for {sig_key}: {sig!r}") + + if not isinstance(res.get(field_name), str): + raise ZenroomServiceError(f"Missing signed field {field_name!r} in response: {res!r}") return res @@ -400,43 +463,39 @@ class ZenroomServiceClient: signature: Dict[str, Any], signer_public_key: str, ) -> bool: - """ - POST Verify-asymmetric-cryptography-signature - - Input example uses dynamic field names like: - "myMessage": "...", - "myMessage.signature": {"r": "...", "s": "..."}, - "signer": {"public_key": "..."} - - On success, response includes: - {"output": ["Zenroom_certifies_that_signature_is_correct!"], ...} - - On failure, RESTroom returns zenroom_errors/exception which _post_data raises. - Returns True on success. - """ - message_field = self._require_non_empty_str("message_field", message_field) + message_field = self._require_safe_field_name(message_field) message_value = self._require_non_empty_str("message_value", message_value) signature = self._require_dict("signature", signature) signer_public_key = self._require_non_empty_str("signer_public_key", signer_public_key) + script = self.SCRIPT_VERIFY_TEMPLATE.format(field_name=message_field) + payload: Dict[str, Any] = { message_field: message_value, f"{message_field}.signature": signature, "signer": {"public_key": signer_public_key}, } - res = self._post_data("Verify-asymmetric-cryptography-signature", payload) + res = self._run_script( + "Verify-asymmetric-cryptography-signature", + script, + data=payload, + ) out = res.get("output") - if not isinstance(out, list) or not out: - raise ZenroomServiceError(f"Invalid verify response: {res!r}") + if isinstance(out, list) and out: + return True - # We accept any non-empty success output, but the canonical string is: - # "Zenroom_certifies_that_signature_is_correct!" - return True + # Some Zenroom variants may print the success string directly as a named field + # or return the verified message only. If execution succeeded and no exception + # was raised, we still treat that as success when the original message is present. + if res.get(message_field) == message_value: + return True + + raise ZenroomServiceError(f"Invalid verify response: {res!r}") # ------------------------------------------------------------------------- - # Backward-compatible alias names (used by existing live tests / older code) + # Backward-compatible alias names # ------------------------------------------------------------------------- def generate_a_keypair_reading_identity_from_data(self, my_name: str) -> Dict[str, Any]: return self.generate_keypair(my_name) @@ -445,7 +504,5 @@ class ZenroomServiceClient: return self.symmetric_encrypt(header=header, message=message, shared_key=password) def decrypt_the_message_with_the_password(self, *, secret_message: Dict[str, Any], password: str) -> Dict[str, str]: - # Historical alias returned {"textDecrypted": "..."} in some tests; - # keep that shape for compatibility. txt = self.symmetric_decrypt(secret_message=secret_message, shared_key=password) return {"textDecrypted": txt} diff --git a/test.py b/test.py new file mode 100644 index 0000000..f7bc05a --- /dev/null +++ b/test.py @@ -0,0 +1,18 @@ +from zenroom import zenroom + +contract = """Scenario ecdh: Create a ecdh key +Given that I am known as 'Alice' +When I create the ecdh key +Then print the 'keyring' +""" + +result = zenroom.zencode_exec(contract) + +print("OUTPUT:") +print(result.output) + +print("LOGS:") +print(result.logs) + +print("PARSED RESULT:") +print(result.result) diff --git a/test2.py b/test2.py new file mode 100644 index 0000000..ffee78b --- /dev/null +++ b/test2.py @@ -0,0 +1,9 @@ +from ca_core.crypto.zenroom_service_client import ZenroomServiceClient + +client = ZenroomServiceClient() + +kp = client.generate_keypair("Alice") +print("KEYPAIR:", kp) + +pub = client.generate_public_key(kp["keyring"]) +print("PUBLIC:", pub) diff --git a/test3.py b/test3.py new file mode 100644 index 0000000..bc0a178 --- /dev/null +++ b/test3.py @@ -0,0 +1,20 @@ +from ca_core.crypto.zenroom_service_client import ZenroomServiceClient + +client = ZenroomServiceClient() + +kp = client.generate_keypair("Alice") +pub = client.generate_public_key(kp["keyring"]) + +signed = client.sign_objects( + objects={"myMessage": "hello world"}, + signer_keyring=kp["keyring"], +) +print(signed) + +ok = client.verify_signature( + message_field="myMessage", + message_value=signed["myMessage"], + signature=signed["myMessage.signature"], + signer_public_key=pub, +) +print(ok) diff --git a/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc b/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc index 0fd43fb9fdba6302bfa892a6cfe7b34719a5d0ef..e9f17782d6796cbd69520afd7603d6053aaed493 100644 GIT binary patch literal 8351 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 literal 13582 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 diff --git a/tests/create_testdata.sql b/tests/create_testdata.sql deleted file mode 100644 index e25deff..0000000 --- a/tests/create_testdata.sql +++ /dev/null @@ -1,18 +0,0 @@ -truncate table entity; -truncate table group_member; -truncate table property; - -insert into entity (name,group_p,geo_offset,public_key) -values -('Svend',false,2355,'35AB456' ), -('Knud',false,4442,'36546AA'), -('Konger',true,3456, '87432CA'); - - -insert into group_member(group_id,person_id) -values -(3,1); - -insert into property(id,property_name) -values -(2,'aaa'); diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/integration/__pycache__/__init__.cpython-313.pyc b/tests/integration/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 871338f757754ea22dadb3173b647bcd26827930..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmey&%ge<81UZkEWrFC(AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl<5{fzwFRQ=N8 z)FS<Y>`eWV)Z&t2{mi_Q)bygnlFa-({rLFIyv&mLc)fzkTO2mI`6;D2sdh!IK+Pb- Qi$RQ!%#4hTMa)1J0Dz$&`~Uy| diff --git a/tests/integration/__pycache__/test_integration_zenroom_docker.cpython-313.pyc b/tests/integration/__pycache__/test_integration_zenroom_docker.cpython-313.pyc deleted file mode 100644 index 633383007238dd6c0765d22a35b4354626c893b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4319 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 D;N`zY diff --git a/tests/integration/__pycache__/test_zenroom_live.cpython-313.pyc b/tests/integration/__pycache__/test_zenroom_live.cpython-313.pyc deleted file mode 100644 index cc87de140504dbd98df279a00e2060b049742c34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4090 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 diff --git a/tests/integration/__pycache__/test_zenroom_service_client_integration.cpython-313.pyc b/tests/integration/__pycache__/test_zenroom_service_client_integration.cpython-313.pyc deleted file mode 100644 index 0d0714b52b9a8dd297d3b6a8bb11343c8f6d66ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4174 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 diff --git a/tests/integration/test_integration_zenroom_docker.py b/tests/integration/test_integration_zenroom_docker.py deleted file mode 100644 index f453d2c..0000000 --- a/tests/integration/test_integration_zenroom_docker.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -import sys -import unittest -import subprocess -from pathlib import Path - -# Make ca_core importable as the module root (so `import crypto...` works) -code_path = Path(__file__).parents[1] / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_client import ZenroomDockerClient, ZenroomError - - -def _docker_ok(): - """Return (ok, reason).""" - try: - p = subprocess.run( - ["docker", "version"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - timeout=10, - check=False, - ) - except FileNotFoundError: - return False, "docker CLI not found" - except Exception as e: - return False, f"docker check failed: {e}" - - if p.returncode != 0: - out = (p.stdout or "").strip() - return False, f"docker not usable: {out}" - return True, "" - - -def _image_exists(image: str): - """Return (ok, reason).""" - try: - p = subprocess.run( - ["docker", "image", "inspect", image], - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT, - text=True, - timeout=10, - check=False, - ) - except Exception as e: - return False, f"docker image inspect failed: {e}" - - if p.returncode != 0: - return False, f"docker image not found locally: {image}" - return True, "" - - -class TestZenroomDockerIntegration(unittest.TestCase): - """Integration tests for ZenroomDockerClient. - - Enable by setting: - ZENROOM_DOCKER_INTEGRATION=1 - - Image selection: - ZENROOM_IMAGE (default: zenroom) - """ - - @classmethod - def setUpClass(cls): - if not os.getenv("ZENROOM_DOCKER_INTEGRATION"): - raise unittest.SkipTest("Docker integration disabled (set ZENROOM_DOCKER_INTEGRATION=1)") - - ok, reason = _docker_ok() - if not ok: - raise unittest.SkipTest(reason) - - cls.image = os.getenv("ZENROOM_IMAGE", "zenroom") - - ok, reason = _image_exists(cls.image) - if not ok: - raise unittest.SkipTest(reason + " (build it or set ZENROOM_IMAGE)") - - def test_basic_execution(self): - client = ZenroomDockerClient(image=self.image) - out = client.run("print('hello')") - self.assertIn("hello", str(out)) - - def test_nonzero_exit_raises(self): - client = ZenroomDockerClient(image=self.image) - with self.assertRaises(ZenroomError): - client.run("THIS IS NOT VALID ZENCODE") diff --git a/tests/integration/test_zenroom_live.py b/tests/integration/test_zenroom_live.py deleted file mode 100644 index 114528e..0000000 --- a/tests/integration/test_zenroom_live.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import unittest -import sys -from pathlib import Path - -# Import from ca_core -code_path = Path(__file__).parent.parent.parent / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_service_client import ZenroomServiceClient - - -def _live_enabled() -> bool: - return os.environ.get("RUN_LIVE_ZENROOM", "").strip().lower() in {"1", "true", "yes"} - - -@unittest.skipUnless(_live_enabled(), "Set RUN_LIVE_ZENROOM=1 to run live Zenroom service smoke tests") -class TestZenroomLiveServices(unittest.TestCase): - - @classmethod - def setUpClass(cls): - base_url = os.environ.get("ZENROOM_BASE_URL", "http://localhost:3300").strip() - api_prefix = os.environ.get("ZENROOM_API_PREFIX", "/api").strip() - timeout_s = int(os.environ.get("ZENROOM_TIMEOUT_S", "20")) - - cls.client = ZenroomServiceClient( - base_url=base_url, - api_prefix=api_prefix, - timeout_s=timeout_s, - ) - - def test_end_to_end_8_calls(self): - sender_kp = self.client.generate_keypair("LiveUser123456") - sender_pub = self.client.generate_public_key(sender_kp["keyring"]) - - plaintext = "Dear Bob, your name is too short, goodbye - Alice." - sm = self.client.symmetric_encrypt( - header="A very important secret", - message=plaintext, - shared_key="myVerySecretPassword", - ) - pt = self.client.symmetric_decrypt(secret_message=sm, shared_key="myVerySecretPassword") - self.assertEqual(pt, plaintext) - - secret = self.client.asymmetric_encrypt( - receiver_public_key=sender_pub, - sender_keyring=sender_kp["keyring"], - message="Hello from live test", - header="Live header", - ) - dec = self.client.asymmetric_decrypt( - sender_public_key=sender_pub, - receiver_keyring=sender_kp["keyring"], - secret=secret, - ) - self.assertEqual(dec["header"], "Live header") - self.assertEqual(dec["text"], "Hello from live test") - - signed = self.client.sign_objects( - signer_keyring=sender_kp["keyring"], - objects={"myMessage": "Signed live message"}, - ) - sig = signed["myMessage.signature"] - - ok = self.client.verify_signature( - message_field="myMessage", - message_value=signed["myMessage"], - signature={"r": sig["r"], "s": sig["s"]}, - signer_public_key=sender_pub, - ) - self.assertTrue(ok) diff --git a/tests/integration/test_zenroom_service_client_integration.py b/tests/integration/test_zenroom_service_client_integration.py deleted file mode 100644 index 9380f84..0000000 --- a/tests/integration/test_zenroom_service_client_integration.py +++ /dev/null @@ -1,89 +0,0 @@ -import os -import unittest -import sys -from pathlib import Path - -# Import from ca_core -code_path = Path(__file__).parents[2] / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_service_client import ZenroomServiceClient - - -class TestZenroomServiceClientIntegration(unittest.TestCase): - """Service integration: runs against a live RESTroom/Zenroom HTTP service. - - Enable by setting: - ZENROOM_BASE_URL=http://localhost:3300 - """ - - @unittest.skipUnless(os.getenv("ZENROOM_BASE_URL"), "No ZENROOM_BASE_URL set") - def test_end_to_end_smoke_8_calls(self): - """Hits exactly these 8 endpoints once each (in typical service logs): - 1) Generate-a-keypair,-reading-identity-from-data - 2) Generate-public-key - 3) Encrypt-a-message-with-the-password - 4) Decrypt-the-message-with-the-password - 5) Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography - 6) Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography - 7) Sign-objects-using-asymmetric-cryptography - 8) Verify-asymmetric-cryptography-signature - - Note: To keep it to 8 calls, we reuse the same identity as both sender and receiver - for the asymmetric encrypt/decrypt roundtrip. - """ - client = ZenroomServiceClient(base_url=os.environ["ZENROOM_BASE_URL"]) - - # 1) keypair (private) -> 2) public key - sender_kp = client.generate_keypair("IntegrationUser123456") - self.assertIn("keyring", sender_kp) - self.assertIn("private_key", sender_kp) - - sender_pub = client.generate_public_key(sender_kp["keyring"]) - self.assertIsInstance(sender_pub, str) - self.assertTrue(sender_pub.strip()) - - # 3) symmetric encrypt -> 4) symmetric decrypt - plaintext = "Dear Bob, your name is too short, goodbye - Alice." - sm = client.symmetric_encrypt( - header="A very important secret", - message=plaintext, - shared_key="myVerySecretPassword", - ) - pt = client.symmetric_decrypt(secret_message=sm, shared_key="myVerySecretPassword") - self.assertEqual(pt, plaintext) - - # 5) asymmetric encrypt (receiver pub == sender pub to avoid extra keypair/public-key calls) - secret = client.asymmetric_encrypt( - receiver_public_key=sender_pub, - sender_keyring=sender_kp["keyring"], - message="Hello from integration test", - header="Integration header", - ) - - # 6) asymmetric decrypt (sender public key is sender_pub; receiver private key == sender private key) - dec = client.asymmetric_decrypt( - sender_public_key=sender_pub, - receiver_keyring=sender_kp["keyring"], - secret=secret, - ) - self.assertEqual(dec["header"], "Integration header") - self.assertEqual(dec["text"], "Hello from integration test") - - # 7) sign -> 8) verify - signed = client.sign_objects( - signer_keyring=sender_kp["keyring"], - objects={"myMessage": "Signed integration message"}, - ) - self.assertIn("myMessage.signature", signed) - sig = signed["myMessage.signature"] - self.assertIn("r", sig) - self.assertIn("s", sig) - - ok = client.verify_signature( - message_field="myMessage", - message_value=signed["myMessage"], - signature={"r": sig["r"], "s": sig["s"]}, - signer_public_key=sender_pub, - ) - self.assertTrue(ok) diff --git a/tests/test_zenroom_client.py b/tests/test_zenroom_client.py deleted file mode 100644 index 8043ad6..0000000 --- a/tests/test_zenroom_client.py +++ /dev/null @@ -1,90 +0,0 @@ -import unittest -import sys -from pathlib import Path -from unittest import mock - -# Allow imports from ca_core (same pattern as existing tests) -code_path = Path(__file__).parent.parent / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_client import ZenroomDockerClient, ZenroomError - - -class TestZenroomDockerClient(unittest.TestCase): - def _fake_completed(self, returncode=0, stdout="", stderr=""): - cp = mock.Mock() - cp.returncode = returncode - cp.stdout = stdout - cp.stderr = stderr - return cp - - @mock.patch("crypto.zenroom_client.subprocess.run") - def test_run_builds_expected_docker_command(self, m_run): - m_run.return_value = self._fake_completed(stdout='{"ok": true}') - client = ZenroomDockerClient(image="zenroom/zenroom:latest") - - # Patch temp dir so we can assert paths deterministically - with mock.patch("crypto.zenroom_client.tempfile.TemporaryDirectory") as m_td: - m_td.return_value.__enter__.return_value = "/tmp/zenroom_test" - m_td.return_value.__exit__.return_value = False - - res = client.run("print('hi')", data={"a": 1}, keys={"k": "v"}, conf={"c": 2}) - - self.assertEqual(res, {"ok": True}) - - args, kwargs = m_run.call_args - cmd = args[0] - self.assertIn("docker", cmd[0]) - self.assertIn("run", cmd) - self.assertIn("zenroom/zenroom:latest", cmd) - - # Mount and workdir - self.assertIn("-v", cmd) - self.assertIn("/tmp/zenroom_test:/work", cmd) - self.assertIn("-w", cmd) - self.assertIn("/work", cmd) - - # Zenroom base args - self.assertIn("zenroom", cmd) - self.assertIn("-z", cmd) - - # Input files flags should be present - self.assertIn("-a", cmd) - self.assertIn("/work/data.json", cmd) - self.assertIn("-k", cmd) - self.assertIn("/work/keys.json", cmd) - self.assertIn("-c", cmd) - self.assertIn("/work/conf.json", cmd) - - # Script at end - self.assertEqual(cmd[-1], "/work/script.zen") - - # subprocess.run called with capture_output/text - self.assertTrue(kwargs.get("capture_output")) - self.assertTrue(kwargs.get("text")) - - @mock.patch("crypto.zenroom_client.subprocess.run") - def test_run_returns_raw_stdout_when_not_json(self, m_run): - m_run.return_value = self._fake_completed(stdout="hello") - client = ZenroomDockerClient() - with mock.patch("crypto.zenroom_client.tempfile.TemporaryDirectory") as m_td: - m_td.return_value.__enter__.return_value = "/tmp/zenroom_test" - m_td.return_value.__exit__.return_value = False - out = client.run("print('hi')") - self.assertEqual(out, "hello") - - @mock.patch("crypto.zenroom_client.subprocess.run") - def test_run_raises_on_nonzero_exit(self, m_run): - m_run.return_value = self._fake_completed(returncode=1, stderr="boom") - client = ZenroomDockerClient() - with mock.patch("crypto.zenroom_client.tempfile.TemporaryDirectory") as m_td: - m_td.return_value.__enter__.return_value = "/tmp/zenroom_test" - m_td.return_value.__exit__.return_value = False - with self.assertRaises(ZenroomError) as ctx: - client.run("print('hi')") - self.assertIn("boom", str(ctx.exception)) - - def test_run_requires_non_empty_script(self): - client = ZenroomDockerClient() - with self.assertRaises(ValueError): - client.run(" ") diff --git a/tests/test_zenroom_service_client.py b/tests/test_zenroom_service_client.py index 236a36d..84cb7d5 100644 --- a/tests/test_zenroom_service_client.py +++ b/tests/test_zenroom_service_client.py @@ -1,224 +1,162 @@ -import json import unittest -from unittest import mock -import sys -from pathlib import Path -code_path = Path(__file__).parents[1] / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_service_client import ZenroomServiceClient, ZenroomServiceError - - -class _FakeHTTPResponse: - def __init__(self, body: bytes): - self._body = body - - def read(self): - return self._body - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False +from ca_core.crypto.zenroom_service_client import ZenroomServiceClient class TestZenroomServiceClient(unittest.TestCase): + def setUp(self): + self.client = ZenroomServiceClient() - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_generate_keypair_returns_keyring_and_private_key(self, m_urlopen): - payload = { - "User123456": {"keyring": {"ecdh": "PRIVKEY"}} - } - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + def test_1_generate_keypair(self): + result = self.client.generate_keypair("Alice") - client = ZenroomServiceClient(base_url="http://localhost:3300") - res = client.generate_keypair("User123456") + self.assertEqual(result["my_name"], "Alice") + self.assertIn("keyring", result) + self.assertIsInstance(result["keyring"], dict) + self.assertIn("ecdh", result["keyring"]) + self.assertIsInstance(result["keyring"]["ecdh"], str) + self.assertTrue(result["keyring"]["ecdh"]) + self.assertEqual(result["private_key"], result["keyring"]["ecdh"]) - self.assertEqual(res["my_name"], "User123456") - self.assertEqual(res["private_key"], "PRIVKEY") - self.assertEqual(res["keyring"], {"ecdh": "PRIVKEY"}) - self.assertNotIn("public_key", res) + def test_2_generate_public_key(self): + keypair = self.client.generate_keypair("Alice") + public_key = self.client.generate_public_key(keypair["keyring"]) - req = m_urlopen.call_args[0][0] - self.assertEqual(req.method, "POST") - self.assertTrue(req.full_url.endswith("/api/Generate-a-keypair,-reading-identity-from-data")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual(sent, {"data": {"myName": "User123456"}}) + self.assertIsInstance(public_key, str) + self.assertTrue(public_key) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_generate_public_key_returns_string(self, m_urlopen): - payload = {"ecdh_public_key": "PUBKEY"} - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) - - client = ZenroomServiceClient(base_url="http://localhost:3300") - pub = client.generate_public_key({"ecdh": "PRIVKEY"}) - self.assertEqual(pub, "PUBKEY") - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Generate-public-key")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual(sent, {"data": {"keyring": {"ecdh": "PRIVKEY"}}}) - - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_symmetric_encrypt_returns_secret_message_dict(self, m_urlopen): - payload = { - "secret_message": { - "checksum": "C", - "header": "H", - "iv": "IV", - "text": "T", - } - } - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) - - client = ZenroomServiceClient(base_url="http://localhost:3300") - sm = client.symmetric_encrypt( - header="A very important secret", - message="hello", - shared_key="myVerySecretPassword", - ) - self.assertEqual(sm["checksum"], "C") - self.assertEqual(sm["header"], "H") - self.assertEqual(sm["iv"], "IV") - self.assertEqual(sm["text"], "T") - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Encrypt-a-message-with-the-password")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual( - sent, - {"data": {"header": "A very important secret", "message": "hello", "password": "myVerySecretPassword"}}, + def test_3_symmetric_encrypt(self): + result = self.client.symmetric_encrypt( + header="test-header", + message="hello symmetric crypto", + shared_key="correct horse battery staple", ) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_symmetric_decrypt_returns_plaintext(self, m_urlopen): - payload = {"textDecrypted": "PLAINTEXT"} - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + self.assertIsInstance(result, dict) + self.assertIn("checksum", result) + self.assertIn("header", result) + self.assertIn("iv", result) + self.assertIn("text", result) - client = ZenroomServiceClient(base_url="http://localhost:3300") - txt = client.symmetric_decrypt(secret_message={"iv": "x"}, shared_key="k") - self.assertEqual(txt, "PLAINTEXT") + self.assertIsInstance(result["checksum"], str) + self.assertIsInstance(result["header"], str) + self.assertIsInstance(result["iv"], str) + self.assertIsInstance(result["text"], str) - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Decrypt-the-message-with-the-password")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual(sent, {"data": {"secret_message": {"iv": "x"}, "password": "k"}}) + self.assertTrue(result["checksum"]) + self.assertTrue(result["header"]) + self.assertTrue(result["iv"]) + self.assertTrue(result["text"]) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_asymmetric_encrypt_returns_secret(self, m_urlopen): - payload = {"secret": {"checksum": "C", "header": "H", "iv": "IV", "text": "T"}} - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + def test_4_symmetric_decrypt(self): + plaintext = "hello symmetric crypto" + password = "correct horse battery staple" - client = ZenroomServiceClient(base_url="http://localhost:3300") - sec = client.asymmetric_encrypt( - receiver_public_key="PUB", - sender_keyring={"ecdh": "PRIV"}, - header="hdr", - message="msg", - ) - self.assertEqual(sec, {"checksum": "C", "header": "H", "iv": "IV", "text": "T"}) - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual( - sent, - { - "data": { - "reciever": {"public_key": "PUB"}, - "sender": {"keyring": {"ecdh": "PRIV"}}, - "header": "hdr", - "message": "msg", - } - }, + encrypted = self.client.symmetric_encrypt( + header="test-header", + message=plaintext, + shared_key=password, ) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_asymmetric_decrypt_returns_header_and_text(self, m_urlopen): - payload = {"header": "HDR", "text": "TXT"} - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) - - client = ZenroomServiceClient(base_url="http://localhost:3300") - out = client.asymmetric_decrypt( - sender_public_key="PUB", - receiver_keyring={"ecdh": "PRIV"}, - secret={"iv": "IV"}, - ) - self.assertEqual(out, {"header": "HDR", "text": "TXT"}) - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual( - sent, - { - "data": { - "sender": {"public_key": "PUB"}, - "reciever": {"keyring": {"ecdh": "PRIV"}}, - "secret": {"iv": "IV"}, - } - }, + decrypted = self.client.symmetric_decrypt( + secret_message=encrypted, + shared_key=password, ) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_sign_objects_returns_response_and_validates_signatures(self, m_urlopen): - payload = { - "myMessage": "hello", - "myMessage.signature": {"r": "R", "s": "S"}, - } - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + self.assertEqual(decrypted, plaintext) - client = ZenroomServiceClient(base_url="http://localhost:3300") - res = client.sign_objects(objects={"myMessage": "hello"}, signer_keyring={"ecdh": "PRIV"}) + def test_5_asymmetric_encrypt(self): + alice = self.client.generate_keypair("Alice") + bob = self.client.generate_keypair("Bob") + bob_public_key = self.client.generate_public_key(bob["keyring"]) - self.assertEqual(res["myMessage"], "hello") - self.assertEqual(res["myMessage.signature"]["r"], "R") - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Sign-objects-using-asymmetric-cryptography")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual( - sent, - {"data": {"mySecretStuff": {"myMessage": "hello"}, "signer": {"keyring": {"ecdh": "PRIV"}}}}, + result = self.client.asymmetric_encrypt( + receiver_public_key=bob_public_key, + sender_keyring=alice["keyring"], + header="asym-header", + message="hello bob", ) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_verify_signature_returns_true(self, m_urlopen): - payload = { - "myMessage": "hello", - "output": ["Zenroom_certifies_that_signature_is_correct!"], - } - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + self.assertIsInstance(result, dict) + self.assertIn("checksum", result) + self.assertIn("header", result) + self.assertIn("iv", result) + self.assertIn("text", result) - client = ZenroomServiceClient(base_url="http://localhost:3300") - ok = client.verify_signature( + self.assertIsInstance(result["checksum"], str) + self.assertIsInstance(result["header"], str) + self.assertIsInstance(result["iv"], str) + self.assertIsInstance(result["text"], str) + + self.assertTrue(result["checksum"]) + self.assertTrue(result["header"]) + self.assertTrue(result["iv"]) + self.assertTrue(result["text"]) + + def test_6_asymmetric_decrypt(self): + alice = self.client.generate_keypair("Alice") + bob = self.client.generate_keypair("Bob") + + alice_public_key = self.client.generate_public_key(alice["keyring"]) + bob_public_key = self.client.generate_public_key(bob["keyring"]) + + encrypted = self.client.asymmetric_encrypt( + receiver_public_key=bob_public_key, + sender_keyring=alice["keyring"], + header="asym-header", + message="hello bob", + ) + + decrypted = self.client.asymmetric_decrypt( + sender_public_key=alice_public_key, + receiver_keyring=bob["keyring"], + secret=encrypted, + ) + + self.assertIsInstance(decrypted, dict) + self.assertEqual(decrypted["header"], "asym-header") + self.assertEqual(decrypted["text"], "hello bob") + + def test_7_sign_objects(self): + alice = self.client.generate_keypair("Alice") + + result = self.client.sign_objects( + objects={"myMessage": "hello signed world"}, + signer_keyring=alice["keyring"], + ) + + self.assertIsInstance(result, dict) + self.assertIn("myMessage", result) + self.assertIn("myMessage.signature", result) + self.assertEqual(result["myMessage"], "hello signed world") + + signature = result["myMessage.signature"] + self.assertIsInstance(signature, dict) + self.assertIn("r", signature) + self.assertIn("s", signature) + self.assertIsInstance(signature["r"], str) + self.assertIsInstance(signature["s"], str) + self.assertTrue(signature["r"]) + self.assertTrue(signature["s"]) + + def test_8_verify_signature(self): + alice = self.client.generate_keypair("Alice") + alice_public_key = self.client.generate_public_key(alice["keyring"]) + + signed = self.client.sign_objects( + objects={"myMessage": "hello signed world"}, + signer_keyring=alice["keyring"], + ) + + verified = self.client.verify_signature( message_field="myMessage", - message_value="hello", - signature={"r": "R", "s": "S"}, - signer_public_key="PUB", - ) - self.assertTrue(ok) - - req = m_urlopen.call_args[0][0] - self.assertTrue(req.full_url.endswith("/api/Verify-asymmetric-cryptography-signature")) - sent = json.loads(req.data.decode("utf-8")) - self.assertEqual( - sent, - {"data": {"myMessage": "hello", "myMessage.signature": {"r": "R", "s": "S"}, "signer": {"public_key": "PUB"}}}, + message_value=signed["myMessage"], + signature=signed["myMessage.signature"], + signer_public_key=alice_public_key, ) - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_zenroom_error_is_raised(self, m_urlopen): - payload = {"exception": "boom", "zenroom_errors": {"logs": "fail"}} - m_urlopen.return_value = _FakeHTTPResponse(json.dumps(payload).encode("utf-8")) + self.assertTrue(verified) - client = ZenroomServiceClient(base_url="http://localhost:3300") - with self.assertRaises(ZenroomServiceError): - client.verify_signature( - message_field="myMessage", - message_value="hello", - signature={"r": "R", "s": "S"}, - signer_public_key="PUB", - ) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_zenroom_service_client_clean.py b/tests/test_zenroom_service_client_clean.py deleted file mode 100644 index 7fb26b2..0000000 --- a/tests/test_zenroom_service_client_clean.py +++ /dev/null @@ -1,55 +0,0 @@ -import json -import unittest -from unittest import mock -import sys -from pathlib import Path - -code_path = Path(__file__).parents[1] / "ca_core" -sys.path.insert(0, str(code_path)) - -from ca_core.crypto.zenroom_service_client import ZenroomServiceClient - - -class _FakeHTTPResponse: - def __init__(self, body: bytes): - self._body = body - - def read(self): - return self._body - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False - - -class TestZenroomServiceClient(unittest.TestCase): - - @mock.patch("crypto.zenroom_service_client.urllib.request.urlopen") - def test_generate_keypair_unpacks_keys(self, m_urlopen): - - payload = { - "Owner": { - "ecdh_public_key": "PUBKEY", - "keyring": {"ecdh": "PRIVKEY"}, - } - } - - m_urlopen.return_value = _FakeHTTPResponse( - json.dumps(payload).encode("utf-8") - ) - - client = ZenroomServiceClient(base_url="http://localhost:3300") - res = client.generate_keypair("User123") - - self.assertEqual(res["public_key"], "PUBKEY") - self.assertEqual(res["private_key"], "PRIVKEY") - - req = m_urlopen.call_args[0][0] - self.assertEqual(req.method, "POST") - self.assertTrue( - req.full_url.endswith( - "/api/Generate-a-keypair,-reading-identity-from-data" - ) - )