diff --git a/PROJECT_CONTEXT.rtf b/PROJECT_CONTEXT.rtf index e57b979..8f2faae 100644 --- a/PROJECT_CONTEXT.rtf +++ b/PROJECT_CONTEXT.rtf @@ -1,361 +1,492 @@ {\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;}} +{\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\fmodern\fprq1\fcharset0 Liberation Mono{\*\falt Courier New};}{\f5\fnil\fprq0\fcharset2 OpenSymbol{\*\falt Arial Unicode MS};}{\f6\froman\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f7\froman\fprq2\fcharset0 Helvetica{\*\falt Arial};}{\f8\froman\fprq2\fcharset0 Courier{\*\falt Courier New};}{\f9\fmodern\fprq1\fcharset0 Noto Sans Mono CJK SC;}{\f10\fnil\fprq2\fcharset0 0;}{\f11\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f12\fnil\fprq2\fcharset0 Noto Serif CJK SC;}{\f13\fnil\fprq2\fcharset0 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 +{\stylesheet{\s0\snext0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052 Normal;} +{\s1\sbasedon19\snext20\rtlch\af13\afs48\ab \ltrch\hich\af3\loch\ilvl0\outlinelevel0\sb240\sa120\f3\fs48\b\dbch\af12 heading 1;} +{\s2\sbasedon19\snext20\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12 heading 2;} +{\*\cs15\snext15\rtlch\ab \ltrch\loch\b Strong;} +{\*\cs16\snext16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9 Source Text;} +{\*\cs17\snext17\rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 Bullets;} +{\*\cs18\snext18 Numbering Symbols;} +{\s19\sbasedon0\snext20\rtlch\af13\afs28 \ltrch\hich\af6\loch\sb240\sa120\keepn\f6\fs28\dbch\af11 Heading;} +{\s20\sbasedon0\snext20\loch\sl276\slmult1\sb0\sa140 Body Text;} +{\s21\sbasedon20\snext21\rtlch\af13 \ltrch\loch\sl240\slmult1\sb0\sa0 List;} +{\s22\sbasedon0\snext22\rtlch\af13\afs24\ai \ltrch\loch\sb120\sa120\fs24\i caption;} +{\s23\sbasedon0\snext23\rtlch\af13 \ltrch Index;} +{\s24\sbasedon0\snext24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9 Preformatted Text;} +{\s25\sbasedon0\snext20\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12 Horizontal Line;} +{\s26\sbasedon0\snext26\loch\nowidctlpar\noline Table Contents;} +{\s27\sbasedon26\snext27\rtlch\ab \ltrch\loch\qc\noline\b Table Heading;} +}{\*\listtable{\list\listtemplateid1 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid1} +{\list\listtemplateid2 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid2} +{\list\listtemplateid3 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid3} +{\list\listtemplateid4 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid4} +{\list\listtemplateid5 +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi-283\li709} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-283\li1418} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-283\li2127} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-283\li2836} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-283\li3545} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-283\li4254} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-283\li4963} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-283\li5672} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-283\li6381}\listid5} +{\list\listtemplateid6 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li709} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li1418} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2127} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li2836} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li3545} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4254} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li4963} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li5672} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow0{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f5\rtlch\af5 \ltrch\loch\fi-283\li6381}\listid6} +{\list\listtemplateid7 +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid7} +}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}}{\*\generator LibreOffice/25.2.7.2$Linux_X86_64 LibreOffice_project/520$Build-2}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr2026\mo3\dy11\hr7\min49}{\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\aenddoc\fet\aftnrstcont\aftnstart1\aftnnrlc +{\*\ftnsep\chftnsep}\pgndec\pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\loch +\tab PostgreSQL database: }{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\loch +\tab Unit tests: }{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\loch +\tab HTTP testing: }{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar1\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af10\langfe2052{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\loch +\tab Zenroom cryptographic runtime }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs24\b\f7\dbch\af10\loch +(local execution via Python wrapper)} +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af8\loch\f8\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\loch +Database: }{\hich\af7\loch\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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\af13\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{\hich\af7\loch\f7\loch +Groups must include }{\hich\af8\loch\f8\loch +ca_reference}{\hich\af7\loch\f7\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\af13\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{\hich\af7\loch\f7\loch +Persons and devices must }{\hich\af7\loch\b\f7\loch +not}{\hich\af7\loch\f7\loch + include }{\hich\af8\loch\f8\loch +ca_reference}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af8\loch\f8\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\af13\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{\hich\af7\loch\f7\loch +Exactly }{\hich\af7\loch\b\f7\loch +one log entry must be produced per mutation}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\fs32\b\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 +\u8226\'95}{\hich\af7\loch\f7\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 +\par \pard\plain \s0\rtlch\af13\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{\hich\af7\loch\f7 \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 +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\ql\fi0\li0\lin0\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\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 \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +The system integrates }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +Zenroom}{\hich\af7\loch\fs36\b\f7\loch + as its cryptographic runtime.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Zenroom is a deterministic virtual machine designed for secure execution of\line cryptographic protocols defined in }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +Zencode scripts}{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +In this project Zenroom is executed }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +locally via the official Python wrapper}{\hich\af7\loch\fs36\b\f7\loch +.\line No Docker container or HTTP service is used.} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +ca_core} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9492\'3f\u9472\'3f }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +crypto} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9492\'3f\u9472\'3f }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\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 +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9474\'3f} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9660\'3f} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +zenroom Python wrapper} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9474\'3f} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\sb0\sa180{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + \u9660\'3f} +\par \pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10 + }{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +Zenroom runtime (local process)} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +This architecture removes the previous HTTP service layer and simplifies\line deployment and testing.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch +zenroom_service_client.py} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch +zenroom_service_client.py}{\hich\af7\loch\fs36\b\f7\loch + provides the }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +adapter between the CA backend and Zenroom}{\hich\af7\loch\fs36\b\f7\loch +.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +execute Zencode scripts using the Zenroom Python wrapper} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +pass structured JSON input} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +parse Zenroom output} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +normalize results into Python structures} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls1 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +translate runtime errors into }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch +ZenroomServiceError} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +All cryptographic operations are exposed through a }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +single client class}{\hich\af7\loch\fs36\b\f7\loch +:} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +{\*\bkmkstart code-block-viewer}{\*\bkmkend code-block-viewer}ZenroomServiceClient} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch +Supported Operations} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +The client provides the following cryptographic operations:} +\par \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s27\rtlch\ab \ltrch\loch\qc\noline\b\intbl{\loch +Method}\cell\pard\plain \s27\rtlch\ab \ltrch\loch\qc\noline\b\intbl{\loch +Description}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +generate_keypair()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Create an ECDH keypair}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +generate_public_key()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Derive public key from keyring}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +symmetric_encrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Encrypt message using password}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +symmetric_decrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Decrypt password-encrypted message}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +asymmetric_encrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Encrypt message for a recipient}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +asymmetric_decrypt()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Decrypt message from a sender}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +sign_objects()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Sign a string field}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx3145\clpadfl3\clpadl28\clpadft3\clpadt28\clpadfb3\clpadb28\clpadfr3\clpadr28\clvertalc\cellx6933\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\loch +verify_signature()}\cell\pard\plain \s26\loch\nowidctlpar\noline\intbl{\loch +Verify an ECDH signature}\cell\row\pard \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +These operations correspond directly to the Zencode scripts embedded in the module.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch +Script Execution} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +All Zencode scripts are executed through a single internal helper:} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +{\*\bkmkstart code-block-viewer Copy 1}{\*\bkmkend code-block-viewer Copy 1}_run_script()} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\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 +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +invoke }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch +zenroom.zencode_exec()} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +pass JSON input} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +capture Zenroom logs} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +parse returned JSON output} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls2 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +raise }{\hich\af7\loch\cs16\rtlch\af4 \ltrch\hich\af4\loch\f4\dbch\af9\fs36\b\f7\loch +ZenroomServiceError}{\hich\af7\loch\fs36\b\f7\loch + on failure} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +This centralizes Zenroom interaction and ensures consistent error handling.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch +Error Handling} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Zenroom failures are converted into Python exceptions.} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +{\*\bkmkstart code-block-viewer Copy 2}{\*\bkmkend code-block-viewer Copy 2}ZenroomServiceError} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +This exception is raised when:} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Zenroom execution fails} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +output cannot be parsed} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +expected fields are missing} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls3 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +verification fails} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +This allows the rest of the system to treat cryptographic failures as standard\line Python exceptions.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\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 \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Cryptographic functionality is tested using }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +direct functional tests}{\hich\af7\loch\fs36\b\f7\loch +.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Tests execute the real Zenroom runtime through the adapter and verify:} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +key generation} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +encryption/decryption roundtrips} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +signing} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +signature verification} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls4 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +signature rejection for tampered messages} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Example tests:} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +{\*\bkmkstart code-block-viewer Copy 3}{\*\bkmkend code-block-viewer Copy 3}tests/test_zenroom_service_client.py} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +The suite currently contains }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +9 tests}{\hich\af7\loch\fs36\b\f7\loch +:} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 1.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +generate keypair} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 2.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +generate public key} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 3.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +symmetric encryption} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 4.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +symmetric decryption} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 5.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +asymmetric encryption} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 6.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +asymmetric decryption} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 7.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +signing} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 8.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +successful verification} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain 9.\tab}\ilvl0\ls5 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +verification failure for modified message} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Tests run with:} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s24\rtlch\af4\afs20 \ltrch\hich\af4\loch\sb0\sa0\f4\fs20\dbch\af9\fi0\li0\lin0\ri0\rin0{\rtlch\af13\afs24 \ltrch\hich\af7\loch\fs36\b\f7\dbch\af10\loch +{\*\bkmkstart code-block-viewer Copy 4}{\*\bkmkend code-block-viewer Copy 4}python3 -m unittest discover} +\par \sect\sectd\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\ltrsect\sbknone\pard\plain \s20\loch\sl276\slmult1\sb0\sa140\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Typical runtime is }{\hich\af7\loch\cs15\rtlch\ab \ltrch\loch\b\fs36\f7\loch +under 0.5 seconds}{\hich\af7\loch\fs36\b\f7\loch +.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\rtlch\afs24 \ltrch\hich\af7\loch\fs36\b\f7\loch + +\par \pard\plain \s2\rtlch\af13\afs36\ab \ltrch\hich\af3\loch\ilvl1\outlinelevel1\sb200\sa120\f3\fs36\b\dbch\af12\sb0\sa180{\rtlch\afs24 \ltrch\hich\af7\loch\f7\dbch\af10\loch +Security Notes} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Zenroom scripts execute in an isolated runtime.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +All inputs are passed as structured JSON.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Dynamic script generation restricts field names to safe identifiers.} +\par \pard\plain \s20\loch\sl276\slmult1\sb0\sa140{\listtext\pard\plain \rtlch\af5 \ltrch\hich\af5\loch\f5\dbch\af5 \u8226\'95\tab}\ilvl0\ls6 \fi-283\li0\lin0\tx0\fi-283\li709\lin709\sb0\sa180{\hich\af7\loch\fs36\b\f7\loch +Cryptographic failures are surfaced immediately as exceptions.} +\par \pard\plain \s25\rtlch\afs12 \ltrch\loch\sb0\sa283\brdrt\brdrnone\brdrl\brdrnone\brdrb\brdrdb\brdrw1\brdrcf15\brsp0\brdrr\brdrnone\noline\fs12\sb0\sa180\hich\af7\loch\fs36\b\f7\loch + \par } \ No newline at end of file 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 9067c04..96453b4 100644 Binary files a/ca_core/crypto/__pycache__/zenroom_service_client.cpython-313.pyc and b/ca_core/crypto/__pycache__/zenroom_service_client.cpython-313.pyc differ diff --git a/ca_core/crypto/zenroom_service_client.py b/ca_core/crypto/zenroom_service_client.py index d5c424d..2a7abef 100644 --- a/ca_core/crypto/zenroom_service_client.py +++ b/ca_core/crypto/zenroom_service_client.py @@ -6,18 +6,26 @@ from zenroom import zenroom class ZenroomServiceError(RuntimeError): - pass + """Raised when local Zenroom execution fails or returns invalid data.""" class ZenroomServiceClient: """ - Local Zenroom client using the installed `zenroom` Python wrapper. + Local Zenroom client. - This preserves the public API of the old HTTP/Docker-backed client as much - as possible, so callers should not need changes. + This class replaces the old HTTP/Docker service client and talks directly to + the installed Zenroom Python wrapper plus local Zenroom binary. + + The public method names intentionally mirror the old service-oriented API so + the rest of the codebase can keep using the same interface. """ - SCRIPT_GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair from a name passed from data/keys + # ------------------------------------------------------------------------- + # Embedded Zencode scripts + # ------------------------------------------------------------------------- + + SCRIPT_GENERATE_KEYPAIR = """\ +Scenario 'ecdh': Create the keypair from a name passed from data/keys # Here we load the identity of the executor Given my name is in a 'string' named 'myName' @@ -27,7 +35,8 @@ When I create the ecdh key Then print my 'keyring' """ - SCRIPT_GENERATE_PUBLIC_KEY = """# Loading scenarios + SCRIPT_GENERATE_PUBLIC_KEY = """\ +# Loading scenarios Scenario 'ecdh': Create the public key # Loading the private keys @@ -40,7 +49,8 @@ When I create the ecdh public key Then print the 'ecdh public key' """ - SCRIPT_SYMMETRIC_ENCRYPT = """Scenario 'ecdh': Encrypt a message with the password + 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' @@ -48,7 +58,8 @@ When I encrypt the secret message 'message' with 'password' Then print the 'secret message' """ - SCRIPT_SYMMETRIC_DECRYPT = """Scenario 'ecdh': Decrypt the message with the password + 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' @@ -56,7 +67,8 @@ When I rename the 'text' to 'textDecrypted' Then print the 'textDecrypted' as 'string' """ - SCRIPT_ASYMMETRIC_ENCRYPT = """Scenario 'ecdh': Alice encrypts a message for Bob + SCRIPT_ASYMMETRIC_ENCRYPT = """\ +Scenario 'ecdh': Alice encrypts a message for Bob Given that I am known as 'sender' Given that I have my valid 'keyring' @@ -70,7 +82,8 @@ When I rename the 'secret message' to 'secret' Then print the 'secret' """ - SCRIPT_ASYMMETRIC_DECRYPT = """Scenario 'ecdh': Bob decrypts the message from Alice + 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' @@ -80,8 +93,10 @@ Then print the 'text' as 'string' Then print the 'header' from 'secret' as 'string' """ - # Used as a template so sign_objects() can sign any single string field. - SCRIPT_SIGN_TEMPLATE = """Scenario 'ecdh': create the signature of an object + # Dynamic templates are used for sign/verify so the public Python methods + # can work with arbitrary safe field names such as "myMessage". + 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' @@ -93,8 +108,8 @@ 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 + 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 @@ -103,38 +118,46 @@ 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 +# Here we load the object's signature Given I have a 'signature' named '{field_name}.signature' -# Here we perform the verifications +# Here we perform the verification 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 +# If verification succeeds, Zenroom prints the success string. +# If verification fails, Zenroom raises an error. Then print the string 'Zenroom certifies that signature is correct!' Then print the '{field_name}' """ def __init__(self) -> None: + """Construct a local client. No external service configuration is needed.""" pass + # ------------------------------------------------------------------------- + # Validation helpers + # ------------------------------------------------------------------------- + @staticmethod def _require_non_empty_str(name: str, value: str) -> str: + """Validate that a value is a non-empty string and return the stripped value.""" if not isinstance(value, str): raise TypeError(f"{name} must be a string") - v = value.strip() - if not v: + value = value.strip() + if not value: raise ValueError(f"{name} cannot be empty") - return v + return value @staticmethod def _require_dict(name: str, value: Any) -> Dict[str, Any]: + """Validate that a value is a dict.""" if not isinstance(value, dict): raise TypeError(f"{name} must be a dict") return value @staticmethod def _require_keys(d: Dict[str, Any], *, required: Tuple[str, ...], ctx: str) -> None: + """Ensure a dict contains the required keys.""" missing = [k for k in required if k not in d] if missing: raise ZenroomServiceError(f"Missing {missing} in {ctx}: {d!r}") @@ -142,16 +165,31 @@ Then print the '{field_name}' @staticmethod def _require_safe_field_name(field_name: str) -> str: """ - Restrict dynamic field names used inside generated Zencode to avoid script injection. + Restrict dynamic field names used in generated Zencode. + + This avoids accidentally generating unsafe script fragments. """ 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" + "field_name must match [A-Za-z_][A-Za-z0-9_]*" ) return field_name + # ------------------------------------------------------------------------- + # Zenroom execution helpers + # ------------------------------------------------------------------------- + + @staticmethod + def _normalize_logs(logs: Any) -> str: + """Convert Zenroom logs to a readable string for error messages.""" + if logs is None: + return "" + if isinstance(logs, list): + return "\n".join(str(item) for item in logs) + return str(logs) + def _run_script( self, script_name: str, @@ -161,25 +199,31 @@ Then print the '{field_name}' keys: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ - Execute a local Zencode script and return parsed JSON result as a dict. + Execute a Zencode script locally and return the parsed JSON result. + + Zenroom's Python wrapper returns an object with attributes such as: + - output : raw textual output + - logs : Zenroom logs + - result : parsed JSON object when output is JSON + + We prefer `result`, but fall back to parsing `output` if necessary. """ try: - result = zenroom.zencode_exec( + exec_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) + except Exception as exc: + raise ZenroomServiceError( + f"Failed to execute Zenroom script {script_name}: {exc}" + ) from exc + parsed = getattr(exec_result, "result", None) if isinstance(parsed, dict): return parsed - # Fallback: sometimes output may still be JSON text even if .result is None. + output = getattr(exec_result, "output", None) if isinstance(output, str): try: parsed_output = json.loads(output) @@ -188,23 +232,23 @@ Then print the '{field_name}' 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 = "" - + logs_text = self._normalize_logs(getattr(exec_result, "logs", None)) 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]}" + f"Logs: {logs_text[:2000]}" ) - def _pick_owner_block(self, res: Dict[str, Any], my_name: str, ctx: str) -> Dict[str, Any]: + @staticmethod + def _pick_owner_block(res: Dict[str, Any], my_name: str, ctx: str) -> Dict[str, Any]: """ - Zenroom often returns: { "": { ... } } - Prefer res[my_name] when present, else accept single-entry dict. + Zenroom often returns data shaped like: + { "": { ... } } + + Prefer `res[my_name]` when present. If not present, accept a single-entry + dict as long as it is unambiguous. """ if my_name in res: owner = res[my_name] @@ -219,10 +263,48 @@ Then print the '{field_name}' raise ZenroomServiceError(f"Invalid {ctx} response structure: {res!r}") return owner + @staticmethod + def _validate_secret_box(secret: Dict[str, Any], *, ctx: str) -> Dict[str, str]: + """ + Validate the standard Zenroom encrypted object shape. + + Expected keys: + - checksum + - header + - iv + - text + """ + if not isinstance(secret, dict): + raise ZenroomServiceError(f"Invalid {ctx} response: {secret!r}") + + required = ("checksum", "header", "iv", "text") + missing = [k for k in required if not isinstance(secret.get(k), str) or not secret[k].strip()] + if missing: + raise ZenroomServiceError(f"Invalid {ctx} fields {missing}: {secret!r}") + + return { + "checksum": secret["checksum"], + "header": secret["header"], + "iv": secret["iv"], + "text": secret["text"], + } + # ------------------------------------------------------------------------- # Service 1: Generate-a-keypair,-reading-identity-from-data # ------------------------------------------------------------------------- + def generate_keypair(self, my_name: str) -> Dict[str, Any]: + """ + Generate a keypair for the supplied identity name. + + Returns a normalized structure: + { + "my_name": "", + "keyring": {"ecdh": ""}, + "private_key": "", + # optionally "public_key" if Zenroom emits it + } + """ my_name = self._require_non_empty_str("my_name", my_name) res = self._run_script( @@ -258,7 +340,9 @@ Then print the '{field_name}' # ------------------------------------------------------------------------- # Service 2: Generate-public-key # ------------------------------------------------------------------------- + def generate_public_key(self, keyring: Dict[str, Any]) -> str: + """Derive the public key from a Zenroom keyring.""" keyring = self._require_dict("keyring", keyring) res = self._run_script( @@ -267,15 +351,23 @@ Then print the '{field_name}' data={"keyring": keyring}, ) - pub = res.get("ecdh_public_key") - if not isinstance(pub, str) or not pub.strip(): + public_key = res.get("ecdh_public_key") + if not isinstance(public_key, str) or not public_key.strip(): raise ZenroomServiceError(f"Invalid public key response: {res!r}") - return pub + + return public_key # ------------------------------------------------------------------------- # Service 3: Encrypt-a-message-with-the-password # ------------------------------------------------------------------------- + def symmetric_encrypt(self, *, header: str, message: str, shared_key: str) -> Dict[str, str]: + """ + Encrypt a message symmetrically using a password/shared key. + + Returns a standard Zenroom encrypted object with checksum/header/iv/text. + Note that Zenroom may encode the returned header representation. + """ 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) @@ -283,48 +375,44 @@ Then print the '{field_name}' res = self._run_script( "Encrypt-a-message-with-the-password", self.SCRIPT_SYMMETRIC_ENCRYPT, - data={"header": header, "message": message, "password": shared_key}, + 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}" - ) - - self._require_keys(sm, required=("checksum", "header", "iv", "text"), ctx="secret_message") - for k in ("checksum", "header", "iv", "text"): - if not isinstance(sm.get(k), str) or not sm[k].strip(): - raise ZenroomServiceError(f"Invalid secret_message.{k}: {sm!r}") - - return { - "checksum": sm["checksum"], - "header": sm["header"], - "iv": sm["iv"], - "text": sm["text"], - } + secret_message = res.get("secret_message") + return self._validate_secret_box(secret_message, ctx="secret_message") # ------------------------------------------------------------------------- # Service 4: Decrypt-the-message-with-the-password # ------------------------------------------------------------------------- + def symmetric_decrypt(self, *, secret_message: Dict[str, Any], shared_key: str) -> str: + """Decrypt a symmetrically encrypted secret message and return plaintext.""" secret_message = self._require_dict("secret_message", secret_message) shared_key = self._require_non_empty_str("shared_key", shared_key) res = self._run_script( "Decrypt-the-message-with-the-password", self.SCRIPT_SYMMETRIC_DECRYPT, - data={"secret_message": secret_message, "password": shared_key}, + data={ + "secret_message": secret_message, + "password": shared_key, + }, ) - txt = res.get("textDecrypted") - if not isinstance(txt, str): + text = res.get("textDecrypted") + if not isinstance(text, str): raise ZenroomServiceError(f"Invalid decrypt response: {res!r}") - return txt + + return text # ------------------------------------------------------------------------- # Service 5: Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography # ------------------------------------------------------------------------- + def asymmetric_encrypt( self, *, @@ -333,6 +421,12 @@ Then print the '{field_name}' header: str, message: str, ) -> Dict[str, str]: + """ + Encrypt a message asymmetrically for a receiver using sender key material. + + Note: the Zencode script expects the legacy spelling `reciever`, so the + payload preserves that exact key. + """ receiver_public_key = self._require_non_empty_str( "receiver_public_key", receiver_public_key ) @@ -351,27 +445,13 @@ Then print the '{field_name}' }, ) - sec = res.get("secret") - if not isinstance(sec, dict): - 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"): - if not isinstance(sec.get(k), str) or not sec[k].strip(): - raise ZenroomServiceError(f"Invalid secret.{k}: {sec!r}") - - return { - "checksum": sec["checksum"], - "header": sec["header"], - "iv": sec["iv"], - "text": sec["text"], - } + secret = res.get("secret") + return self._validate_secret_box(secret, ctx="secret") # ------------------------------------------------------------------------- # Service 6: Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography # ------------------------------------------------------------------------- + def asymmetric_decrypt( self, *, @@ -379,6 +459,15 @@ Then print the '{field_name}' receiver_keyring: Dict[str, Any], secret: Dict[str, Any], ) -> Dict[str, str]: + """ + Decrypt an asymmetrically encrypted message. + + 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) @@ -393,24 +482,25 @@ Then print the '{field_name}' }, ) - hdr = res.get("header") - txt = res.get("text") - if not isinstance(hdr, str) or not isinstance(txt, str): + header = res.get("header") + text = res.get("text") + if not isinstance(header, str) or not isinstance(text, str): raise ZenroomServiceError(f"Invalid asymmetric decrypt response: {res!r}") - return {"header": hdr, "text": txt} + return {"header": header, "text": text} # ------------------------------------------------------------------------- # Service 7: Sign-objects-using-asymmetric-cryptography # ------------------------------------------------------------------------- + def sign_objects(self, *, objects: Dict[str, Any], signer_keyring: Dict[str, Any]) -> Dict[str, Any]: """ - Signs exactly one string field from `objects`. + Sign exactly one string field from `objects`. - Example: - sign_objects(objects={"myMessage": "hello"}, signer_keyring=...) + Example input: + {"myMessage": "hello"} - Returns e.g.: + Example output: { "myMessage": "hello", "myMessage.signature": {"r": "...", "s": "..."} @@ -440,12 +530,12 @@ Then print the '{field_name}' ) sig_key = f"{field_name}.signature" - sig = res.get(sig_key) + signature = res.get(sig_key) - if not isinstance(sig, dict): + if not isinstance(signature, 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(signature.get("r"), str) or not isinstance(signature.get("s"), str): + raise ZenroomServiceError(f"Invalid signature fields for {sig_key}: {signature!r}") if not isinstance(res.get(field_name), str): raise ZenroomServiceError(f"Missing signed field {field_name!r} in response: {res!r}") @@ -455,6 +545,7 @@ Then print the '{field_name}' # ------------------------------------------------------------------------- # Service 8: Verify-asymmetric-cryptography-signature # ------------------------------------------------------------------------- + def verify_signature( self, *, @@ -463,6 +554,12 @@ Then print the '{field_name}' signature: Dict[str, Any], signer_public_key: str, ) -> bool: + """ + Verify a signature over one named string field. + + Returns True on success. Raises ZenroomServiceError on failure or if the + returned structure is unexpectedly malformed. + """ 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) @@ -470,39 +567,50 @@ Then print the '{field_name}' 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._run_script( "Verify-asymmetric-cryptography-signature", script, - data=payload, + data={ + message_field: message_value, + f"{message_field}.signature": signature, + "signer": {"public_key": signer_public_key}, + }, ) - out = res.get("output") - if isinstance(out, list) and out: + output = res.get("output") + if isinstance(output, list) and output: 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. + # Some Zenroom variants may return only the verified field itself. if res.get(message_field) == message_value: return True raise ZenroomServiceError(f"Invalid verify response: {res!r}") # ------------------------------------------------------------------------- - # Backward-compatible alias names + # Backward-compatible aliases # ------------------------------------------------------------------------- + def generate_a_keypair_reading_identity_from_data(self, my_name: str) -> Dict[str, Any]: + """Backward-compatible alias.""" return self.generate_keypair(my_name) - def encrypt_a_message_with_the_password(self, *, header: str, message: str, password: str) -> Dict[str, str]: + def encrypt_a_message_with_the_password( + self, + *, + header: str, + message: str, + password: str, + ) -> Dict[str, str]: + """Backward-compatible alias.""" 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]: - txt = self.symmetric_decrypt(secret_message=secret_message, shared_key=password) - return {"textDecrypted": txt} + def decrypt_the_message_with_the_password( + self, + *, + secret_message: Dict[str, Any], + password: str, + ) -> Dict[str, str]: + """Backward-compatible alias preserving the historical return shape.""" + text = self.symmetric_decrypt(secret_message=secret_message, shared_key=password) + return {"textDecrypted": text} diff --git a/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc b/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc index e9f1778..e78ca27 100644 Binary files a/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc and b/tests/__pycache__/test_zenroom_service_client.cpython-313.pyc differ diff --git a/tests/test_zenroom_service_client.py b/tests/test_zenroom_service_client.py index 84cb7d5..e94a157 100644 --- a/tests/test_zenroom_service_client.py +++ b/tests/test_zenroom_service_client.py @@ -1,6 +1,9 @@ import unittest -from ca_core.crypto.zenroom_service_client import ZenroomServiceClient +from ca_core.crypto.zenroom_service_client import ( + ZenroomServiceClient, + ZenroomServiceError, +) class TestZenroomServiceClient(unittest.TestCase): @@ -157,6 +160,23 @@ class TestZenroomServiceClient(unittest.TestCase): self.assertTrue(verified) + def test_9_verify_signature_rejects_modified_message(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"}, + signer_keyring=alice["keyring"], + ) + + with self.assertRaises(ZenroomServiceError): + self.client.verify_signature( + message_field="myMessage", + message_value="tampered", + signature=signed["myMessage.signature"], + signer_public_key=alice_public_key, + ) + if __name__ == "__main__": unittest.main()