From a36c895253554d70a7acab3ef12565da21ef3148 Mon Sep 17 00:00:00 2001 From: wKyrong Date: Thu, 15 Dec 2022 16:23:48 +0800 Subject: [PATCH] big optimize huks doc Signed-off-by: wKyrong --- zh-cn/application-dev/security/Readme-CN.md | 5 +- .../security/figures/huks_architect.png | Bin 0 -> 22157 bytes .../figures/huks_import_wrapped_key.png | Bin 0 -> 38392 bytes .../figures/huks_key_user_auth_work_flow.png | Bin 0 -> 10108 bytes .../huks_keymaterial_rsa_priv_struct.png | Bin 0 -> 29484 bytes .../figures/huks_keymaterial_struct.png | Bin 0 -> 34634 bytes .../application-dev/security/huks-appendix.md | 227 + .../security/huks-guidelines.md | 4689 +++++++---------- .../application-dev/security/huks-overview.md | 75 +- 9 files changed, 2140 insertions(+), 2856 deletions(-) create mode 100644 zh-cn/application-dev/security/figures/huks_architect.png create mode 100644 zh-cn/application-dev/security/figures/huks_import_wrapped_key.png create mode 100644 zh-cn/application-dev/security/figures/huks_key_user_auth_work_flow.png create mode 100644 zh-cn/application-dev/security/figures/huks_keymaterial_rsa_priv_struct.png create mode 100644 zh-cn/application-dev/security/figures/huks_keymaterial_struct.png create mode 100644 zh-cn/application-dev/security/huks-appendix.md diff --git a/zh-cn/application-dev/security/Readme-CN.md b/zh-cn/application-dev/security/Readme-CN.md index af608f4bf4..27f4f72cc3 100644 --- a/zh-cn/application-dev/security/Readme-CN.md +++ b/zh-cn/application-dev/security/Readme-CN.md @@ -9,8 +9,9 @@ - [用户认证开发概述](userauth-overview.md) - [用户认证开发指导](userauth-guidelines.md) - 密钥管理 - - [HUKS开发概述](huks-overview.md) - - [HUKS开发指导](huks-guidelines.md) + - [通用密钥库开发概述](huks-overview.md) + - [通用密钥库开发指导](huks-guidelines.md) + - [通用密钥库密码算法规格](huks-appendix.md) - 加密算法库框架 - [加密算法库框架概述](cryptoFramework-overview.md) - [加密算法框架开发指导](cryptoFramework-guidelines.md) diff --git a/zh-cn/application-dev/security/figures/huks_architect.png b/zh-cn/application-dev/security/figures/huks_architect.png new file mode 100644 index 0000000000000000000000000000000000000000..25d98954291e237644b5937308545804499c90f4 GIT binary patch literal 22157 zcmc$`1yq#ZzBfLgh=d?YNb3mFpmYeRw4`(mph$NNNP|jCcZ-yCcY`3E0uCwN-9yZO z&+nXb?|c92+z5(0p3-k509D;A$e1f&Lg?}6~@}n`F#@^|wcFrf;-R04dt0#CwD?VPM zA{9RsQsmCW^Ytn^#>u2Ef`&TFO8HK&Nm82C&qKKAWS%>!<{*hS>-Zy!&(EvQejWSe zI(EI%yQwaXa}z>jAz@*7Bco~8o2ZLZ+8wux?Phb>Z3u)yv+n4V^IYxDLD*%=xbJ7S z@j8fZjfvB?nR|oSCNl&w)Q6(Ej9JAFGlxk$NM{a75xyw0Urg`MuPD3;@fJAwG3qbz z!**NL943vQ&b%#L;WE~qWHVj|`f6qJz9MbY<>NSTYag@_4|GyI;cYeW!TeAa0|NQ( zt%L=Ee8qf40_po){N(gPuP_)A-cy4lgc!F~z&B?bB=x@!L|=COIfc(XNJO^io(^gB zmur!G6Tke9>D_(q%(uMlE^Mb$Uc}(UU}7P*3)2V+AhrtpTsT*0Z60ScrVcCITFCGA zFj5G@`YH^*M~;a!3Gzo5G~(&w+0LF+9o>P3JuClb>^8@AjwP8DWsjT~s!eyyZ+@jm zmu5)J`qF73sDB`EC|v^XXxaIVuF(C}87Z# zZePsR6mD#Ib;ar@e-K;BN*~c_qL}%Kl4P_N5o-5pY3tadg~$!;#uemCEcGn+>WQBC z1?VQkTp>&O+=Yd^4ZRQ=4!#c|=q3+LjArO~R!UoHkHx4_v*$F7Bs@Jit-s=6MBuf& z`iZWnt=Y}SWY{$)AmG7n-W`c5xBEY@mqO;P&mFnaaUtOKdl?KzrMu>8%0@ z1mcbR&qw?J^#xK37zC1B-N=#h+j)(zX?RZGY26>&%TssLz9QOrG^qOeVlcapWxQ}e z)2o-Wuor`r`xS{dx5Hrbk-ptnWo;Ud#%%QhGUmr^0py0v-0-64A@#+6@YSGu{)J3nsY?GeW;aos7Ko@J{ot2=HxIdg0ybx;GJcM2rl z6?UNRsPEZ!p!6;iYD5?{QVf68f+_JGGo0q6$?@QIijO!?qmZ?}TZh`jj>r1vabE}? zQ9AAm*Wb>jTT$7ph56DqCiK%9cZX~$iZC?;K+lhSvN<}<-qbLKNl-k6HMco$ooZ`f zu!?_XhOEz*$%^(>dUDxa^%%v$PTm&gLr9_EA&*L0CNcy$E}JpA!5V`=lrX@2fV{v0 z_dp;dck{qcRWNt|$pzM%*XqGpW3-K%g@MgvQ+fIO3LfDB)skc?qm$;t@=A+p1O@w@ z3J(Mn56%}^WccB~-m76stAUE~yq!(Nsw#W$DcD)=z2MfGXhZY#5O!ZXA<@s*hxmRsYU6(sO=LUFS?H8ZuaFm zlPNyCTrC`q2^1pcbh8lbpq_MX`!;$fu5JbqdUc)0jF@B7>Ms@aqdai@Ic-gD9YA~C zD@&1lGaR|5iV;6?Sx5@gYqvFv&?1kPO}<@(Un|gmeKivq#pT||0bLM%c5hCGQ~lWQ zl{Hg&1u`b7NBnq}c~Q09%h-f*iQCP>F|^=ij=*z@HEzoCrFi|zi~6iH-VEgg1p z710Ow6pCNu9$d>l_{SFxHV9Fc&FpNIrT#wJ7k8<<)t&1F)d@;U3a+*^J5Gw*wd`WY zrQbGo@_V9NZBnoSSE$pN{(NTJo{N6!kZO`J%UlGL^0cKY0ijx8b*DYl{irmB6lRk7 zV!1`xxl7>q_#;FaFCDkV9n{gcP)PLVRiBk-^Y8<+tFw$?>Hz7OfB+ty>W;|A$Nd9L z3S=BinG&5CvNMlz97;UtcH`A>#AhjBVP%mYOM4OvS?)WVzB}ypvg8-a|3LS)WN^+L z;qqvZ+*=X^g%aDdE1_yRT`#UqT~)#71^5H<`Q>ssquaW(l$26m`s@gOlZ}a*y<83r zV#R91mB?=p6YY;j!-E_JpD4JWo;b#m&YU!*QbPKP@*u2KlI7*jPxDO<^3NN11YwGA z7~c!WlIcD0Xj^ukrASfG;Qc1|A<ogW*SBDnGW0h;~B&qmq0RN zqz-jTPubV@J1(WmzZl31Zie@u8(Q)pzbc5{o!kIS;SV8s>aw%XETj|omhpX)LDfa9 z>s_Gh$Z>t8ZZY%?1?k<2S3!w8wTq=>?d+}R<$*G=S?Mc-?>&rANfXnOu}F>b=M85j zW*+L*zxa|Q6mGYBy%4N7b$8d@q_Fj&L4{u6_+J3bK{M-KK3)ka-#Jhdx<2yCrl}Q6 z?nMn>=Cq$$z_6ay(|94f0Os)Q(mvVy)V=Fn(FWq(zZ7kx30?RKhw468w4zvE-2ZEb zEJR%&4r?d^@It|l+~}^JMV61N{e9DYYNR(W@s3AesKh)vTy18-=U)gBXQ)GG?L(hd<8gEl8bMGuZ8tyZb&p zkdaQVIp(uzkHNq$>gwXLx>(htC}HeWGq1lzi@OFsP*n_R2GJ|)VPVukR2uE|7=yeP z*yDAZBV)f%c^qf%uI*4X+45LlY9eba8lM51{d{X0#~NZ2 zJn)CjLeZqMBF$=K-myN*P#u-Q%g3O8(M=nzSm>x9l_Kg_-MIsJwukgj-%vx?lQTSu z$LS%`B>#Ol_)p`+zh9vJjt^P2n#q1L*R|PW1+&fyh%YNfOuWfAdjsK?%)x~WOrb8l zL`s(;gEO4tbj!}XW{+_0Y+{6?VTJ9i`EKLlQ9}7}ea>KmijIfB-!ViQ9j9vU>Z5uD zv$QIgB0KasbaLPVkP0aPxA2Ip)Q7V%zXI&pf~B0OR*?%~L|5bkDl%LS!k<)y5VPh& zSWVOFPo+K;UrK_}oy6wQuN2#)7*U(Oqj7bV%V7?~`bzPNq~zL&e7=8_i? zQqKmTUSj_p%l=FA?LVO3|G7K7UACMHlbGSPF+88_rfxMC_ZI%*L5hq2(1_B>?X@!Z zc#JG5TfUh6JwGfJPt46C!k3eyJe9z_rDRKK5>S}U{ z_j@r4L=n2D;Vj%sxOZx&B~+-+I&Dt_q)&JRpqvN6SNAzp)~jQoXd}i zA$^BUWdy?>25Xf4N^k`{_h{!$P@_R}~*A9>% zovhCWx(;2U23TmftR4_`Umsa%1TW1fF-LuYIC+XCWX%?QVbI>Kv*%5{;Vl3#(o>Yg z3R~?fF+0E*mwE~;iCnHUxz`%@r*O&#i*t7^zYmg%IU!N0LY4OaOcmTl|>k2Yx}!3X|^@AlMNg5{d^WMypgfxe5cFxy(?`EAFLo+ z(FWQuQIDm-9756eY;I?*oP`!R87lu^i~n=$J`Ed_c$WFf4{Q<{i3Z1#SuuC4bD1Q~aOZpbh5~Z7x;hN1 z-tXv+Q_z1^j;pzc-tRoMotyUt>UY_-9p79|Ae?8-JgXEpM4hg!R#=V4YiWR495lN z;Gd9#f8X@~B|5Q(@G{#4gKq)DEO|kFz8oKb-^A(UbQu7GKb`{a_4#AftkPhPYj69< zSY9RGzJC^iXLVT*$~8LmT+b`eg;b9e-vX5(tF+kDs4*ZJh!$wV^ef`gE_j*PJ6I3! zfLT{^mkow&t>4wnfs;J@oX34q8m4F5h_XhrA$I@8{>Ngo4Pyf8773urA)5PRK(zQz z*w24|p;gzP*(<+eWFxHk8NE$NQKwJ_*M{SL#pS}`4o&UGLeiO=^P`Ha9Xedymz>5sX#QPm7UH8but{@%SVJmeVC*7zL)0lRWwNE zjLIkY0CM0bJA*@*kdi($z83yvZe?)j%pdc7O z7|;}U1Up&#Vu>{VYvmeEmeht1wK*9xA=v3(k)PkB82>5>Jg`xp|ZC*8>t2jdX z>MPfyH{>z?jXZUncJ;EoJx`kAgQXQ3g?iJ=lRp&IT*&q)WfZBn@Uaz!DiAT1e_4{= z&m7IWxA`U27O4emx%}!F9b-!3VF)Pb`+Bj)l`co|90X%}_^6JdKxt)l&srgQV8d~s zoCdKV2m}QJIBI|`5nE%(3ty7Cq)~BcnfBz;Zr$+}7yp)XG`{89!`5{(kVbxg)B4Di|qJ>IXc&wh%ETx^x^HXMeI`^x1O6j;_US+!959F^PSXqDzIxruIl zlxtDiCrMHcg%mPni1zEs2~0453sf?Haoh_P>iLHA$YDe;5LP%)!^qtoooAXExuzy( zoKr7VJ*tznB{&_PW6{;!ovz}mb#MEPR-w8TOG3|TPtVTz+0XV}vp(p`>>e{+C{6R3 zqNA)``LmOHG8*M-@r!<${Fs^ZlG(o+TI{nXo;1DMfUm@7go6diC=dVmY6SB6mT?k>J&D2@?&9z z`%h;nZxHytN;gKdl?iD=`Uds}JsBJ1bO2))|DkErzq8d>rEoyGALRy>Fl6L7xSZ`R(uM zARa|U;NLzky*w`((M6dAc1JRTMg27e3?Y5w3PX3z8#`yjv-~pIHlzB2 zSUiLD#7#ogTj7alx(f&?Bk@LIp1t3z^lvmUJ?H(eoZL1}e$j&;560KP2n|>Y46_~U z^h41ckr6;XheyI-b$Orq{7*=iXuR;J?P@;E#sh_DG#PBlhd<)4ek|4w<@{iJwpF9E zsSPnm{r`#YfB~BIl`hUv(gMhU+t<}W)>x}K>n2$fMJ%I^1{8@;_=jOTheO&GH zaPDivVH&mbs$x?eoPqVVi0cgh#x(61hpT?9zskP6I5GKOrTG|}i`VB>9Dpbfv^FmG zgVKBjoG~+lqNI@T86Fqqab8Qxh4}$*zX|@As0c03tFE3B7sJm1uTZVtydDl&PW?gs zn&L+2bXUrwq}j%W7qY%~3KloN@`4UsdDfhxsXwz4Hpe}#u4-_oaB5m=VY!fjp;7sg zRzS(m5{dcgz=%5WQYVi4*2k+G@PwYmJJeS`221%FPxpn!;oYXAlU+E2F2)e1ATTt^ zQ4^_ijFs3(mM`<{s_If+EmWReEB%#8gNyHdqA*leU{EOMxOzpY45$3N8d1~UunQ+Y zd7nwPT9p)x7q8L8=IAnYLEeK>5ZT`DaIGSuvV3A1#5uJUMYEjC^+8-*4sN%K6NW=t zh0PT$4sVdeF%jeSXl)Ahtas_kF5YA|!@p@&e)a{@hl>{6O}eVa>EZSFT;DXQU=Xz* z{fyzhMOBNTaa^@VYAEAeLT4TJB`EcJPu^=UKEOuhWt_I!jUk}2w>;;Ytf>0RWH;(} z$X}%Zx-W*j)M};PT%NM`7~MD%k&THM;&{238$w1p?>Xz^nXx}!l}4{rQXb9UnsD(T zgCv57iC^WkxX)fdX~8o2!M*cmqFjc^U@L}{wPjn>d)1>OjSt&?@>nn~0%6Jx=CJLE zZDIObMEjF>8eKm<<4d<&uB3FG_{@RBJq2*v$XyK-hGxkZWP!tWndfSewu-(>1Vi|d zHzHg2=R_rweWk%Y#gv!&;dA^HP4$+?t}eryWD%vjChzan&8bi|j@ziD*-IZ;9E@U0 z!vGP-n_S!fOi%Lhr$@r3O$g;y6zGS}J0cp_nRrz3R$!o;g#9BQ$DyJ*>}0Saz64Q- zsT?Y`9Gj{*Y1KFwv#t{qmzU&^M{BtmU>?aNYK$6js`YMKIG{cd)HcN}Rx=Ts1`o}JXpFd}2Sqda1k3uq^ zuc%^Zw&2IG-B3;`=D0n_r$)dyni}~)wrZj=ey*le(+RFoW$CO&c@$yS__=bp{g&$m zuVy5J1gL*Gwe1eQ$Fi$gSmA3DEwM-YbUwqwtg?O(X$J7fhnfe>w<1fzeSo*Ztb-Y| zD#Gho%|wVpIl0QDyZWGPeY1|P{N#n-m)8^7AB!S8Up&?LVLZ3MG)}g4+sj@4ty_s` z*+`6iKtKQ(ZK~BnISkV<0JoWYE3q9U=tL7^sjhs^J`)F91WwUU(BNr8MhG1K3r+sN zN2UL=y#Ie*Rd6LcBz_Q?x{I`W$_Q+>5P_E$OZafPEqAK|v)A&tCd!>m&n!@CVlP!cBO@)~bqt5^2E zJ1(!>BdyQ%un6$enz-;)|d5B3Iq`i)t3uuu4t_oz0*Mw8N9x z_+%=|%jKdtfhEpr*6GwJPXDBoMI;X{fj2`G;k2>#Q{l+nKG=F?YQ3p|OX|Jlp00O~Y#1oX2Tn()jdOxPSvNLm<+!9JI(W zE+Nk$WClYe`^_j#*L8m#ucP;4WVB~3*raU9o?x~>!eeQ2k=3+>J0o?ta;rjv-0mVa zi=K0b+GLzkn#vfEaL+_b#aWG09?2`^=@HpD@N4ChsaNYy?t!IawlX#!(cEyQVVs@g zdra?(?uuf9FjH8UF509X>1(|b16^YV!!kDSfcv$Qg_^=T{(gzlXKsn40DNoTuZ_#3 zp9)t%GF-t#x5zm3dSR?r#5H$Z%^-WPc*o=)xd0*urM>Q`{k(CtKCqub9`@@R_8Iga zaUblnp!95p%>Gt4*Am#|v2Hbbyx4r@jTJU{EGvY!l=cP8u5g$BTSO5pGN>OvJ12wQ zxmdJl8w@dclDJ?-(JF)B>RI(};`irmv8p{B9H+xhO`YLP(Hq^EMH`v4fq^=0An(UIFMIz(`01|^(;CtEh=ykAKfYuh^hW3RX6>$!U(qG^x%(N><+&K4L-{?T2zJG{ zZbPKECx7fRS&uujh#Gn-+BrC`_L113kQi|~g4zcR=NUpPGhB7)#$}>Ko{IbG2+tzX z!Q33=$u~;TsOs}{sS5V@xKT~VJrnf{JIuw846O!4_K(S*L$iBMraDxs^Xob91vfg0 z?Yg9}y=*enuo{+3s-Ck}pC_O90CQ}0NW&U7$_@vSLs!UOzf%6f5fPNct83p3PX8Cg^GiOGrU;G5R(oi37YD~@%RQ@Pmu4|d5Y`|VcG`hXAHw^l3X zy*yA%X>0dSf}Bd8W66KJq$jBQ5{qDw5C@Rs51a8o$Jh4X0TL zz804ZLn?&OxD_4ea;VKDeH%6xlj83GQmI9lKuv$^&8%Nao#=;&?r$^Dd$z)LrzbF< zRNElj8oh?SaweO-Cp5LWUs-naUYWL2fo)TzcTU;tpvFD*)5U856fG&g=MPMr2EI>J zAV5%GVfY8&ZyyE*$hz>=KBB66LVX?V^n}xb@bjF-ijdP-sHNpW?ZGj}IE`s;cvo*$ zuDwByrR<0GFIYT!?5h!vrm~yvM0HZr8zfly{Grf!Q&N{Bt~)j*j#>0Ip!Y6q~Vw z$q?n4#l0H8X>`jKo84eTu^eJmoo~`Cxw(S^EZ5eGN%wjDcKu=s67~^vqMA@Bn3x{1 znLEG0GTSi{?k;5QBY8#gG^@0#aDKlD!$Mz!dz4Y0sd(mVG1u7kMp0iS_Qix2UXO4> zCAK#67tSG}bwpcWkE$|bYSWKEt#_P8NcRkR3%pS0BWLX6`7I-WfnqGE|V?m9J2|dTLdP77c@P4#Dv_tvK=I2M}VlALVS~`hmjDc8e29teW+~}TZ z-x5Jo0#q38diSQjV{GINPSSAv3m*f!h@V3$?+8z(7CW78FfhQQYP21Va z_1WzfU;3XI7q6$*GlbE7W;S?DP9l6|3}C)K8RA|7gFWBKtHc_c(}NTgK#`aHq$ldpwT0L7j8pR$NGJ$8$`8=y%;s9WF@B^qB!z zb=a^vEO38F?|jQ?@x7=teF%m>)u46790eSfT(29;$rl8rQF?fdNxZ@rCP(n zd=~3>qkaTQXpp&OM@BEskk=emF0rgWQF~Y@HlJ_O1}oC~5E&0b?$az?w~{wU@N;#K zmNz5Rx-)0_Jsy#{7-Jg^=A`!07TpQ-yH91A%?uK2`-Be(P<)g`t z(Kx19M*M~oxi}_{Lqf)5sahxh08z$y(LQCjuU>zAH>~XDY+AHS9&>GTs;?YpQyz|I zzDowX@uOmQuDe=OP|^5l@h@HSG}jJ|{D|Z{@>I4?rStwhxR<2j{u49811DJ>5Wl{! zesF-7!cXzD!neGo4jJ%S}4$;LBCl_44=p%TbWrk6(5}C0K&GY_LGGK^C&m6xqERj<=;OrY$Na4|1v2 z$juSaweNxjbErzJEe}Yn8P_eeb=a_<_?h*AEVid9HHc-Id*jVCLIU^nUYNGPx1U9+ zvcSHtefb_~yU5Im$&Wy|N;E)?i2y?-t~!loydjK_V!Mekks*ICm6B+W0 zF}0E(ysyez>sCQgw;$@s+nVF@GG6wkBxdZhU?HJgei%8FDY8K z7EK8EN0wLLj;LjTuBVAf=@H8MJ(WpLm)?bFo9LIT?F@?SRoSBOMVv8M%w2yv*&Y=6 zn_&bWbZ-Ts4&*5dC$sC5qBJl-qxWis;T-OX8Pc71|R@@@c#cPBq`f?py? z=OOAn;BYSzU1mg2{O3?_HkqHQ*j(?RZrylPm>-Y#*R}5RBL>uF7%B@+5}zyDc&w7@z}ezQ zrchU_yS#NL7oG+1M-HEXF!GR1cw8IxM;KKAnl|w1^zzWK@K{^pnKLkbyek|RH@xar zuQ@b4_I;{R7gmK9FU!E^pqjq>pVW(HcW`;6F1A(-O$qV=23OjDfml7{Sh-bbk)Q(XL`Y^S!1bd5X3JMG5H=qLlT47SuNEV_ezISk#dsCNVR!>c z%@@sHu3$&vpb^+lM2UHwru%Y*G1H(C@`?F2VT0gv&a*b6Qx(yptDpI>gQb8sXl0CBRqMS96kApU0PR1Tc3Df7J8qgT6k=LfInEtZ2`N@P6fUf5+Z!{!!V9j3kn544l1tf z3VU0zfQ|zB`YcPH9?G3RLV+&ECNZUoo2a%9Qu!d>v3l2tuOr#Y$pRk8<yGQ22~YX>u;=y9~@oze68yEbM;Vl{<-^KRPVDR zNt34QC|eS(t2ht<0bvA8mi;qFthL8$Ue=FqvriomnEtUsWUmboIEI#zuKk6_-8smiKuOxcM7!0tZlIc@??D-1!iL zCh%nC-?syQvNxSAWQN4)+T-+t+*B{%RkCiFJ2YMl2nCa}X;+vIO9Z#g3b>v4qwku1 zo5E~BifB4q+U@KVUy2j+_vCS8WN<#tvDXOh65S{suj7Vz_n<4OI}YVw)knXM-sJ!sb{w zzHE>{5ocq*Qlq%o&=?QyLX_`%aSv=#*^>*o~(s0kjxd`BBudfif)%0n9Z4tDGG5%8dU4P z|2wJ$da?a0>81asbF{uI#B1@rXs4Y+UYENKrg~^>zOuEJ^78{ZCqsJ(yJ~bF!NR=! z;{py#9M`dTn;d4AY0rfU@o@evql`-Ex)J296y~>l`D^Uf`pXw-y`)5n{yuiD+r^6$ zW-y7w+R8u1gM>6=bwN)B)y!BYGnnU2NUT^|1*nC$gk3XHerh21DxtCn>qXFH<06sX zph*!>(xLBaPG~{Wub)!P35aP~?o)Azd4|Xk#i~q#ny*EZ73{Qm`hx8+$w(a@a_+L=TCkOqhbWIwa?4*G6MOl z?VpoD(0Y2W$B;v`Vv;>yxytu?+sCtB3r7b&0$|Ju(3UZJTct-{i~RUHR|B}+{LII> z=)x19+9Jx9X+sG(BtB?Pr6JemFIZmd)?<=4O>_12QF*^e_cO7ozvT4o(!s|)-`7g} zU2{Ro6-eNE!uMWhGqMhLau#NBl!7bgCfBmO!>OjOc#IPUnDmcfF#y?YX!j-!DPyL9 z50FYq7A}cSBdwN98YktZx1O}6`8h5lWB8TxaaF&&MxmKutw=6W%)_UG%m^bt8QMd) zpozLNL1caZUuwm$ffYW#y@YsXaYOa9yA{69Q^enehxFe@d*=%_cX17=aGv8}WRjv? ziNe0g8xiMzdKgYgLDS_rJKA~+z@>q5%6DHFHDB_X%^Pc}Hc`UhMeP9mz^ULb-<)nQF%&$B^b0fPx;I zb#eu8xMKV8KB~|WAFr6(!0?X>;gC}doVF-QMTynMOL>;j$=JB18_vZ<*1_v({GN;D|hov|%DvguTDeOe!B#b=J84+;hO@+texbSXSo_ zB3tJU-iz13DDuYYRzh$-96LvT-1AUbUOH?Ip`Y5+>$92GWUa4b(W$D>yMr9w6QdtC zTj@3-nBHL0)44040~D874(MOPOcHKgw2fwUzK|!Wdo*}DjTG9{cly6+-V5A%2m_d^ zPqs61$3SKw8%-~F&CRHeyCuak0s_`9GBfQSj?P5Gc=ujarrW6k@4;JM{DAuE9UG%a zE!w#Q4|Kw&towd2vWZ>)t($GX$kh&zPDEsx1JH<8nrWDy9!eJeUHp8{8@D;6F^sx7kOv1iYL#lye?~FSI&Hz!TLvL zJXuUbSks62nC9eZU~)999fqtFli(0i8w366S+i2g96z)2Z_lsxKeaV9mc&{m-i!Im zqs25Q^5zyni2+fbY*zG>FugC|o(D<;gJk~Uk|;Q*lb@?rO^?_d4-!ChbBJZ-=d4g zu&DSe2BSdVcM6;wq#e-XqYBq|x=C>KX&xl0_$Hun@6%>{&CNPsKS}~!x!arRke!HV zon_+G_w=b+5Kz$OfbP;)i%}kM`sZl%OWJFa)ZaAfzl)5w%|bwTwzw~xZtW3QVLLo# zNjDj%>AFjvRHHD~6wzbuFFsMpIX1&H#&k#>!EzlLQdxRnf)ApP_xTUakwRVHxbV+_ zn^~4pyNZi%^%$g;6{(Go8`t@{O`Elf!@)Au z-0_rwlDHosAroq*@=Ati80OGl=pwSwNqsL`$=I|Vk3>B^!J8er z8xnRu57_c=&j{lsoYmDi0BAm)1=iB(ETtER<^Dp4^V|izQ1%AoJ6enX7UXX+m@JY~ zvA=~VKYiZiZ-mj}4hWR-L`uvUSfckD-MS;`_48)Gy%h$4_!YRKpPvW)^5KADS?0a%}>dqjmP3l zRWMpEI|SAE3H6%~DT}F209L8fh6bmaxvVF|##OM>vCWk995vE<>VPwR27;fNll?Ua zE(lUKM(c1{#q*2VexZ4a>YN_8EBB>mWy@Exh`vpEHaAypHe515N+1wG7)=f$N%d-< z*@XBV|M>{w31nzwrK^%Ph$~0YW+f>02u;IY80rV?+iMi13m=CbgK-(|A^@*scW0oRD#RUCcUT0)MV`${fZCHqnXy3KW7 ztgEAGeNXi&SH{%<#j7lmrbH9(NsBV!^SEkMVy!)5#mZUV>D-96ej!6!qvFry{HfPv zv1r3_`E~nXZ7OPLgm8S4cBl?|=Py#ewJg@`nz=EzlYcRZyhqA4I`XsVYz_S`Q+y!) zzBp%>cXXB3qOjTSSkw$Qk&JHo$k-rZ(?MS-?d1pC31AtS!|>jX#}Ut#vG2CJU7yc5 z&()jt-_efa`Wv}EI!Mkh6m?bJeDyRiE6%g#SV~bHaBV_&gWRYg@^T4EMzh+Y%1%X=i-p}5T~77olvQ-w zrr;=5dMh$n*%_SIvI-t@$ROq}pGGX)VXeybRGkFA*AyCXp83xIcceQ=_mkdR6W z9`<^9ySYj5`8UwKm-X}L?5^Us{AP=`sYQ=m8FD;Js(T)Iy#UE?2E_=*PYkRsJiQD* zGI8eKd7au;jq!J67@L?r?Tws(Jx3q7vI!Z8h}pXWyZt(t1bs1p>|kB%XzBE4c&7d8|8;7hdM~Nj zP9bN|=(_5m064kYIjrB{ijd+ML)RrG;*_u}ss~6w?N``$aCqs&&&JiFM{)x@rBjCH z1e`AWvCYoL5={HxOHZz|iGXA5{Xr6EFBx*#UvTC-PtD+tLDC6EbW%Y{48az|RcZ@3 zRJS2Tw)ollJ< zPx*S&rgBSnHvjojq1gj=czsqu&iCr?QYL08+${9)+I#*~7!R|krpszjC(gXo`iAGj zezS2q^^%*e+|i+Fo7F9B;a;4DMb}(@#e;aYvh+?qdS^kxOmLQJjtaA-LZ(etxYR); zg1n5TH)*S;*0vG^TDos^+9Tt3R+c!Yo56CbIl0$tARkc5;cTQueY`AXZk$IU@ z27BHS%*Ik)%wipNa@;MDTP&FPnPA6^C1U4yY0vd=eYN{3d^!A}vG{5`W#al|z`~2t z<%8NzdvVkK+WPAE>ZvF0+y?Tug@G@*;IPba^T~3hQ9(aQWfc4*a@k{$H$`&$#J^b( z8Z7o%KXlE7hPZbNd-~C+ z-?k>7iX09$Px}VKwGt%NKzd$ZPeMHQCTX>C#6g=nL;O0sQbmaDmN#Lg!X4JOdP2am2}m*cd*%|q zeMS;M{+B;?|MG3K+8EL1YM8y--|bL)kFg&@qULa4$9Siu+ilb04;d#%3ZIyY4olTo!#xtGXX5Q-0v|IU2){6TU z{hO+cFubv9vYE4lhJKJS0e_?ta3MiyQBK1t^co+!0L)xrxYu4$ltjG*-5*!S-R*iz zVe%eNjEP{CR0Wrpdmvd!W$O>x^Ofg}uR>dlskzn~&PJVB-ne30#0s_EEQIOlp z30plcHoq$`JE}iu;?%kQ7VLcre`TfesQ$ViYK}gj3LkOVj@P(VfVG^OX@y2qiSL~8 zran~#rFxbFlK3WC?lF`kX5Jq-$(NE^Z&e4XO1uAN2T4uDo@Y9e`qvm?tKC}T2rgHHm zT_s8rCJ)$7Fz0xNi&wrN_G=G0sNyJs5yu&JHn>T4 zsG6yJGJ@oUO7`5TF<1i818S0PVxow2k5sv+T#o5yzzm)U3IIm`*W@3o=KHa~``U16i&<#H zcfT}{TE9%Oohy@>B#f1>id^QP{eo2-x#P?mNG0m|3OBBKcIvqIz@`&*N91hjMl>`;qU=mz4CjPsJjyT*h+%ZBTDj{^wFDJ_fKb=@`ZkbW4a}QU777(Dkmq4=fWKW-ZHqm zghhxP(Hi#20!~5%1(yY3G_}5t^E3x%@$AE`$?HrvsYwp@>Mg?@R5Jf}zL2iHInY4} z&j<^3_k%b$g@7&}+V1x4eM6TEVtX?@`jx~#{wFa(z|?TXV1e#?~L~s+haXbc?p86d-_{C#odz? zZeKaa<0wp>jI@v(OIk0ebGa|E`w}kMVxENct_svBCWKj3-T9@qx_KeaDbL`)6}r~D z(8?azU-sEH zhXw3)Ql4z?w3fkd)!#Ih-DDAeimJY0CiUeAs8Lo6m)K3+0FFzJdV#dut6tH4CZctR z=is1VaRg;Ssz&J=7Z}j6zp1D$42^M%mhz(s3t6j_NYo=KO&f8zgG#Wjs};Sg zx{lDT!Th9GYX=Afm{Qkm!bxc^<{L~h4y>FwWhGZHmP0FxifRJoDFrQ|@4j1>%39vb zJa{+aSY z8%xf+dgm=1i`74J64Vr#EtDGfHy>#c%_LgrvX`+sNL(N{C&4IJ((nAakL}5=deHUK z2$9Fr&o-jL28H(a>QQL9*`M>M;G-HnFGUxhwB;c8^tlzRs)Sjgx%cz@8V>qjJiWYT z@aoa;o?e<4oe1ViQXzT1L%IDrQHA6uu!1MQh49)m&#W=c(rver57M?2JXC%As7J~8 zE9DV9g01Hgt-5^7-s(JwY&z%+sWJ+Q*@=rb9SN%9J*&-^lUm_XkN4c zf0>=HC{od*bP;8g-o59mi%@zMQxN(~ z{LexiHm=mxtlSrT=aAN-s31)F>e>e{gB&xGTW>}s%6IwPTw(6mZXNDe!mB3#eluEG zP%OvR!OPOqk{8F|>Pgzn*5R|u(xfd<7bEEA=Eb?ENSe*8D$WT_Z`Q$)Uzr9WaSgWa zz28;!j4tx;`j@kQqAsr2eW6f$=h@WRSEM5MNOL21xcXC*XbMy!o)ossI=6ku=skPm zdMeuJlO4iWm%Pw-aB;ypKy9g@wm7>nR9=Zugwsj(%PP04>Vb!h1lw|-qDF|auja4G zN=6PP>#f9=U&5O7@3c*yPJN0)2{Ow;Cz|p0cqH@0KWgAQ^;_f}nj6i`QT&0W=cC%H z;J1f!=Hb*K!o-rHgH!a*W+i5o=V85#vAOp4qazxG0bM&$Ec!M1{a(-ENacvedRHgq zHIogz=7#8sozCH~*FG1@nqJ6;Wnr1`RC`uLIqn)M8@FBei7CSnNgLX|aZl(8oUp@Y zN7F-twr-8Kv1!n{;}(ewRPsa{^Jk(EWPT)BxTTE!B#cC2rL{v}V-V(;4$NhTvK})T zFGJWf9{pBWz~h~?;Bv)IqrhX!Y7~1-kJlFJx*d@h+6nXAmocm_e3(8~Tk%rd9)&QD zR?kV-ytO0dvnaBjN<^FO94i?%bWXLUe7|48+I9JCA+O$*otV8?-p01?!@k~W7V~zx znc5wWf+*CvmHhzB%7#=eTPqA-@K1$v2WD92Oq%xF=F)GgZQlZ|2vr(INKe{Fz z^Ee4GzfB%gb;7N&y}0tnp43h#&&p-edz*oO8z%nIN@h#`(>2Qu@@Cg|BE_y_Or_u* z(#Mp)a>a8oi*$?=s=qqlGtDp0G+{Duf04JqrO@incgT7<2 zGYVRn)nb`%iqt z`sF)Gm&wmMrpqhnb)_UuPEr|WbcW!nTan*F9;MxbjtJ*BYSgdZ z39EDqRh^52hZO6uh>2o|yNcXT#AWNuN>*ojnfr#2zZ!bPpVS931#gYf% zJ86~du5&UJ5HWf?bI$YXm5RN+(7vGpzu9!4OZbwKIrCAlW;tgEtNK2- z<3vr-sC@tS)7_CtijHLRDm69b;m&Io*9E+d2CPSepEm2v-jvc?%w+ZEaea@76#OGY z0M+<%*~6&zB7O+xPvaEn2e%V}2WzY)W9N0ok(pR*8dEIkry*}0`XuPKcDO%T#y$P> z|5bCHaZM##JOtP%E1*&Y*_ar`1u4=r3L+>~N)QOJ2ngoUQCc<>fe;qufr1JuDiY$O zrBM|Hq^Sst^cIjRJxVpO5FilV#C`j5fA8yiAMc!*b7#((^Pm5nxo39!F4aJ-!(b_5 zn&X~PzgKFz->sVrW1SCn)2%zL(21dAG=EZZAW3w)61LGN@?LELiv}b8s64L*y$b@y`Y^ZRGo=>F@%9) z2n2o!>^4DuH3X_5okDqkCwLY+5nz7tWZP4m0~uPXq{zzEg+g3~^Tgci^WnwxyFlN~ ziDkhB8WKtlK%uU{Uyv^(U{uI+VGFQz{l6+OWlDf6+A2afELIk4%p?%Jk$>fosU7mk z4~ z;3?U=aCAQZThxrGJmY1uRl2CWIHe}Mr!BT_`6Ghf4ox;p2D*2xER`4TGz>o72`-*R z9&umPzLSr!Y{HbX;L9{nNeNWC6vW?ty-fDfVO)KX=0E7_mQKdm!$st5fhVQ$Q?f;% zeO2-gtn;{*hx7!*PQjBo66r1wVh3MrjI=HpG|4^MuT0PX!))f73LAxdE(!eYrJlLd zQzf%xko7o6*L?m4Zb0&N`p)dctvif($PqBnNLoQPrEMF{D-8P&Z(<{-Z#9b#VW|*P zC}@1L$hCHvuZPHZIiP=DvxTcYI%J2RZr-6wK#lbU@{I5$GTOk6AO`rjXWMo9XR5B~ z-c``0-qWG$zx1K*%u4UwSE{asI1_C}5JS$|G9WZgm62Ic)wUx`8Hozz-3;Op@y372 zx*|7SfrlCPhQ>z&yVtd;9dwM)pRd}}`0-uT_JvBlS}C6=S{MIn5%Ps(L%6B2kd^t~ z5gdZh0OJvl;ZIfQZPpp#^^yk;U4NT%@=davnN-`tF1^bg9uNUH z65Z`)qf`EmSBZ%_GMCf;xn!`2Rv12PQ}{@T>a2VGO3+^S_^u5sBAqs^7)j*y>QbGJjWZ6qm`t23uXmj|Q0n;l)90pO zrxUNR;fTZK&B`d#s7mJ2p29KX)x>b;3BJ8vE|_#9-+eKy*jOODQR9B}ouhr9ZR&{X zeoEyfmxVldT~ybK4Y6xBKj3mrd|h=-QxIK7?o=T2Qa)%!3nDRCyy8zZw< zSnwYju8DD@d3cFw8a1k~xY3v_#e`l_8`H789H(DP@0_?cPc&)r#-acVE6Oi1(s`4a zlD)BA>2L)Y{0JNj@91czyusVY3c^VaVGn$-FU0oP>Nhm@Q%`HT%;{fD+TBk)LsI&E z+5jR)s?({r^M8S64Tv!?a@wk|4p)UIVDk3+X{-gp{0rxaxiQ1@+JGoun+U@WE$djw zXebDnFCDdJIXy;c05~3 z01(kg!=|evc%cA|Z;GdK1;cCap8%FG>_C9rIc@Fh3YqeEgD8;!9|5>kK zRXAZ+o|vJhd{oOPRsbc6pXcK;%Z&-^(C1(c+N~&)!O`&x_GUFuz3m1VD9r&KY_+RPD-}0FMFTp!sme4nAhrkFk zf(QhX;3C3bx@RZT(0I2-c^wJesfR*52+g_H;cdi!TWfrap`WeC{+u+Vi7= zK9gG@6mXn%;1f5?o{qBI)`Ho{%&}Yy_@4hh-Rg{o-Uq2(?msiZE*URDMfNm}ewT_)X37bjO27$u7lm3%!7_Es>Q45MU`}cp~QH{uTj=*N#(a?0kd;PDZSAIl5r>rh)?jmQ?_eHGblB) z%`Bf@#Ncgn?xA<5feP{7k*auI_Q(g+d1JPj_o0FDJTr1905k8XLT}1vlf5qv#yD-P zJ+WP)&Wf^eiLymK>3%VM?xi0ksZ6y1n{4N0WyLZ-;sWok6?JXp?M8bZW`SCG?6j`b zBpOQGN<|lsjXTumN+RAIb`nE;$OM~9a0PPsM$gDd#kb8!JBN$udCkURLhvo@4~tT8 zpH1U?`GaQd;HSbT>$gAZ84QB2Q&E9s#$ujgswB&d_u(PD0w6wVgmxHr(X9iQmZz_4 zBZr;S7>#f_=NZj`IWOyn_MAHX{boz(eJV*ic=`JG6-v^0UFV>lM%Fi>1pb*oj_9^G zYKv5zw{wz>kK6>Na`l`O(rkj%l#}9C*)(<;Sy|tqJQUaLUWS5=vgtVGog4$eHYj(4 z*lN8M#{P&Xk0a^*s>;)ky~`V`VUbc=WqY<9a#q5Nexf#^+Svtny&bQ#JXGZB($NSq zjAFLhBoV-GSSgX`qpnOhA1lboZA%U|?!=#VY#*HcY*f&c)N%EtRC#bGh>F))yuzF+ zX{D%V;%DQwl3bO&RnYZ-@YPy2w3ZrM~dU6i@_@i?_?0}8V#k<9{hI#~F z+U-slYP?9Z{bwcSaRP^412=2+`Dedw4v3dSaoqLg$@^fCUN2JDrzU%!UuEOj9s;S5 zr|yBCCFT7*%*%6wxDgx~$>S_K>|T7NDs4z2eCG(=F~S7{j-WY$M5Po-H}5trc-#wQ z84+!IPw9GH^W$=u_Ot>zE8teAP90B#I_5oCr|QC;?4;Kry-!EP z*mT0UKNVj!riFNQLEU*&7ChB3#)9+M2@@qR#FL=?SAvU#;Fmd{3^*YV>WFH^nCnLD z^ctdJZw!W+Yg z`OoTHBRLM29${qzWwyU!q9VbLrRi$c8867oBE@v~pu;0vCBaHU*|=1b{U2QmD13@8 zK28ga`EXn7SZB^gJNMg}%Wy%>SoL6hvPVfh^-QMuHg43WSJ0EGaUFru4|r%eOWczg zkO4Bbcu2b({hK|=+OgrI_OoX<@vAi5f7PIZR(63_G9A#n8W?^ML zxgKB1Ca@d{T7YjcP3Pb*#lyqt?&G2aEwEmVD@SmB z1DKaP|5!<2cXUbfK#i(9SQ_Fb6w198S|3{LkJr8_m#^%j&gdt}te<_P`sl~wF52%V zgI#Ib)nzaC6wQ}6g~dn4@`c|;0wdZG1vasqcB?A>!ZB1EAp3hUJJ|E-8aFs_sz!!Q zxm`(1tpf9S@3Yf!PrT;#!|8yjx-T{ zzMJaxc_z;*-B7}z1>XN72YEXQmxv*1VFk`-+PZ!#TL^*u^Va;kBD|;VliKKg-Mh|Y zRgsN`S~rjbyD_2^PtSGrx1^vYM!^dH$Pj_LZ0D+I#o*1T9!=9=&fq)FnIZE+AlMaC z{7MY}_n*J}Swf}ul0oAg$IB@NOTKYXnx!`^PP!r9m^}B4t6_UZ2{IPB08;tNm;67q z4fCuhA@X^oMGahWt}(R4uk|9~-H*C=Gy&=FBV2RFg%b zalPw@DU0tFkg-eaa=qW|%+G30&~Gka!hgcmzlL2^_VxT>v2mY^aqveo%N+No8YK(z zFb!Z~iyamh&I>l&_7}}uG8yJeII2o@z*G-SFZ8K2k z=Dzpm^1&WEeOY4`S3qoiU=V$t;E9{Go2W^Fk+&WH!D~WF)Qpqmj1aoVp=p9Z&~)hb zuH8DRh`G5;Quz9Mm%nNi`(YD+CZj z0kL21tO>=CClBgt1N*U<4uL&Q(31z32ca8vOj`>28z1O|9Kg-QF6{n|1p3x4_QyK$ z$1m2+`b-jC(}CaTr@j{+WC3G1&;s13wiy@ zc-$r!bO)Ql(vH;&#j7h^>|6qojewnh0HoU{{{l$B+aF!NOTpQ4^(Cre`5RRMM>zz5 z48a8Jdoi|jfx)!z6L+2ohwJf8;$6+~sLcy+LZIu}#{)#%fM~x76NmtuIu)qEsX~V1 zT2COQ%;`*r@8!4GBVPSaz{>v{i1|P5`3{-AtDqK=kdubcvrp=>bLSzLZyc!N+GdBsftF~TKO4r+OJd4P>> zOy_Ce&gEmpzaIk!%)@n_C*A;d{+dLVoE3GR9%gi&I{}y8lU$#Sr=Xa?3$&x`={KWQ zzFxNscJSGsxdOr&%n^72XgT`iZNt+&*4Ph=EC4?-0S@_JxLtPOl6R{6GVM##ENjBz zYYtjYOVW00wa3;)Dhlkz)7*GXJr9nT8hSB&kB=Nz4xO#FISXgMR|6~ZqOCe+2A2@^ zeWWlQIp5G)59AnZh5eZ+$2@j{j{L4&^5KracP^ErQ_QESR%2`@Ggm175Ah9$>>%4}OHj~+si&5LNmZ;-0B_aeQLI~03LK5%BvNNvIM zy>Z%)_3%(`MB#GiQ7V1X@SQdclUni3myO&@j1%AQp8stU^Z!fJsLf5bO+ z&mAQg(tN9dU(dp~JlTgrzE(%!1%)C98Wc3{Jc+A|w9d%YwL{IS}uSyp} z)2+OIZUD)iNF4Dtorw(slsfe6l zZCe=O^q^s{g{Nk{scZ;#k_Bt0{}4lyXfQid*jmpC{#J{ zT+!DsN;885uo{*P82WTI%gF(#c$XU*Q%~dV>=UO3!0h$Dy)U*V&P=K@nXj2fQ#9^`Xb|Hq`Ys*_ypS?JxT7R@0SEr9b(4Z0lmazh;d(L;;Kr>u)Kj z|6BghywZ?YMwy9O*BI=vy40=lQf? z)iAYanqVoXjIt`jWH(Zm`v$`xyv8+Ngyy1w@$~3gge+Nu(>kcbDSlw5KdoZclZxE? zz5HBap5Wb(0Oo$%xq7r!={_ocWbJ*h!Wxfrf`mkms4QfEo}L`qlyI^_E-W^2x3q{a zm2qS@TB{g0&&U@gc=9_vTVjm96d{OJj9+co_<8qZf7%Xu(qUCR^~9X7oO1Vf#4Pu7 zqcPBe!>cs;u}PoR^DUH@$r$-ivb~GC^JAaxNK8$V=DAl#8v7?Ma3IRRLu+(B_t5^j zRF0m(ZE@{mhU)h!Va4x<>|M+S@Qu@(;YXjffqSP0JG1;@KcB*^J@1CZOBNc>&}hNT z&d_aUF{dWq*0SIm#AMpC!4Kfp&A|pN=M0>R`}Q?6VoSQw^T@?G%dxht$@ZiAN!R=u zB~Rgv-0VBmgPpES@o(+s?V4F*9T0+Slc?-JdstQ&z&_Es>^MyUkjW8y8e5L8&2Gi_&}L@Eqq$DuK7Ss|i*URo z&0~Fp&4gsyZ-2A15H>Qb&1CZk<|uBv!!9ouv=3+=!3Ix9Jc<|!rWjq>S>};R?}wcs z9>ueF=YUJDBOse82|S^uM^?iKG`N>J;{-f_T+ z=cFsOafglfc8N$lL>xaQTWd@S>5m_F1{;!U*%o%>S>G51Z|%9%dcuuJ_o9BRNJr!> zbFqP!pNdIbOrCBR>|B^?VuUTVVn$5ozj`& zNz)q7Nwz70>LcmZ2LkLS87J65^SSxI`lH*J#Z>+9qa&B($M=;ieLtAysG+*)!xfVk z@w7VL&TZ(1ii^*ViC|yr8S{X|SbJ}r^q=BeF*L;i9!&FS)E0IS>)ag~=NB@gLB@S* zgbLSj+_F2@{lOoTRl1EJllzV?Ybds|H{1yus^xnG{ZyS9-`Eho(eql~FsBmFz}sP} z4JBHc^sx^%EH?9DDh=@H>#+ZO;@~`Vl*wAyMrR(g<55Z)DhjI+q-Fx%pZ!v*BM552 zr6#G4Z1TC^+C?6|P)=G$PP|gqBw+MwI;#9inFx0#DzTvCb>qM@o1H>iX~G*;)`%71 zW}>HPgQY#~E+bQLev?tM13$M1O8fyf@6C#0{K43Ehc#mtqhY})pRWVK!jE^s*6D+& z|1Iopwf#&*Ye+RlR(#J(vr)v-a;UB$Yv-QRbP>Jo%BPrjCHX(uHX+S6OSxpH%G-;L zm1FsTvK!<9PEBkLwWV?3^uF!Tv8AukFi?HO_ah>rNha=xe>eY4=MS&3Yb|B?S}hxE z@Wqeys2Mq@S8Gq)kAueID+^%sLcgzny_-zJ_v!hn?I!@I?kxs-jaAX!7~xo#up?h{ z^1N|?%iY1N#~$#Mj0WUKi^5TjZ+sUuEcs`5MIP}H;T;_Skv83f+3EnZw-7h>Xrb!F zRig156M&++-Zujuey*93SOe~7%;X;oai>R*#w?SfZwg4~e*3gDrN9_1Yf_*;B9m%o z8?=XY4%TAaOX7@3Yw_AcZWV5dco!n=`cjP6UkPewp#`^l#iV-2Gi>kD7zdUD;6TOU z%_MkN)9#ma2gIvU%ZA!j-EtsO)iNC~rCW9|+@`O=#C=THF!U~O`?GOLj*&jifb=hF z1txBAxdNk)bbvmfzOo#VX%q8AZvqp-55E3G4)sH+1ujQN@__jrDS_dJnng*C_kl54 zHm%qXl-=P%HeX7rJ$aXMoB||kbH4@7q4GAtjhk>~dW2a2ft#wBd|zV$VdyJi?TbIf&G))Sf;4)5qy)l!klGr2V%*X&c`&SKv_U?UDj@i*`w z!d6)E)~*165S<#*1A-^06GT|KoKI#*~nE*SgtG~@{Kzvh)O@SJ%*gDlwiYr zeO{+GG1{(inMpOIlDbIo)%0!3G15qy)CRdPO{2Q7|azCQYyaL4qg`3O5=e<0~* z5~T=tXWQ{;i;6&x0!y%54oQ_-XFE*ph8cm#tgrX%j+l%){E6CsolA7-+;o*MhT;*# zIcdOG0EKa{5SnY_pntKo3Ms8~$oe@V z-DL%ip`FLe)KsSj&5I{+$_uBek5}Dj$4jp+Uk&;_3UTc`H^i?qE(M^&eQVE-}m^UVUI1fa~;o$M$%$7NJoHy@~Lu zYxP9r-^^XPcs#%)GOmeAnab#G8N%O0g*bp(T>zO?O7G*FelpSt(INd3U_vaI`eY3 zetx|_+7=#|Y_iAmy`e^=O(fNw&5i^QFBy++Ahe|-+)zf|UcBBe+`tTsV3&r?R!dlI z<%w$xhD>cl)0WWGc|&BeFiTvy=&gjW5_zr+8&Ypw^8Akov&-mlTXckL?%w3K=N`_5 z^9$zvUYwQ+@i29BDU)Uhn(ND$CeAgHDt@?T=r*RWj)}cJSVS?{R*)mm5y4tvB1=x7 zo4^j;{rc$Bxb4%2gN}MsM3gqpIb`_OjuqsnxMwVb()w-vVIhE=?xYE3y7q07u~2sO z;Rb0f7RjTSQ&VKnLLKbr#Kx~n5=P>>(;oK00UzLLnvA!htIQ&BuiY+snd$jh&NlC+ z`6sf7*Sa5*oC}2lOh6;1$gy2N3=LSIfBB(@p#bV z+xf^1Q){JowQcm=Op0YvE`skFK=!8iHsOxEtdG215rUQ{7QUyFj8=zi(FXP({*?s~ zd^>MZS2Alo@O+vL06uqf0Q0b<=+vm%?#qq5$qWFcjyXu)-_RX^_e_|7mwYn19c}(7 zM9f+2_oz}rfFoQFcVy=Y=qCc7StG0 zS;L-HL|&pkm*CIVsD7CBhzf95btPu!9D=j3dk*_>;53u8F+K*U10pGb z;|>~%&P>F6#2gWHqjesk8b?QCz7RCoqET{62DV{C7qIBWD0c5Uiv0seB4-%wM?0BE zZ6&@6@uGXZtG`V*npZ=*CJc%IDrNU73xhyNJDRAJ)vFYKYA$V1S0NgFi)b%m$^tyn zb?`z#4PE!1;dA6ms6wQ(S{8XpGP5fKi->FfZON%o44&ee|2Aum!s~)UF{ci z#`^{8fMYpbMz%PPJDf<_#6d8pP2H`ndk-xHJsPRdXu3?mg*dcSb$srA>gr9RD=mJ6 zY!l$w+#=rWdEM)|ST1_ z{MHGj+T6&YjP#=51)?Cmh*6niuz7Rg>t1xISHSrr`klazt1@H0bM-0Vn$lR!}q)3VuX~}d%k7kijd87#PTSEvwNSf!PzT4QFci0>w?3#X! zWZ=+~{Tda@$F|O&LL85|n7Frnp5d^hVy-8)myrF-$BL5Ew47)Kb+t#{>ZOZl185DT z27r!yTr$qO=-DbhB2mm|Hm#ujh`YJvHurV`Wwe?v6}ywZZRy(baCEub$id-oV(Ida z3Q^;Vj;d>S+7UxZjQ5nc^G|Wg2dwLuMvJ4P+BDW7aVi1PvOGZ0y>@&f-F&UpKkZS{ zOYedSE?ou9)?pegl)>mymQaXFwPmCw^94wIWpgv%{F>#ZD)BsYX%ao*u@Qo^&iuil`QFw%cUyxWJtW)-Q7Ys=P->G z0q%P8#U~M6z=5Lt@kleME`_TMcVpNzf}Ovlf+10 zskzc-c8?Gdu9{GLNr*+x95dKGjqwT#4 zHw`+`LW>j?!fBKy9kte#6uB?WV5eb`b)8OOqf|3D?{w=aFd8|~J3jSu0mj&01I_D+ zw(e~tH9h@x*L<~^F3Dhfidd-mbKHGSa3EsYptk-Y2memQUthMMvnA7g?v8#{KA+<2 zzV9{rD@Z&A#DH;bC)+2kGNa@W2>4imQDeb3H#+i{rI-XA(c*YOiS1v%{ToH&U1i3x z6N`p!>T-7iT_KX)$nReopJr{*`<1kQ`=iJ;YtugW3`k*=@dKI?=iK+TsF1YQ%q?at z=j6iLZ{>LI4I>HePbTCixUl|n-}jd^0MHATx3?y;VsiCIj^dYh8X4_5?mJhR&5`A$ z+{>DUJ|P>6^RuR|grCphfSbm3f3d>SLU=)Ozy!cBT#yXfG40MM{0T{uL$lzf+qa0C zc)MzvVB%2v*GO%t;OrF@aNMfQbBAK|Z-)|Es;q@cT$mBmvxVvp>_u_g(zjXQS| z@}-sl+S`0Q;%G8ImR-P;XfTOS9vup@3eeMKE-k}nQk>)BCvE8!ithtUrq*!@Cp3d~ zD6LDg1SKn6mo44DyuR21clcRZqGdoe1-G|{Hp)n!q*q=E+V=?n@;MUb>$#p&2%ES5 z4{2j|T)J!ZDCh)7cF$CF4j=Heex5ygn_F=?qsWx|z1*3CuaDZ-(3=h&9X*TrCpG6F zJ>Z?f5;#z7lmMSMYcgillyeg>iKqfr&Hxf#iI| zJ&pio&%!lbI|u!i-NLxDU(DDl-%o(YB46w%`K$R_{-y|%vzYTzA=hS>5#dn2Q$zS` z?p(hKh%8BqbYEPHx<;><2JVJ(l#ttI1>ohThz+_EVq1Y!&fO$iTmo$9hgWJxsyp+e z(x*eh`SJkvUg7(4KPKC3L`Ic(w`n$QaI(pN{23=?f1Td~Fmz}`2Q^fP8>!9S&KZ$G zR{<3OToHQY?k>#7(XT~H;k;c$-V2*>6;U}r%Qg({&92@lwe(fhnlaUtJLCj{U%oGK zai`SY_wMD4-WGWy9RBF@y1sv2IpBFs_;NMl7Et6hXyk37*c!Kjm;D;-xD8z8e6o74H% zd^$?Uf)1`22wrqSZ4hSu{}LMB?P``!)DAh) zm9@FeW;XoC_k#mRPpV7MliC%uS^){B{mNX~1+SU56ap`LX}kB9O*`G|5zG64MyTjX zJ@G0h$~O2Hvk^UX4tBB<<0f{t-_Mc&efYBd-^mQKN=>;?!=wi>K$wh|S+T>>A=Y^g zUf+nC_t<@mpr$dbUtOJGl(=$|*^K^qIUfY3TJF}8$IRq4}<7ZD@?u^=w- z#sQXILsZexEAE~%e%E7hBU+22vIB%(w0U#e=ZrFXz?vDT5!;be#z!!BAh;t?-{>}? zt8eWb?j>a23rhmVBOkmNEHb_ntE`N7*;D|`8IItRNN2OH5HHg?q3#$URd)tL54Fae zc-vY3l3@s8DD?<=8T{4YABKAL0~y>ew&ZAUhDtOk^y6aBP<;9vK zC$`k}GPu{aFUgz=Pib%zrrQZ^b0UjHeZacnoT%$ z+UQyG@>SL@BiG3`a=ww~vXm960D%)@O_*O11B*)^68u!RHDA`$lmEn6;8<0=VB_jjKmMC}cE&_rgA_dL2t#ug#*q(ax zk?cgf_S0~I16H|mPaw5j3rb(%w!kpc*}4`HT7tFrRJx&a7EIJ*u+gMFXR`#5C+!)C zJr9~;$T_s6gP;8+_-_l5Cu5e*a;N6R$=3=A)#Q2vLLDXWjgGwvoD1;75+1H}$V6U7xMbv`zvx4sH)^=XYYpt^^181}6(UUB4tj{M6#dG|1F~G$3q@h4Gndx4 zuGzZ|%YTO&7UQCkv7*`Que9PuTG+7_9jS=Kd1lPlmDl|gv4yYWMlxSi=t{k8*RoWp zejD(ww6L6t&iR|oy5K#yjqJSr&1D^t z?0vHt4jVNOdQ__-t_E^?S`I(9mQ=ZP|I4tezi9!#XCORi8O5Kgrl~ME5faYM8Zt}?Wpb8Meb?HnYToU1enM=ur63ZyD zy>{`P_?=K*o8hfDH#*G%4JIxC3Yk~|1azC)Azn@w2E*r8^+@ez zYl4Ag?@zwTHgE{(o5D@){|k9%xZ$t%NSf|^1LC{virqb`ao1WQG4xMC?ufCMgI-pW?c z{xG)3q=R5TT~DqVluey=wB*G51=Txce`|X#Zst-82sI-#M7qk{_c&Y=AL05+hR}Y? zkZ&Z)ej~I3KpfI&^SG50)4W4>K+=3-@4Ss*X7lcwQDi;@al3@ubklmbx<%9Hv+@rv#or}VV1M$VlY zBgo(tlHs+LhAUgDX3>p;iRLlZ&f=@>SE1JHa>k8x^L}b-tVZ17{tN54^Xe42e3~e)V3v~HCt<07(I=) z!C&UpBbx7#YiS-2*O>yUz>*`2Q|sra29r5%hpzJJo~%!Q=?nknQb|T>9anzihuaVt z-vX?=o7R=N$fxZhkmiJW{FILWoG(oYD_i!_FcS-)`!u&luoDCN0Fcez9opMf^nONZ z);={it~+wVB7T6z4UZE~bXW3JgFl`ixhZusr90-@gDbQxpvob$CKGz!fI&ii>e7u?qDy}2)*YOiAy_cbiGLkt$KR)oARk7sP+P zRp#k1|L{C@o03yHqoTS|BNCp0;@H_P@4(}p<-XeuWM$3w77q0+KFu={UMaI0Zp`Wmok)sQP>1v4~cmbxRynERkQagDsXO!Ioy z&X&1QtQOVfu%$nA1Cm3bS}_!hR#OYv#wxrf0@HP0(&gnwHJXxmM|LI#MOhMaIT`n= zuglBbAx43(=`*eZcbEc)_W>hzimrbjp6Y1+$Ldw3+#NybV~!X_%*PXM@tRDGW)0Kb zh^vu6BVZccW&VBPIA7hYy-@~*f|^M)XI`4$GLIiPz!pjb-xmtlJ^Xg5W9o$ZCd7)u zwF&Bn{ppu+fU51z(C)(yzzgw|%_MHlXZ;*)qaX5B19MIOqK)c58~dP zDL~nPWzRS`Dlm%VT}B4#wB24b+9v)Otmr<}rtc0{0%|16W4Gd7@MtD(57gN!Cff5T za@HH)#r8pa(COzD^2>m>WSX%+dpeq# zQdG7h%(@MBvDQ-g8s_OZ{Pwa-KoQ8**L%Po`07!NBL3Anus8*ONzI1S z7FfQ>IR|FY5$6KnUb@VnP^(b^sY?x|G{%t+W^(zxyAS-!M|{|~Wi@u2f=ezmZaMkUzSzE;znL}*W+0-{TJZr*-0gBkJ=Hph{Y(yk%rAV%g)&8kvu(~Ra{9ICpspYy)NxF^HBHR&@m^$OdeA=@zt8a>yGy(NKN9}y zcE{CCCDU2m_SkjY%4xV#=wqz|$Q{5axb4a7p7|uMJt?=Y?Ecp$UJq^n(vi9q|MIp_ zIdGlrxjzI1rM`+t#X7-6e}O5UoKG%|l9JnLFEhUMF;-%kd-FPJ^KNG?X|8|t$QKa8 z=8Z(luG<|`U-#(%Z03+)Xi^2vmW57l)x$#yvOHcYlAt;WKSqS||A%VDeJG8?VW$qq zEuek`Hlwo@6YR8t`l^A9UL}`_L#7n(ow|O5y;o?a? z9rFzPUmK-?&<96NU`3Iw8kSi;G(AdWP!s6J9A`$%etwtkgGG*WX+tKtcQ`%L ztFQkM2oNVM@EbN%V5E}t2f10L8CwCT&pNw0_E*eiu*p>F0Jk^XIoACEz^D$(^57i-sI^%Q89^f{d>N1(OyKOS>m} zp|*22~{@}mQ{v*{x_ZJGz zSDJwdmm%{3K&)GznS<0Efw&PH)4P}UpX`KJo4sqnx?wWbO$>Z0uf)FxmQB#n zaSvwMqS9-B(?vhH459G7yVSa!JO0`QShZ(x2dt>dTHb1w8G{4j!FB7wy)SeG&l9Dz z!>I`w zIA7^J@8!1!3=RD>3o*BVGQnmGF}fDBJyXc{5FgIzUp#e}0SHHl*c2~UR;nb+T3&O% z)=c}p&0<(XR!Vnnu(}m{cIzVmBz7XQbspG+a!Es#@GJgXf(#ys1$AYO2|{f57B_Q@ z=FFeO@-HT4qV9~B@dH3S*}f^Ed-N5)E#8RAOzL2onKRboE-=QNdR&qZ>c6Dx;aiS7$8frC7aci6*^c}JonVEVid@i1 zfAPWE&(d&i2%4KLm@;60JmE#tcd40dwVUY&*C;DgO zB%#`&kL7Mt;`76%6g~jU@_fuRAyq}w%%58dQvZRB`i{PZG3<(Wa~+XWF%)ubbB+<+ zT+h3dk1t2D7-*48X>UB1`E&$oB$Zqj87K)#V>S_vO{dADtIn@~>srV7J(FN+?mpKf~6BzC>Pj{!Bk zH2VNzv$D7|?l(i+h-=xGP3ZlkEej9eqRcxB5W)1GyVbBK50;l&66dw~D{9GYis^D4 zLmVOhW6c81^07PUTd`ib^c(~oEn>T*Kg^_>9WwcMzhc$E+d`NsaI%cIryS_?uFucU zTS_Rz-a;$bCAZS&W1TNjATpIx&53y=eew_*VY7d5t;%YA2Wc+%+$#$E5tjedExS$ zu=astf&7SQE)Tm4SAGW}U(MPCNK`Zm$#MW~7s5hKexy|f>S!@SD+L!{U%SgUs5uRR z&)B11i(F>i$J!8LnL$r-#ImE(v)B6o*3ej8P+u!aPTXvHK6_{o^Uq=sAOl8-usBDc zL-Ve=F%>u;w>p$m71LQ*N>$0==F_6VVNKou$@b(<4Lu4fccSW;i<f^KAMLLh2oY9Z*ANP=G`sq^=RN-y zN~6mEG|;htA=UtT1w~*MwR~?#Lyiv}q;?FY@uyJrwcI~+o=v8le~LL?jYCHyfn0FI zj3{3dB03r$%G{rko(Chua`E|67xiqeeQwBYB8v#-#CTd^=*l$TEOkmkK)hQwJ(yJT zLj^j?^s;-!?pIO1VrZzw5ml|FoZS3-~e+*@6I7^y41HN^*dPyGb~_L{AmdZHVg zFYAP6&sl)F2La&~<}=aB+Gk4`NBHdH=Fd(X!Flg^{x<2QIq1$vg~cx@H*?~OpWYp- zW-R@%1B8bL!G!+ond1V*UA40FrUm3tqLk2iKHU+tFUH;s`F9EA%OFq!3AsTK3qK7n zFT*I@09qj5rtI`i*-VYMZ7U;SZ~mXC$wZ>MET?Gp!jN!;B=!c;U|DC$Qgm!VfX6Xv)-?_JMRIa`Ele{0ZafNl=^E*Q(&<6 zSkb(t=JOREVI)wWk%c$tR4%u1-clfuM};2uy>W1C>GMc%`*HO4R#NO@Ia*(1%n2#5<-+9#=tr|w z4)jWfK!l66p2>}z{Mo0Pz>zAS0SmLyfxEOTk)O=9J-I}Dm z*Z;`y&%n9OW(OEw2wo?wr0YrM9(C`X#Mpte@@`FxdISGZ!o~CYD7^HG>!(KyLVlf& zxw(ZqvjRV>j@6`BYCoT-(z>IxTni!uA(9@V4=J@+S!cYu@NyF&Wo8o_!5=SAiqd-8 z=?2jdzPEZ$-z+FK(WoM%4|7k)_AZBG#imD?u$#AUPXjLpt zcOPKlSx%jj|IW$S+5)NfBjS>2>PJmp##x$wE^CKsgKt$XWF>v65wFrQ?iWj{`7i_F z20uH#Qu~gUx}umQ#im=UqEElLK!MSoe|6(6#*5 zgFqQ7FiUy3!Z*lWiiwmGS5J;Sz8`-iGptollo>GP&r6^~$M2J+XJPxiL+w+D8y<|Z zx7)U*0)}Kt4`DOh_NtpEo_WglfzEtdy7{r?o4k3(1S3VcyPpyuLvZDpDe)4|svtw> z^dlcbcx4scN<2e?{UiKHdM&+le(~qVVpx1yJYO(>NtwmipPf;hwKdJha>5Ojz7v}1 zrxlM6JUCa3mq_V~?zPsot))e_unnA$J~H{M>`Zp#X9uDgH+H>9c2yi|cZl57!jS>x zpQx9-^wG#_UW4ON+;Mv?RPUtkwN~euJ?q7ltTuj2A&`160>q6K6@xvX z`*cj*!YV!#8RflepsoRlkteXZR*g&VqAelUviJKWa zIz>{PM1xs+5JYu-(ykSyWoxsjQluM=kj%wtq!#jLkV=7^75cpn#YVBw1N3&@KF;!;oOE{eG7p`qNJey2THM ze{8){wrJtS*4&nbSjffbIf$0XTMA zEXbI=IPcY_)vq6Vu>rT6k8-vQ-){K}Bqstd3vAUMTYrzBeVO<3@j)bhJH%e5IuvzG zu_QJ(Rd=TO$4T$zkI%62D=9_#t^vN&W&f?c_l|1vd-Fw6zlvBuL`0=27Nkq>pj7G7 zJ1D((LJf$BbP%LNKza#9CDhQA-g}VHq}LE3^bj~3zcX`xbMBn8ZaMRtbJx23@vg-p zE4*#*{b|p$v$_a53KLY>H#?qj@)kO%e9q9za>qfv4o*E^K=>! z9X@f}SZy2I=isVzL-7p`_nLT&EVa@R|3>MI+hdV+qrw**kiew zDzymjA2+XIm?w-iSl#XB6e@h7877&AzP9z)qG<^fggI0nuLNa|Su5OWd}`ieaOp8G z^2SqPS-T!HY316k8t=QNEcK?3=b={+WA%?1^oZ54PZIW-Wgv$nq}ZR1?uZzTwgmm~ zD%lvHB{wdWpEh;*^}=hYpqqV{xQ}?~5^(U`4pSNw2bYKEdP2T!*dyJD_?=GyS_od9 z#?U9oC3W(x&0~LDmR;=xg_JzrM+@(;z1W@H6tS%MF8M^5%kC1JX%OuavuRbu9o-zu zYZFN{t+C=cKBaMOnG694btz<%j@0SU)@DI(f;x+VK%MaBU)0`|Ibs#&K7HyK1-3ND znkq|?09tpr3M{Nf&*^)TeSgwkw(R^XFNetiGVS@_K`p~*SK+ePD^D|3!gBo^@f2SE zV_c%;E08TlE_nOw)F!%F&Zw{fuZHdmd--aM?B`4;>PeO%EA#iB-!xhwc%!Ges7J0x z6@(n@MC=g|e49(_<1u%mO`l&q(xge97Kh`PV70YXALSlQi_N5ViQvB>s?=G{^==GWRkZp z1KPu5pJkh_y!M3;oS3_$Wm~getwQlN6O*i6{0DQUH`cj>JgyvMDmE%}u9|d3{hZT92Y)}@jDxS)mE@xACOK<+xU(Bv)tjn$KlZphew~R`H7o&&$L$F2nR|bP=NIS z6lAgBddD(+eFI4;iL_S(Yxb7;PYGY!)9h|f3m)89{*N8L8zZUQ34pL@g2u1Yo zk2ZEY`1a|-_TEHLnXe#QM-Bq?IYsMLaHR_+7Y&H{^QMSiCk^fO$S60G3l-%YQ;60( z7uYRGnRR|z@iudBtXcMHml@>>Q1UAJU`VJcc%(eR|AD{S@5Z*F9jW~XbNdgIJ}?}Kv74fnpzi`QXqp|x!fjIP`}VsGgN zXv)uc_EA;sxSL0U9JbHCA`LsDs5=dRIYJ@u3KwI*Z2~P$AhDD0e?e-Y7rH2B%XRyz zg&|CImCXvSk_~ooS`2gj4Il4gu={gvzxv)yX4(H3Gy52 zQw#qZdk+%oYFJX7)OS5Feh7J!124mds@QGI-1gOi?YydETvyeLh#&c)$hi#mnFS3i1@>7oLs*7;SrhG;eJr zwJ54eg|@XIR$z37jv#zXI>&iC3#TPAp?N-Q>0yJ00!%%w#lm9IFq`=|hm_iP;@1z1 zHq%mN-K5?6h~;-xb62TihV~+4MZrS{FJ|QAnqRqjnn8~x6KAcH_mM+M z#~6nIHAXCj9zt?or^qt*HS1KX*l+C`J>D((EAF6e9!0Jn*kvRWXO>V?3}E@1+sG52 z^a{SV>BTIj&_}Pi-Gb?M`UWfDegW0f5ZHYp)ZaG)^rW z&6LF?5BElbw12m~P_m%*Cdai2jR%_(@t!q9cA6<@Zv^GNQz6bpk*{VW>{ca3MkpzK z2~Djf>yymvbGE`)NNwf#muqeGBn6i$HWJOLew3N2?0z!X(%s%b2ql*CbdycaDa2c5 z?A*4zHHVI}qe5gBbal9z?dr1n`!{uZx;I**WwB9cn%ZvI#V*dW-ONSD_+yJac7}@X za(xHw87>&at4l^Xs+pUvJ%W)H&*Y!~G53I#Nti65U6iXK?&QR}674t=gM6*!>&5!* zM)d@iC?&dFVC-ST9-Tp2_{(L{d1I2$w0ci>=~3w>70_IA0yo`gDyUoAtPgLuIo;fB z>%1E3zh15p?a-y}db!%em=kqym>M+4{X+HFFhLn{gu;pD*BX@~yMCD?{N8aY z{B;{0kXNs|yitO)QOen4=J?vX1&4TdTPMa^=F81|&O*l=hvwabjB=@bi`B;!>@)W! zpxCx1sn|k=k9QhUD(hqb^)gNV^3!T_!r&x)SzArt8_+CB-8!eo!76l=ifwd8Lvr62 z-5YEe{DZcy$~fpEc21(sV0CRrZ+bXztVMQ-Hg3eu;~hWUnpmH=6NzOjGCPH|K}DBR zP*bI?jGs6!aFE{jZ36aUGlv3H*|S31OEtY#xl_?=x9U+A#Ouj?;uE2<(M*`|ZP#)XC32ZcirU*#gR{!MWkp3>u%ymWx1`{NXNpKYC>lC)2HdL7LREFPW$USePA5j!^P{nW}wW`#SInu_VvI^&>GrRU6 zxtgJ~I_u!u)va$3S69C!FU}#+x0=T+JEl1rK`3H$CP0=Qopn`sF*}1!=NS7ZeY&%Q5MfqH|lSqN}+s;tVM19=C zZPQ%Q`GB@XTlHDYXIuX^ZKMdYf{W*Nobsdbk+OE(&93M5->5;`)nBMPkI7L&l;q^v zkF;Y4ngfLok$JGuNzMH2*9#wt5w+I`T{Hg{IKCa`mYHj)E-k9H)HG1B%ZR!d zyLYmXJIlsyQOVcdP(B3H0T`UIMds)GCl5t4LPp3&I@dIg)_l?;t&G6bA61yuN*pps z|8sg=I(u0xW5|-IAjd*!W>C)q3Lmm7+-eO{Rt?F${25W7bEzx7m;cD_wrzXtloeS} zo!0;>%i&|(Bpu-XwvdE)FbUXcs+qv_vLWL>Z^DyVV%HJ*`O4CNYY`*Xe`^t*LK`=6 z?`ftCIk4FW+dqY|G__O>yCK6|!I9<`F>RS9Y&*JB92nLRn&27tK1%IJV}>JKZ(v(P|IP(1JX0MWt=kgP_?UqPuxqYk&;F6X7n^UQtWEBd7wr*>VMK0Z# z*tS`VZN)yEIFAr@Nqtv0<)!%86xvrshja!pX1=NmJmZF^oa7G$4=I{UBexm7ZQa>a z^YYa*?@V=+IOOf|Eqj<=SR%30a4cEDmV)ncIq5jEa1E}~iq251^%%e2K0mJHFW1Z7 zqxAcx&EqGD^=uf?z8YQ?#vuKohI@%+#FR*)${#CaVnppW559_wr#apud^20uErB?T z`S%OXdJnlS%KHd$%^&|FU7nD97Ws*485bSlo-mZXtbVu9`JnpVOW{c8tbv%aJG+k@ zF#N+6O+z@7M-RZq_S1IhHEJ^IicXII2oYr%pnHr8mtD`M30C4xxpP%N+_Y!j_PgSK z7;Jr2$B(H{Wp$Qjn6rOxCc$cP92Tnlu8;Ivr14Dx7pkew-*&(x4Ay~-eWYsur-+=7LX)o zX>rT;x{~GkG;IZ*RkK^9^Aa~(hS-G(iMowj);oRq3=G{q@fN4eKLvR5pxk2|`&zq@ ze#`8NO1N^X2zK(v9oNOk5%<63Cy=hm3DezkDhq@JbAZ%NmR$%XKYi}B3vrp$eT!_S zx~YP%Me}8CcV=`H(*=9%ewztsA5UKM{fjlMKi2YQ&$Hw!Vnp=yP(0tgNWW9U{;)&m zWn;0!t-rzkF+5pijlOMRW~p<8G2?{(PR8D7^mp_}?sPL%5;s-+;gNt{MfcGE%q4-)6d*I2Ou~2_s9)64#5s{P9q@Mu{JTfo`{a3O5XQe}X zigiyx+N8h|`OGSL;Enx;=IyyCE9(KtfWQxn>3UC|ST-yC|1VRF`97t_uga@%T_84>O_9>k0?K9Bc@!nQl;i!jdyfmA+vf#u3^emQ0a)Z zozaM<)}4IN_@4TeV)OF^YIU@c+J)6G3Q|4D@gg&=359Bg8Vk4@TKD59bbioRij>l} ztM1#AbDs2PRJAGUU9y&m*SCBgQp(2=tpE(lhPJ@VU{fyzAF$hU? zUEh46FF2k)-VxeiQk?U_aLz!Rg@yRR%;uq4BusIBLE4gFiIjgn;@dLrA=gdH-X?FO zBX!0Yjg9aB2Sje`Zz5MegGr~VSs8wNU9hMaDQ&|5(5T|jvC~TWmL*fYaH91gpdfZ# z>bx8GP$B!)H{b3{shz83Yw3m+sES`cWamruX*lVIC|CVWz%coh-Wqa;3KQJaHKPOz zmP`%|IL&n!V{vi8$Z%AXson6O9l9NrgRRZXTaTGm<% z29=dYcy1F2d219HtBv#wLmsJ?%a1XdY;E#uFy#)oIYNdVtNCb+t|e~ zX-#(#=!)eccrzxiyhxKc)9FzH#IlX8s*tRy#>M~5f=c(v0$AQ~FlSd@Qu5femC5Q%HwtEf2+890ut77H)5Rn*Pd$Lru;4!iUPP zwkO)SKNbP1Le6nyvl4WtU8v_4-%+Ve*>*nKc7*Pu&_rZp0lW9u6W!zwRJ-*k8Ps{= z>!u;SJYkk}Q4wU`6z9iiq0DdN_fKfM?~c(<>+Kc0d=E8q+Yi)4!e6htz-e$Psb3*9waXL$r&~@B{4OEWj<`q5 zn4o7mEUwUNN4TFu)g)wi!8ULjE(ePjQAiJ6;jOa)I8Z}$klOW$eCNuHP<^pGhcOeT zrvYCXKt>H^ZhCnVKY*5VE<+||10uRV3Xy%e&+!^C4i8z57bWKB6*>>6GnP)I)aRm- z2L@d9IGJrEWkVla-L*1k;G6)Vl!)lhTGO;h|rK~hEF5M=f|#`y(3!eHjC<7(W9HTZxw7% zZhLEP@B%;|JZoH?iwke${40UTRQ8vFWJ7ncsl>hCVvV^@R~+4p`B_+)N_=FBDZWN* znkT2y-hQj7wXYz2m7)Djc69y?dN+dW*5X%>X+gxfKU{$jVSXj~j4L#*Odb8@%j9~z zwx%>l=Kkw*reSWeVZp|fYKf?kD7g7k5xu-%dbWMZ?A|whlbllsw?=$-P2;yKP(W*U z;2DO~>!P0y)d=i*KwY|7#n)^389>%mCZNyVvFut>9ax#}mra`knAx&yXb-(L?w~hB zP_fwMAB9M*OiPIa?t7QNAOylWnAKsuohil};?*G|lq+&(>Qt}ORA+e%A$L7yRe1Dm zd@W~UskHY~2`q1TL2Tf3up#ydz_(!?9lq(OY zRQda{OX$|a_u8cim6ia-r_W4oIyKZU0d)ZfOts8K1VHDj%oKFrYto~33sISbQQZEh3FN7Jk=fEfyr9Z zjUQT*D5T9ewa}?n_y&#cv6@9v$Ej_%-q%lnf;iotJ?cY>67b@z$`$g#@;{NX)~>(P z`gF+MJ%W35oWqa}_lUX&;pi@sQXK*(~Ung>+ zJw75kB^dkT^Y``>Rs3$noF44Viyt4&#^vkuj5I z;zMLW4JeHEuPTMcxE5or!ilgUrm(XJf$L5kK7n?usdJz@DM8r#t&Ir>odALx#NJXk!WM zx4R1H6&gH^ljAW^RxvmU;4@JZuk)E{rgZ0O_F0R2mMv3Rqk^^%)zLXQOz$#d zzz0Nq^6S-r^No%2i2A)CeEwHHxJgGW_WNnGPX+}v_*`7lb!uy$-fvdPij*{;yaa^k zUaj2mBA#weEWisxj&v6%6mHF3NG%fGQN|BZ#`+?cT~c;*qg3hy+}4*q4#K9<@)HU+ z_h})aAzov8@jW&^BtQtV)07v!`mNScBqh2d(?lr&J9>>s8+4M0xY2`PW;&Hpv8G{` zm$+SbvnQ!9t~dL0hs3ddMneSe_hMQFNb^104<4Oqh;j{KqyV@sYs*H!Pg*04(5LM@+%vqQSz|}20t=hazz8dyo zw%Dsv&D(>$eAy?TE-t2*$SKG{5qa)y3BbIe<^&nS{RQMtyUoKR4*l-G64XV#O0d*itLih}UiXqWeL=b0Ii_Hw0wg|Y(sulN^G2a8w?vB${L zly*RG-%#mGt`iBlw5Xr(KPY%yH#4NT^qT+Xaa2mQfVA*E*V=cum2Gbm)w1$y{sCIK2g8Q6K_uL9Go#tCMr_>Df-&X@4vv+=K?46gN}yQ zBuxK5un*GtY$~qCOs2FCDpLpYkj7IiK79)RTGQ^legrMl%JsUn0|EsAzUD3@^JS?< zd1>!gwA`?whS?Z^^ElafcXK-U-fl|THLxdAuN8OWU8x10RrQ>l zwNyWH9IVo~aB{vUPkdudkM8Yd06NWpjPD9$e7b`AuJCVjsJ?gJ{{W5S&!F+>zd&Oa zMw>*elL>b%1r4}R*Y?1gOVUzO0Av_oJF62&tm>s?8v8uahL;r*^%s-ICL=0LOZ0z$ zpcwy%|9b6o)JKz%6@gWz3v1d(>f)DIA3sq})W4LBxC>(hBqnsl9r-T+nM#Zz*KEfW zW`wFuEQ^C!5Lc2M+y!?&Da8LV0-`p#&c5_iOrZ-}F@+o;bRYbDQ?$6V=1zw;!A(Ia zfiZA~k6+alYk%1D$V#ky^nrCG8|20^%a;I=Wm1V`PXk8n5W)`fkd@))Bv)$;#4F58 zmgmeDVv)pa%QME>RiagN=|*Nb-PY$+5Vp`O&h2Pt6Ylb%Re$EfGSl2_g&75K9YYX% zX*eerU{XsEtj!HaZJ7)M`3`AIZmlyI3v>-PvMFCtgE%HutfS+ zW}g@FEq18ZvPFozd&MZBX8g^$4q#Oy%I~|-@Ev&qAc9;*@4hmuXKGDspY~cFxO9Me zsNB87oxo%UUc?I4Y>)Mzg8_IMl*x7B4`_D$N6=hvU`q3zym!UIE(^@p`jSKWu6qd~ zA%oW1;vKK@&wtc-n+YOw?AYD(QQty8s*({hlTiKCC2n;f2=ByA*6K%;Ti~8rx#5=| zDm#PtSGgYGdmuW;wtB)?bChZ$%;4sjl>t~-1hs|~GRX=Et9b+^=%?uq}vPDU*HydW2x&}>WdmDZoW zT--3#{ov{aaj@dQPMY5Q#2H^{DN>)vGsfPx+~}lSoxWpGvs_gzoaR_oK!}v)#z{TTV?MR zqrH^I6!u{#b7^Inz5v^nkj%R##TGjN;e*v};LY@)1nhV)dWh9~eQxc)XX@$zvAl{cNqO zX1(zJ0s0LO9NEj6~QlMv8k#Ktlvw>g7RaMdo(Jk;*hztFOa;=_bwCLhnjh zzq<@D*-~Ju`o0MjyZUqoUicNvMAe|x4j$W>0a1`S*zSz>_Z?~cb^U-TX)cI0g4vANuZ3dF&Jt5e}amOxckIB3VlLZL5Y6NftVT z*QhDaKRXA~Ky~7{SLAQuQqpYy1i|nu%F|SRhR!hUqt;cRm9*^~yx&0?Nn(2VI=1T% zWJ@J_jW}Zmi6#B{ATu*Y{9lLD{@>!mGay1#8fbhv+jzR}csg=&0-hklHhqTQaj@|z zUW5ALFQLyWKs~M|9QOJnw_==eKqPhpDzmyB*<0JQV$dtckk!OfT%%)T>gjP~)me<` z-$G6IW_$^usFLk14zVMEpYx_@h{ix4o~F@fwVErh&N&60A?O}}r?V7bmMKmTfQPgSZ-@D$8Q62TQ> zWVf=@F%sA=$>55%%pv?MeZqY+nl^{Lwh=Ok!04jN_%|wJRNR=t!gEuZnWrxXB-%bM zlUm7$v#fTZg1Ax7I-NQjPk$O^C6Ksf%q!*pJ{Mit=u{GIlW$L8HMgl*T$m~s zen<&4Ceu!b9YM0lH(n|*pUHgY9IOYSuca`UaVB9}$u%*5v6^e`sw=3cKa+@4!}Lmv<&A(9qf zZfL&oWmBPIq$&KTa2*4zAGT_2ZJFsIr~N1~CNjWl#rO#9kTeOH_3r=@khkhftN;r| z=u5d#Yyq|L`mkCE9`e|z^i20oZc=kO0VuwT9?gEB=11;a3XhLJ%USvU@&d9gF> z2I`zNpfzCLxRFgw<)-{%hk_ttR|Ykoc&f!h+`hh%%*(-a#YbF9Uyqc9rWQ(YOA7j*jU%`R1=*pXuT}$ zx^5YJ0fh=~xT=Sznp>Ro#ynzduel3_opFX`Db<_#3<_WyS|mf(Us6!!HQl|*L9z2U zh0uu=k!cVfNMPnDF0H|XHk|V;N>!O8@NI4?v=>X4N@_U&O0{U;w;pfa(fcSeTN_iy zUpl>=?p@eIij5NZfHg0UW&}G+_BIcbpGFyy#|)^N zt1sA9LCfvkTYaPA9;-;a+k)BrxY-v)s|(iiL!a-z=a(OlAHIP3$GL{Sd^C8>u<5C# zmyBhtuhX{P3N3PKVeqc2slQm+ZxxAN5v&TrOHAKKezUIBAVZ*8hKwQ z;sbjwaPZM$&TA|q@8)aI93p3%s9UbdS`NC=^GuEEdhr?$=sa=v>78$9YeFfG(!(nG zfDs|LN?hW1!@D}0uhDb!6_?(lt{H8dAGutTi_=WKCzWA_aG-}Q@XNf*`1jKokAhd> z&pO*Fp1s*2o(DOxh~Wq@NHn?Nd9nnO64P;HHlO~KcsF2b1_48RYHART?nLbB;xBgB zEF;Cdb?ak`n(n&+mGw_4kr!h~ETdYgh1Bga``7-0!u*y48Z(~|JkMV` z(s;&)9r#XK!Oino0Md%QcPkm@bagp2{RB7gSO3(cxRtN!ZO@w@cQ5A#f~7gtv$0eoRW-XH4;n0kt+-ec$Lf zD|I|KeJY10uZq{x%TxWB_lOw!W zV<8#EtmJPk%#w@>RA`+SxNZ@(lzu#*6W!5yHCe$UL2N>kh6QUyEw!X=t`EGhN z{W5U*7U~5$m zNuRFsNqI^RM8saz-p?BwNVEaLtGfh{zjZ1~ANjF#UXz!lrg+fqF)M=!ZSvYkp__+$ zHHg~f9Iy(W7MSGFYd^ZHeajf~zzF5w(AO`*A#)TJDgZ*7R_9tp8=WA7H=I%I=qc`d z!w(EY{8{Ey;-=rD=e_epdD|iqq!UKFYC}f2RE+p?w7SE$N2WT<&zxxhW z>DYTumlJZGna~0$K0a*}KUHxtZGTL$D~ZTC)nJZX*zAtFGT}K!JO8Y>t4~~QnXVo2 zEw$-|sUSnJyf-_{b#yK-mpV8aKHchyBb$Ug(zB&%*_lxX8Ox|OA1yuXdkxV6E3g77 zkaWry%@lt1wpoMdr@4n$HzakJc)5U8HW%CVCx1&JiaOx)7+RHZRB4_p);I8=9QW0X zes5^p(;2R3DAjw3au^NDATgqBax1gnVaJ9sAi(MYjYcShOnRIzGo9#{+GI4$s&{(B z)N;WQZCfgKRq&fP6>P6%l(3Yp;b@;XSy2bJR>u}@jmdkqZ9}#pk)cUszmI>krhYhp z5$NfpR8#}43{95C5Pl!~N*ws680HN8dH6r;o4;N471_eQ0L=-2X7Ocuu-X@zt|Q++ zm0GVeqRjAJ@id$puT7%3QfE_t?VEQk@Vt1Y<_)y>CG+KsXS!(#p+kK?#>2G}ZU&w-QDEbC}lWGt?ZxV3p`r~=1rlcFr%q4xaJOR zp*h`VpLqm?G&R!VW5%y}5>VCktYMQvE!+EE55k4TQNud%3jo0)QKj^*z+?0}{X91@ zsoPQF-Z$L3BGYFu3+G5+Zm%2PL)8&`&shSL<2nWn0G>*w$Q%YCCZM5IsMDVehysi$V9zyxTbVT%|`}i z=TN?m&e%)#X;eSILAvb2q0H1m%bo6|g>Gg)5&pL7j0lg3oUTwH*gxFm*Q{BD$jv)( zdp%h`q`!xTO?%~AmF=!s*f_fSdHhae2>N7zZymL@2AKqw2OQD8lHw0n0t$^>#o7AB z2ItW8<7)V42|)goj>_0+3;H4F;J&z4`i9$ihU6ue;SI0^r%}h(nfB3YCw3nXb@1C; z`!EUm$9;+D&H-`H`Tj!OCts0swAtcGZ?)i7lRLS3VgQkDPHJ^C4SiAkqS|eBT6X%m z(x-bS8YPWQ-9k8WwACVGc+s9Mr8jTz(szoAUKX_2Aw3)=l>X@4`RJnOA<)QZIM%?9 zt?0h`K|1Zll`SW&N7O0nZUw+NIN6OteM>i$%^%=O8axHt;iZGLiH#1agm)w18NlyA ziEZrm@-0jkrG0=L6!raVD8V{J_m{zxW97}(0$J!MlH$kxK#|v!2ZpvJ{Gu%hAIA^0O-5Ump_i^`-I7 z88u5{%x7}HPI&1E3p>0~Ul>*#64H$Y#sV9$C!)sG#Q0Eoo##K{e+V&#$Hn~zV z9SKUcme(8_XJ*&gm3B9E=*5-Rf(*_u@yXK_ovXd^{BTfn>KIATyBy~eEydXp ziI3a8@#GCaL&@DnHh1kCYdLB>f^o8mQKb?a52$O8q8VS`FMNHorowY`3 zz4OZ?g~QB!<}pY#y6PYOW&Q+hM#%!Q5S;_}RnnOP>iyC}mXUL<2H2S)Ga1`%_5RP}+uw zE(!&nZtnq}!;Qj5TpkyMD4%SlUE0k*WjgQ zKE7d5WBO1i9h_jj#rJ<|mt+;OOdAca+My#Fph*oXg- z{&tqMJN8ZPZ_ll6Oz6Y1~^@)h= z{{JB7*#`0di4oEN>If(F8D+)4i)>1>6kU962@(M~kH#Z6bjck|pd9m=%64*K<#1+L z|8P9{J&*MxfC%up*NFe4EYK-Fgo&v1Ht5qwbXF?%&ph5KO(5wxv!wx*iq788h%F2U z9oEoT)p=tCPjCGlpg!FZHaVH_)aV?MUL$@0HAZAV$ii$7oR#?r!PnG6sQYrl%7GomAyb5IW1dJ?t>3qO7Q600+kJx9!H?eErM2e$`Pxfa_cv6DP4L%X$Q)I zYHoJy`@s5jTnch71(ew-e-cx;G8knx|9k7J89zcpV_0h%TFr{#XUvg9Xmi=^OXu96 zzZZ>Yp4c5S7)3uZ5d2*(35@g0;N0Sj8y88J^+U~vn}M+|EUKvRcdb>nr>qXc&+809%{0$Yz zK%)C8Qr*bUfr0bZ0aVnaE1>gl4<}dO7^}Skobc(H?==;hI4k@TdP|eSGIX)&oI@Mp!!Pr6InjB|G89#!aq69`IaSc zgX4ROR#p9hw-pDuT?ZnV@#{htqv!}V=W{W+IUArs*Sq2F?~=s8a@HO#QG=)3ObNOI zp{pg9lf67fKjI>LT+<85zeNdA(OVgh`<3CYtMcW?O>7MsUVEY7z2(TI%>tBs+GEsl zM>e5Rb6ZbE66Ed@CV1c2%YzVGhr}-jBq!`evhobr`RFlW5F>%V{*3jSHFX#%UDiEU z`;+*O9RM-A!HGow#0h#mD+VLajTA7Zjh<-GAmXaz9vEfg2mtu=8bmi3D3~a)jdeum^SYc*-QkA|zj!$1X zM#@G7C~zXK%ln{9-%TSbaycFXLq$(>ahkcPiw2OdLSD|+V8;P`2CV(5;pJ%I+w-JA zAlR0k%00AMF_M^U$lsSFBioFKAO)JYS~+U6&?ZnYnfTrAa z`pKx5>T|-(686^i{UH5o)?Cbr96maI{gbp*4)CBK82L30(pUNTjet%H_l!kI_etMb z@zThCpG1c+4DVNcvqblfLZKs1zOv?0$W@*~f^n$z97Rjp?v-dQ)rl%)X(==>4OhOg z^TNbrVNo;3!d(aVT$sx-%aoz+c%+k-)H{9C-EqWOtk5XSOmG{E;3E=!$^3S7T)Np? zc7A);%Y;eP!*MSQ8sPA3*cJLMB&jbCI}g~Ke2uqF5#q*~oQ=;M&MCu6e>sr?5;<}* z6XM0^n<~@ZJn*<5LfPKa)=Cg&k1-i>n{s?mnvn@zC$}7vM8Wg$an5#BIS^-uT9X2n zpYfGrMVX^(6rz8DR6B`Kq|sRIL&H5OTS>xw7{%8*8#w;dU)W>g=JH))r(+Vg4&#B$ z_o$pK+Ll{fW~)v-5CQq8@5A&zP|Gh^g+Ahc|4#Bb*TTC8PEUh`%&xwBm2+9{I=|a0 z>izSZgX+Qujz4GX$8dezHm0tPE&Y~43oatl7pKeiPtxguhMB!8f)=Z z6tN+$OY5Ila*xGcu4?8&r@1{!cV4?Z#a}(ys5m4pjGYl;QigJt zkJC8!j@aTOhx6jp^iH5}zKG-NhtTU(!qNM??fpSxi&4oag<+0mwe`!@CnHn| zr*X|n%)!4*O&%&f!rV+d6SWaDDwjCC&}M<2NtUeFlV{>hXBQKGokFc#q&_h*K|w*` z0d|z$l_&kZNbok z7>hIUarDblyNQZ*A6n!>Z@+5PwTp8srYvR8!AUk3dJ>SiLl`P+I}2R9OeNb>WLjB& z)JfcCC42K>Ugm6PZZoDuj>+DwL7|skOO1`1(?JOxCFZM3TR3yPAm?zCe zPZ_^#pC}|)WOp5QDmN`NAv8-{i^^?N(hg@t`-22rE z#~_-m&hhf5o7>tR!{j$HMC0vlYU203SD;Lk$hgehS5w})m|cdb!Q9njflr^-{SOrT z<8N2Vw;ynaN@Dh8>$0YpVWz)%HN2~tENAT2;>q4(aw4oFczdRwH45PFAz z^j-srw1nOhiX=dQa2|Hw-}%j%IscuR_nqI&`6H9bQ}W!;U9RiA=m7#Tx1W6&;)R$wL7?A3YOsgTebY$O za9h5^H~|wN2sBTyQHA zFTG-(gFr9pRqud6f13RN%e`!-FBs!R=_e&2v_4{&3D9S};Ux;qM^l->lH@zZ-VU+7 zne^R##iD@De@78nv+gBY zZknqInI)vT6x(~C-2Om5ImKt9>5H|Lyn_>d+c&0DXu;PU>bi4NNUH?NPTOqXQk@{Y z`$0nenNo)il9}|m4zpf33>B|1|CG>RbXPD6N}7-u)6=Hp*K$ zQRpEn=IaWYJ|BD8^#{#n+TxEi7eOOZPSWA9gwO2-Fss;)EE`MsqbK%r zOXu$z#P>f9=ZXkuC2hr{PA;P)n^H!w$*8UFF!>dRT0da^N)2T?;}Il;gXrCgxO6Uy z3~rXFJA!Gu)Z_|8=7*eyl4dLYy{*l0XS6k1gb4?8ao zC0W^c^XYjD%O?mTAbEc2O%+wtv^ie|JN|Rp1I`&-F7{NlID%1rn_S>wewr&Sa@br7 zbRiBcKf)c8CR5w2Z}ROIls(!TJzl&7&bu0#jz?F=B#|&re}?6xm8z#0h*06D&KT6H zwWlq@-qgo8J=z)M4j=Svwrvzg^$KW5$y6@Jgl%ppC|SeROMZSYnUNc9MJ$DL3UBTS zvBtEj@0Y^d^TH16J^3YALc~W>gU_auipyfrjTfgvv6r{Ao?Av7#Za{JU4^R7$Dyg3 zC`qIf{wpUIGWs2=>~Et|g8Vpdk+1f7cX#?YCQMZ#AD!mRp3bhNx#}_xZxB4_tr*+U znu$YKG4s}SWGbXh<93;Zl({=?u6rX$n)IZJd-Wq(xJkx+95UQLJtEeHWzhEQLH08% zOe9IUAWmi%+nN+C&4e%5h`qix9QDaeJK#o7vUb@@tb6MX^v~i|>(6nxYgYz+K7R-c ztDVtt(I`yp0G3_-%9Wk`smB&L4nyMx(Js+QEIol5ap6edOOyK8Mx{vjKxbiko3^q0 zlO@C64CqB>M1Y_>$>jWYM$!p`LZrH9Zfz;{vYush(MoA8zqrH_2hVebTZGIf5%*zv ziq(X_YIa)?LpIQ)VR?c=!><9d^yYY4}5*=Swy*qvC zSR6l+sSt_kqaAwuxHXWL7th0iZo~f(s?SUJmUS~Q3Sx6BwjcL?>dgmluR@I_)SI&O z&om0|e_B;=L6ztQah6rR-jtJzQ`wou>$y*EX>zKekT7GO9-bsxY=ix^R)n>0)yqRy zl>P4BYR*y1H#Z*kq5j4Xmfj z;i}Rh>_lqr`v?Nk#~vkm8*;U;qY@pOhu4x*7xmj~fWH$48}&gT*)F2x5*JgQ7*B4p zvBo$L_4coe3LJ4x+(QHmDY>Lx#ZXvkHVtI8vae7$Adup^(A`B{k2S(WH**pBd*w~s z0}T>tq}-ZYqv`yUc7B#!6y|znWSR!L;pb2&3o?SF}BN8L*L7a`IJuH(}Py~OG1Sp8EO();MT*ogUIeN`NCz*)IMbob8>+lYoTsO z3cve>SLeL2`9B4ks^0k)x`YtdE{~6mQps+BEVn`28xI4eA4kq3MXD!PGQW>J2*AoQ zm>QXP)IsYwia6fBoXSzTC3q}aX}|UJ><_Hjh1i3GTyl z9bd#7-erF)LtNdN)lJx{>e-5x2-PMnjO`B~wIhF7nd*4)+>+VF@gOgQ@M$9Zg{ssw z$qg0pDsrq&+5XD=G_D93)qe5`kj}pJ_m!n-Btu@Sge#oQFKq^eoKEKlx6@a0sZ-Ca z7sfTD&KtmOnQj_AEP&~pyJWVwV4>W;-m^VH;|HWejp|BMe9V*k9+N-l^Xb;(*5tSR z<-x3FnGR^GQB1Jl=C{|*U?A4lC1vlIPA<1Fs&U~GrXfr83LoJaZ#?;JGF5jdibNrZ zyx~)D2`bTB=Vq!w%JzqHAs4369UaOpZjuC(I>i}6xJ+XihTG8i_WSNut4b17Wt8@ptivXSlOBcFZbj2$a!xOa^FecT;P6(&g=`$ z{hkJ&t!vU>6M64fWfU_Y_}-~xp!EY<5F$RmqsUE?_b9kSs6UQDQ$Apcan zQ8=`v@crwDe;Q;O6znrCVBCjRyVHEKX0sr|@U1Ga?qntt-^A|22DJD;rM3UxDC!I? zKnJSFoBOASydfu{6j~o?3${p9INIA+Ji?b=*_(XZU&~gYuwpY!TzC)cR>E<(5d;EQ4Q}4XHs%@^P7)PST-`;^cg_kROVasf`Bn%tWf-qaO10~ zK47(kzYkM|E^o^Yo!TD03k!SxIlGB;0xR5AXxqG_7{F)U0;j+|+}9JQ zm52w}Q0wlPk6B-Te)=!m@w_)-FGz~tmkJswH+7f~KpOHOPU{wiNBZrC6pfvve5PT& zmsaM3_XnCPhL0>{#iW~d-4(?%Hyy`(s&Sgd!oE@N;=gejo8Oy=eS>9PyNlG61`80$ zq({(YgWFUUhrt0lXEWb8v4t8f&0Og%{=0U#f0SQa#gdoEq~v|h5)K;d&~I0DQPm@Z zTq#axl@o`%4h(cI0?~)*7M#61RuZG&S|^Lp91USjOQ}Bjv8kq>PTrM+>?Ot zVP^?fL$Tsq#|M=uF6T?S1;9=wJ_dr6xrzfe1r4Xf2D01Ln76De2kQ6TtwUUvYl!Wc z)-V`H$*&Wt7hsD(hbDgUg!d>JX-rDu<(>b9N)YkAE)OrHr!YD+PIN^!HwA#X{WoD} z(%ZN{hSJ}_0Ramulo|JD`qwORG)qXFfo5*%8`%P759i))4QyE{*5C0Rz*VKv`*-Gv zjb`kwj31x7KJlXOJIN{Sp9&YtNabG*ND1SU(#{&iNd0W7PW@JkKS*9fk3)06&pBM5K9 zALkJ{u3tu7>*rv6eF=GIkIc5Ug=8diwldmwZ zoNTipcOwB}tD3CBnFm?}yyQb9zda2%*Jq!B4?hTy@HfaWGpiN*+;v8n0ds`lr+2}4 z%>|h=B}S6B>}A5^+$6ftHs<-`MoRlOvp!DCnV$KN?u^7^qf{_D!M|Jb6N=Kd7Hc)` z6<{J4H40!(CeWof5(p-g{dhrwn1 z(dpd27PhD%^Y`;f2*LP-Ou-L1wcojKXdqPbIO$X`({+QKT1uk`gi#KSDed;0FV&k# z3jUDIa4R(D)rYsN6j^TM>2l9b-m<5!($HWra`~LwMlSUbt5gxp-;p?;WqwX@HY58} z$l|4vfOl9%LA6Z$+HkB2E`rr0f#~BT^`+Fk2{V?`Ww-g*V#(SWDo}xiK$hN!$i0pW zDi&z_1v6Ys{$AwPl55z4D_)0zA+^DtyxJQgxgSCL;ZRz#O z=+u}bZsP_&@4FD~EfqFEKSU07+jewhF|QVzl~;ii42Kq1mt=2$wR*4{rV8lCT2c4d zJ-+}Zk#D5-O%{dgh`fZs?*ta_F~a8N(D1#6?&AxlNy}@F(hzHRjcqn*@XdTG%uS&VYR z**C2f7FD_T74Gh(K8L~T<%J{)n?G&3pxSIn@+=r9O@pgKdZOKor0EQAnsx(L_x}K( zpq65<^s%RPnx?(8TocdQO2Wd(8(v&}{hfz61U^vW3NGS+VsK`Pz3hy!zw1Bj zH*yq0b8Ji@G1+Ed%Bm`ieLZ@iq2b2O#797|k7&y1Tixyb7afiX3Ve5HMLkHBRjY(t(l6V3-11j3L_oK$$;1rqfw(QP55atd=(rxSHI6 zdYkGF*~1A~m=K2L8f1o(+>7A?Wg?^fc1K;}69(kx&i@5H{gdtZl%l4=nJvOw6dwI` z*WDE5Z%xfP0Dz1pYBci;9obf5Qe!CHi2HCmohJpTRtaA^V@!j%H&`*6HDs1*M!i^L z@MCj8Z8VY656>9XtXGx=e^W8m-`7AE#x2OUrP#RS$%B7oM7`6zRAR}DRw#id9SQ=G z!b*SE?by?yJx5JIcKzY+*K3gL(7^L?dU3F3QIx2omsjIy!Rk%>joN|*oLwLLc-EEZ zyBdP!*<0;)XN8n;zmw|VU@RrOPpGJq3lTJ<>Y3+W#2HbUzmYLoxV&v3!Rzizd36np z(G(`SZoqjt)Fxznj-v#dK55jo_q%{m)_jz`xyozhE2&y{8V*^R?!2a~RKDyQ1wz07 zLl{unp+?xx1@4PB{~^vNZs|Z|aFzfDv&{PBR+_$`0)gNSW z6IGmeXKTH@cu+R|KyfR_d#Cg@_zG>(YXq+(T(e>z1!sa`{+pKkx$od88E3FWZT*}D z|8$l9G}*60eEPbFr+Z_m4kBUNe%+wep|3c6X*?{AKU4SOJ2t&No&9(fD-ol(iW-k0Xw{aGrDXO`e>lgpxk?XTMY^)^-#HJS;35eEN0ZWtUO1 z-$rxwl%c4{fO)*lZ9iM%l|#sK!MFgRIuN^b&BB0qg^v?rT!_sKFMG!f%-UdhlKu&H zax{7DODC5QU70ne#U`CJmH+1Fw?BCQs^(O59u4W803sM}U321BV)_`M6%cD#TIHD) z`9JIBf?NGIc$P=}eXdDusLZn#z%WxfxXUI_422h;XjI3&Y%yftaDDz6iTQ1?CW4`$ zlZ%iP6l*1k95@ita@kdd1mK7{iWsnnNb#FI$8-T8UPd;TO=W)N>VVEAglnMVV}P`? zW&E^^xUCDl3}GZ+5>rOmW4sYo1Jm{p){qFHLB)*`!Yh@o{HY{*9s7| zmJF7!I^6iIiW0R8cxER}ly}qnj9jtU8uLoG86%iEHD8)pblbQK1@ct=>m#^JYLtGc zr~<%l>OhwuyeBg9Ec*nO-kiSGb26nY))TVFueY*h)->sFWUk2#J*0B1Xj1=+$Vjzy zrisz5ht+2Q&G;HE(}HqIjYAz&ZK+1_#Bo{xRGT1L(|q(}5`2aWDz7GZ|0mi@ zH(dUI3~U!}Q%Te#YRcb9U7za8(<8%k?1uXm!wi`tdyRlm;MH(6lbl%fyo%m*%4d zBH%H-=nDh}g-GA4aMO3IGKzan-%D&hHJ_0H7!AP52WQy)#=)F=@jv43y(s$%!<3bW zg(V$+Y}=?j1F;}mnYl+ zSxI9WyH;l%+NZ|qMwRkZ_aHJ-(qe-C2M0kX#@zyT!d!wCYgDzt@-a&M$g=hF#b$e7 z{U-hKImoNOvyg=7qsc@L{TgiOX9#~ljZj^P7Xw^~G$VftnWc&MiyEvk6*Jve)#4LO zEK>3rJzg?O$)rnfI=d};zwW`&l8GaR=)Zj}r1P@W+EZ;KXS#+530PG&`Vt4vqu_D+Xp; zbHl*fr1mmUL0iZg59E`Eltq~>D@9P+&E0y0ch}B-nncN*At7*jsJMa& z#QTfd1$NGmdK2ttq-B!_!(U@KQ7)r@83f)H`J9h8Ht<=xezKlUmUY$qXEpc_^*0`+ z$PC1}fX_jq&ITj*wTK;lbP2Xf${sB*yn+s3Mz;fr&aLNqB0n#Jjr9^>{zP`W?yfRd zyp23_QclvH1u!tLy12!Pb?r_?TAx|@HLjDVnMR^F^qrMq_4t>}RV177+(^Zb1!`j( zk>pB|$@;WC3&W9K6r&t|>dFh2mlM2yUZ}nqZ!m#V7`2PHU9P60&Eb;r4qzk;kU|l= z!P1bU`tdJX!6vb^ML=eHUx3`9rqmW+4gn4Xv2oi*R868&oQ!as?o7 z;|Fu%6BIZ4!$`2Ax*QLzE2znopUrF}wfUondt|s5c*dx&VJX5*R3r`1bpb>Z$z_-I@ z34HJ`8M2CCgnASf6ud1pz?)50tv~jy-N`lKOtA-qUNl(m$wmgywcmQD{R=|Q(^9|i zs_j^L*%}0!KbOH+eqQ1{bJS@5=y4|G)xLt^h?V%k4U0kyV&7|FuvIf3p_EyvXO)tV zmpczqxMv;mqHJxi0I0Lt`6w(JmDQ_30!A}mBt?~|OVsGX9pA*|i?dSI>0cBL zK0gUPiwzX6>*pX0z@&v(2OnAKXjJe|>ePfKvm9!7K_oaI+Znp2jK}@ahL!yXD)da) zH?7t*qx&6?{nzQ5{`9{NMroh@wk;Iv>J;z!n%idNrp$b5&oZ4ZZr6KYLla%IWj*8F z30GMEyMvOZQ3+p-QPSir9>6YSO0$I7>S(kRH6JSpV9`BIU?3dpgYHM#?Ki-R7e`&j}n3T0)f1Yc=N-t&|awR+dsm#s*Gx%I#H^WH;n@^k~1b>5!GD zpH+9;C-1LTeY9=T1N^-)u%lFk`9`6zftVQJKc7oR2w|P_gSMD{s&xZupqEP(@i0+U zw-LJWZy~f+0$eVHc29wCt`Qnh22L0p=qb+if|uPZZ^$)N-1W^vS+~`0i&)v6*&6yj z-S5M^1oC`dmq9t9Np}6maZlk*HF7N~;Zi~?yn&Ei+2!H&A2e8vsNlAAa6R<%jod4k z`QT+h*4T57w_GYXe%0-GY`66y$)<}@4pUbR5Vl>%C&D>LP~=s$3(sDRFPc;Q`ultP z9h;{XS{W~$3%V1NcAE)%vghhPMMF81PhL7d{(Ziwn@&B%#q9#Vmos^^^&jBJV=8BH zPeS9S{xL%JG^HY2<_pD4zY=ZuEM^=7>)CJEqvZ)2JxF10$7MYo3#S=$$qZcn#`N3y z&-#f^c-XL3O~LaU-m@t{R%(#9s&tHlMb%IU27K!jmy9sVgQSk2+eM8w3(PM#RkRtd znJ+6bZ(a4P^ft}I@%F6ivT#)>-YhmTcJG4?i&wkT~+z9PkEk(HdPCMN28>D>nNic zf@>S0Y(Mt+Ejq3}eL^6vJsmzTE~k@<*#Al6$7U3;Y`{dj2gcfk7`_N({?ISn%svMx z`GPpN-&ln$8))TVvezV%cFYFiy#_>L@ZSj5TG<7J>Bd1_zez>D`h-Y?1AR7#4mAai z0er64pohR=0Z*U)&D(+sc*Co2uM6p0GM9L`g{b$(w(Gx!xVLP=W#&S%bWaPAQRH+e z=GffiY29ofuSPXbK|0xt$9@Y;7!@~7m=ZD-I62=Su(3tlwsF5FTsA!0=T z;qjXHKvNdMO@iPVeGViL^ncU2@_=dtz4*}n8cqABmal)Xoa$1W_wU`3Gz08i!BfUp ze0Uwl|9gXv9QjJ|5{^IqZ=sb4?B!{4`JZVs{QLHH{XNExl1IRPG&g-<4DZbX+tf}O Y#qPB;j$=E4E(C$plr>?+kIY{EFGn5zivR!s literal 0 HcmV?d00001 diff --git a/zh-cn/application-dev/security/figures/huks_keymaterial_rsa_priv_struct.png b/zh-cn/application-dev/security/figures/huks_keymaterial_rsa_priv_struct.png new file mode 100644 index 0000000000000000000000000000000000000000..f22b578929b98877b1b8adbcb5623dba004269ed GIT binary patch literal 29484 zcmdSBbyQSs_%1w%h)Ros5+dCUAzcEaNOwqg4c!e&IDmjix5Uuh4bt765<`bHLw+0I z_k7=Po$ss@zjOXMvzBXynZ5Ts`oG`PRvhao(NhozgeCD2q6h+^nu0(N zUOs*Ryko+E`U7}+V*Bxv0|5re z>Uj7$#sld^FSm1QuZFAn^Wz(D9cUn4^A`Mm4GX_som>o@%E{LFAv2aSIrV4!c}#B}x3jh?QbfElsQ1*7)uJfGf4P-*AtXDo z$lju1br59W~dT zNc+^2ZD)T(b+4I%xN4@=Nr%R#{W{K7#|f5YYy1S{B$Zh-t`xr2OOr}(@2_x|7 z0h2C|Yjat%eW~U2dbKhn&ERlb?Jaqh|F?p^37L=}NDN(~Eq`4*C~C{#lbQ=RGYd`G zr^Bb4hA&kN*M3}VTT6AAEAbkdls`Gtu1RWQ$sH_`Z^_kqZ;w}fwA>R)S9k0XJ7ii* zTlC%hsUy-;n@Qp9OpsjO+vVKaG-$fW@Ea?QS723#e2y&RlNOV?1pJQM3xi4MgMmOR zs}~}A(e-~gpBdn7?a$KQhO1xDEc^} zrRxzqGS`zK)@CtidU(7>&0HP#Vp?#)WDSbaS2kl=Wig#&m5dw3zX73I$4`8M;7A;; z%SqDCw<G26CiIE^-Du#cEdu z2+P-xzt3rN4z8xtpDG19)P3}*&e!sYI>BGXs%hPaR1Qppz3S^VXX+d+tdo!O5R*i* zXw-Eb6O?L@WMuwgD3(Dmg%8pCyuoaH%x2ui>9IXEK2UZfHjvEE$YL6nY+$2QJt|*T z0bx?8*5d3;>{WMN;rXQtmO(3#s5*g^HaEp3&=qQ|(QwxGW^7s#jbI`VjLpW%&l1TUvJdMTdKrJ{>1ESCtmN1-a#8keT?N7>3n zXHcYDQWWfy(rZ7QDGKIhQlnk2>ftvQiyvomGmW5jTQ$So> z*7jX-QgmZ(RJG@$gQNEu3b{CSEyoYq>B>1_hvSHImyizMw^NXSD@Ikf1&b=vyD}&LOD2CkOvmCbyAU@9fs0F)j)UNva(g zUxh`@%CNnsofHc$rm@QPss^2Q#qinun$0MwyvnwNbN)=m2gF3hULs0X+5$bQ`GgL; z&oz->0;4(HLTZEM%C3XVVlL+(*2sPv{M0MMhNX4qdaFuW43uflInQ+YnLb@&rw zNC7i#*EdCm2E1O{LR*>l@g9C$@g%vl^Su;vKLt>~kK%FS1wFwe9FUd?^>}qC)7u$4LadhIonC0)k+4*TL^?$Hz4M41{ZE2~SXFHNB(o}94p1ov;}{A(xn zyg!siwI2D$iPfDJj^+G%&#@A~l=uk;xylUihHvY$ zRF6ITi@lrsOAn8Bw3J4rj4aI zEeox96?W&qSw}NQHsKTTs0xA_B_Rqz;85vq>ZvHKAbLxdgq|o#YNS_v77>{SWzN?o ze=BU^AzKnku#*guZoSKku?4$yuiUe|C|6p7IG9bVA}ZcKcPI{6H?#O&snWzm=Zl=3 zHCk}=^3C?5CS?;@+$cp1B{~gxiuRAtB)$N+AT=Ty1vkGh{fO!}ks~z( zf@gqKmWty11oeqZc;yZJWpuFQ`-yv0X>hjY@eWgk?QiP)>U5+1JZY(c3R|r?S$j7*NyxX2 z_G{5!-kcGtIbBDb;hqL%piCTJn-#qPDt8$dy0Kzc`*~XmUnef5(-pWml)>+55SDQ# zf?9IJE8RZaJ9pb}`xv}quRsxDEGAUs$*Cu{sL}4)XcpSW;8QVuau-E-QBk8)dWP#xG23jwkR21BjN0ceTu1gv;%{LdHX40@~p9fE} z&S@Z=aN$#sf4H|IUi+!hm7$s=Hosn*_)&~ef_?d)+`loXT;H5Fwd?!BmMhepm1 z=oUp@bJgV3%|%1=l6oVQk&oN4oJuo?9N0)GFXp0j3@YtU>pF2Mo`EsA5(oC2>lgNI zDac(GA5Lp%dZbU==t}po857yp#&Wg2S5jh$foUlTWt)6}bjJ2%=7rzo_fa)Tp!Jn) z|2j~)6K*GUE^QC=l%1e3wv^@~ZrF>`oLLpWusX@g0o&5R%u0gNkg5n+0EI0FN zS;*;-wt& zb`n3S&ruE%iFi}0p&u=2_2kTtw6?3JW+^?Cr$#Kf7~u<#(9H$j9clF) zWxmnDh`3OCyzlQ_#Bum2i7c`hEt>hWq@?=p3GZ7fvO+JI$eF3 zZ0)m^`QQ49Roh1~K>cr9s5{n2Zcx`T+wTOuc}&&$bD~K0Np&b}TI<`lbz<6;B&Bxq zV=a5f{MX+!5Kb(TR1C!#kSK=SE5XBCuEVLfZ=1iMlx$V~fh^DY=t^f<1bdYqT)hl@ zC{n}CRRswN37KKyJi*UQ;+CvSiC5X558^4xe8Ph5-gC)}9T@V$SlWIY+!sq_y^x9h;jRBn*(4bd{0mo2!!9d4T8j4Q-6ml!M zQ~Rb|$zq8-Zzl>0gZkK*Os$x+A9QOKPNQ;egSI2mW_I5@mAYVr4-etz4C{f0b?S#M zE!PiAI4l=}D1yOR49mrL)DQipm{oL(NExW?KV?e0a0zV6haa0a{F2ALu)lNAmzJba zxdK1(-Mfe36a1aMh>dNmcv2g3@iGDEqVkNAg z{LOF9MOWHenruzw_Dg#3Vg2ZU?5{FQ7Z*Y?`){@T*g@wELZp6q4_YQI7Qs)q-=@XR zo9x*$&nU*EqGw(p_%TxIA1*HY-V5; zDhFo?bvs0RIH)qS(2hqRUGlMaJy41X9+JDHhD^SA%A-PQX+OKgZ!v@_g=+()Wkt|x$L26GF7_FWbm8oA2d%Atd*@c!-8tCEj7 z?|bwy0Yd-ne>+c1qWS58d6Uc!tfsZeC{WN|6Ru@}uh&JYMw9TN`=bonvo*L60g zcL%%M;yd}Ypj~~-zNzApJGF8s%DW*!k2A|v&E*@b{fBsXrD@Wqj9Iu2)7Gh@h0hJJ zHF=;FPE*Q_(5l{)=FOG%(k9XsH@{O#lmXA25l<5-~+@cFDYktwxUydv`ZKk-M$d>1qWbY=4 z5rRN^8*{GX-Lyd8OQ}Qs>cDlgin&LeHw=?V!_`Mq@g^xGrq}qD4MXK}Fv|GXzb9aE zk#HXMJam$yXJ-5cPi&PC>a`nrrp6cwphNm%*IdqzClmcd4=kT!YlA?(mDArwSS{5G zLnv2OMqMcum)2!#9w73~YyEK@zI{?mAb7oa&}}7p1F~pI!vKMXOJq{rzHS*n3-AoE zO9^K*I?+Lvgd}x944ndZYUnKS=0~EQKb&cf=-2~#cpo7LJr`C*St)#LN4=)0mSMlp}MjmDwG|%7LsKn{- zF>kGvycJ3e8gdiZP1%Wi-`Xn_)wB7^1l-TnPX*08{_*>UgG)Z_FpG%(QHl27lX5IC z`}yxVtP=G92@-4CJD_Il*_sN*h2B>V9WxEDf4MJpO7cb1?Qjg0DY##jUY%C|Xc`h< z?ZwLAqSUeDaY4iU%#w?9!aCSNQo^*M1)ZPaoTB|I?_uoL&z@0kg<-^Kf#x52M0%tAyqOJ~1{1z9F~yWl|3Xnx~+cCtGanZBa4} zMY`k9bH(#YPd1ku%unL$M;#<+Sm~xNy`M2ku_Y-_o%`ux`HVKQUj)4f;)v`kxCz zAd+)b+zH??>t0QC5H7ieR(2~y*v0O%*>Bf!=mU^u81aBoNgMs2TVoa{0kXp%<-*f4 z+%`-}DgC5!pA5q0>BP5M5CLwynZQtmKfd+(6!r(Q&C%I%%5a_(P;+c`sKBSkE3TMh z0gMojg5ueVmryW`pd@9QC(G!hTDj6QM*5CqZl>6F_*dpwGd))$ZL-IpT`3sRlo(&4 z@bgP-Fn2u*s03^DSGimuSy)2A8$3L+`nSz`g1oU{MUgiSMzXeNUGbVJA6dxg+mqR7 zcr9otM1H1_H<2s@@ll^=C7a42qc#`o6bFOtMvw@PEG<7**34=3TvomP(%|Qdu-+IR zO{N+EGvyG82f=Ezgwiv`pW1EAy>;$ulh;otGi3vr;ih+=M$b zz<;sQWS)?^{}XC|#7)Xi!mmSe`i=Lhb1bDhp)UVr1?LYgiULN`x5>>Cj_&N-z3o>l z&P}JoZl`~cVZ@LX(XfxZ4sM;;;`x%@QBN?diZ^tW7zLDrr9cVt{d_|5K7^NErXL}|89a0PYWrgM z(Iw|I`o@>h1mfm@LZ_YSCYWt&*+CPh(28extML*H82u@kV6&%LAsNopO+dPQxgV|X z&6lqidg9wP^Gfm@KBD^R!jpDVq&;|WKNKP-CMZ8)QK2p+-i!4gjz__xxoo7H$%4Gx zZ9quW?tC}sI(qktf~_ElnlZUSkvCt~I1wS^gywkj1_i`$pOMpAV;j=IkG?1wo?7kf z4vi`eCveE9Lm9a|90|B`*=&!y{)Us10Yze%P?RyQ+Pob20FQq|dFI$M%FULIK~mXB z#b);sRPYKY%f6jC^&A$&+p==K;r>_U$3Ws$3p_ulXZxdC+Pk?y6bZISJ;OXSd@D6b zCq~7EFLWqj^;uF5qI;b~Vjwvupp=r^B`~t2uTc`VX($fzbrKtY3>x;GjWi*oxisv> zuT9j`r;wmj%k->zMiWdayOvZ&LqO%FX{evBD=U|6NJt1ow+f!F&X0YTn(kf|Vk zQ}94YGhK!`_j64G4E!6?EHt;_Z%ymmPW1Kz0wN%g`+dDEm~onZD=z$a==G&{IkYrD zR=|DZ7Mf-<^#CZ7!oDKp&TQk{4^{hrW4&qZA#;9Q0U!^{>TP^Bi$Gpf&@PXzg)`dy zlB-Af4j3`;=*0btO?=rPp?}}U1mXdByu805;PIdQcrGhP0meIf?b-OOL09D+jF*H> zeN5+RSP(MR>&S?@y|mXh^8sV-b9R;LA=S5r?6)tcpM#T;fS z?&P%ngHB#Jx89fTl4C2yak_SjCsWEM#R5fJe*EJcKkf4Er>-PlJl*@;k)CH-I2 z@}a7!Fd`&Ia^z7j+oCdM2dE0;egZ(z|63|oJnjLuv@u_-XxqxC%cLbs{$8DUj5&s==2C6g_bs z2WH*=AfM~*mOo;Lhx4{L$tnGZl&TaIerVX}ph#VAq~0GRzZ~IQQ6_+Esd~g4Ic& zh9CA}9LMq=(#T)$Gs?-tV2>daccwYX#+T%t9yaC0??2sYaZ4bvDqxeB^dVgTp&iNV z-EKh|&w=T==O-qmkW>bGhog`YHWR5l*kKax<7+)HgO3M4SS-N~4|+mhZfe=hz#N;2 zx?yvsUWhkHT<#|vdQQx?8y zFJ64fGnh*#lbWAGE`cu7SWdg~p|_B(R@WOdM(>a-7hYu0e-)#GiXR7 z8Lq3tnBLkxRCY`plDa5fcF8?Z~U;4aN`*RMm))h1@GCZj+&gX3R%l#qVXLNf+L~Fh|Kbr@oXvaFhNpz?!E~kqe8Vf zDVBaUn^LK8-TfoA5SmwVj{Yp6Q&4NBwN!O}Xi}&V2B8PPVwY3=rejMh5Txn?xXjyw zYI)1c><-yZ^x+SmfXsdtmOi@Awpl)QZilq(nHkTYUt@w`bflY&<6g`|c;>}eqde{D z$?Xl%O26~DctP)uGdJio`XzLwSvGPN`d{}TC z@)rvW*jAS1mVFS*==S{OGV3U`*&S*>f$=u|Xg(TSy~ug+kkaOAD<^*RS8!7tx6Ih= z=VhRn0;x$sS^|qFJs+kN_%B|70duu6w$c&-8^IRb!(R>|VxGqQ>2c*Tv5DXd*&*F9 zeQAmoY{TZ?Y{$RC?A1hN@-_9-n+@pf_+EaDENS>j7-9wlrgh% zQ$~R1E!^BJR-L87!kp^GQ~?;l2k8pe_E)d9C{v>T-7~%^ekyJQaI;f zYHl!v>59Qj&c0!tB3ICWWM)f(XQUF_A5T-(UdLFz1UUt&A59GZ&JQ8oKj8;Lc-acz9UO`v{-na$xCtz{dMHap`ihy6HIiprX2(yI!Z{>Wvr{ zfMLMbN3qSFwu^7KhttHCZuVW&R8BICuIFLL-duG!nXvcW?TyJ4 znlt`i1bH=*o$9^qT#v^ix>`jKAdN6CVcp_~2Wl^H4Y0+fr!Or~Uj71*lE*aQb-BGB zx@%Bp;;N@8;TJgHohc8s0pL|FKIE_I0;~jw{r~tN`d&`OP^It>!dG{<*=a-vvS7hG zpLwC@md?y)QKa^p!T-X>&OX!@A-k0k{&QW7!YhRE9dhUnnRD68P9{N$;lM_U53wQI&-QV3qIlV97a6kuNRH%Z zxdXQLNb2oLZ&#{MRf^h!mXxyzLfy+bo5se&z3UC1+w7$SpFh7pTO%>R4S&W`Zz~E5 ztyb%O8?9D34j$LOw@1s&r`o_Mt)l)6Blim=07x@s;f-{-{U<=yx2iD!o$l3$o&SFz zl=gLFIsF`RcB<)YmF(`KKb}MQYG!D>@9x~^&c(!J-DBx$F>4p?tRz%Cw7*ozlIPj6 zolYQ(48v4gi9{lHl*n9wD@wwxL#j|Dz1U#6P^K+VIpC!;p*BpT;gg zzJ-1OpgqgB@wu9-qbBpYJ}mY=&CgPPjawVm$C$UIE*7{R$L#VnD^HBmRwg*+qp01#s!240R?|A3A$}@&`5-Tfj9nVHe z8r%aLs6+e1Ei$(@Z|`!U$j%^-#Tac$i-HW?bLM6hfVZmkw6l(5aoDCSF@?pdB1~A~ z5O4r9UypVt)d-N4a%aqfzGEkR>7A-%9U)tTqa!>7$QKDe;sb5}lOu>|!_X@*o!r#N zazq_Qp9&<~RzmS_wyPdP zSb77k5Y=xY+SII=szXs0W8RIcf1QaWsbmblL3La$a@7H_jXbwuk*f%w?d^IzH1Ewb zP(m&%(C?So5)xXcRKjowSk5HfK%Mw|B$4TLo()6dBbi;&X!>@)YtF>m$55ocN{~qs zb$9|KW2@lddyms-h1JiPm6H{xZ| ztE|$1II%EJhY(1+b41;Z0_J|1{+!0xa?;p)z%yjOQtr|7Fru%=PV22kf_-V)x4b5g zsmZ(a-#QB2!fCHf7hGAr&I6q;$Y%*roSz;CXd*F`fePw0PlU)}9XZHmT=CM@Pfx9{ zc{lD62HRxEvy)xvGIREJ|Mix+@wT6n+R*mc^6oQ0qIAj--2eD8L^V47p{fPtVf_PI ztoUTRA4y}YwP5^9S*0JUv_xQ6U}qh$dp~5NO;F{tMSsd#rSaI@;iBg24>xfz9OtCu zNy5~Fy&83IXMih9=yz+UO*{?LtG^yRxRynfVF$hmwI5&DZV@PD-vb;@dk*BsMv_u`J!55wpK^}I(GU##TXzok^rX-!ww)m z_*ZZO*@R)*fdF)CX6d3_dixy>ta5!#%{agdP_Hh@#O#ERx6s7=DDcSuX9(nSa6tX zvsc5hb8<{A-_~y4QwIs6!^v7XPNsK8=V8OUT4^h6Bk@qTT6|f=(kaL6fNe$|Hm`}? zGxAm0^HC{qWo6|S!R^A*&4Tl!$hUnhrPXX0V1p#Ijo5jP1=#mKN!Hv50E?C+oKC#; zM_!sj^nyxEMdi%ebT}+TwiZT5{Mr%nj4IH%q1nBSVpWEIFUg?XGz#A(U)CL1;&P*o z*zb)o?>lNP$7|HN7n_$o%lSSP=fRu&vYcu2Dksi#yb>kd8_H}0L0OR5<{9q{!-ax{Y>yhT@oVQS1;-MRLwpLR%=Q-cj;LVqPHtvM*X(xK8)0+~(AH;}? zRWxXnKk3edn(hQMa!bzV5`1?Ao~!U!JdnjLB~Q%~`$$1DC@BFoRSf!Clk=n4SteRi z0ZFZL#i__^-ER`qjac(nrxKa@qc`zJ0)`cgXUb)%gs!KksHKFk57ZotzUp~pK)+ms z&uigfp}KXQqZ5TS7lkJ*HG4xlFB#Q!HKA`WI>n`7nmD0bLI5Me0s<-Qp6{OIOPdp? z?YkijDroZ?rEUZIorJ7|{YmU%P`x@Ir_9M(o>GHVqEPk| zjKU5`c36{j&#vnB=fSW-$K-_K24rjDj;Y!5%3R&YkGrSp`)o6Me=^OXT1)jzOhYd0 zrwJz~Xx1rSiJk2+qAKXR(t{a=t*A?PU%t?+?b~<@0vNx@7OK z#y5J&k@S<|`3Y?qC%|fNxv1^;8lgi^-dGgS0#IL*WF_RoOiT)@357vi(OxbAXgsp1 z-mLOj@*lFAZ4%AAkx8?A4Z;H;pJ#cwdnNEj=}d**^#u?OxSn-aj=gZRU^{LJS1(*y zdtzXrS>6?l|Cl?WSobI1my(Lk2a`Zc5w`0mHizX&;7`*e;*GCFJ0bnEROSF#YFZYa23^igFg>Mg-pv6-UzdO9sL+< zo3B_l!7)C%Uf(Xy0QlLIIjq+rpBRWKk;lN=51ER50S_j&%a?A>7lK*EmCs7I7A2G8 z6gXP5GheZ{(RPIP3`C69e}80=uQa~hDUyq)Xr65hV5f|^=g{Zs;x%+48t=))X6n-MvjC__P6`d>y0;zbrzBKxz2e4EU3|eSn zx;*%nR%Ek0s^3RFaXc1Tt1xIecmXszvIuzatO0e#=iYgteU+S9df zI(i0#_oA5)Lzpqaiq~rYx4h(jl6YdBUG1}t54;qT=rS%H9shqhY z9Ag_SObL!pEb|auJGB+cg9148Mn|TyUYUy%q6~ zCaJ#DNe*eXn+NtCF&kUH>!mXpm|-!o#ZYy}n}>XF$@bAC6EfRDAPF9PgFDoU?|rsWCZp$6I?@4?^)n*{zE8nms&xN)nfZ6``HiFH z(n6?vpVb1iF|G=1VK8qoJu4D^k_Xs|zfARQ&IvVe z7d2;C{KORf;7e1YD}RKV$5q}IOE?}$`Dm2td0c5Ns)Ak5^yjamgj}RNK9QRy6((W{ zaoIXKQ?EvZE{AzN)Tfe5=lh0V_mIj3Il<230k#rKicZN+w&_MOUjOtZ!5az+5%ui| zV%3Ro?vFLX^@FzwC51XZH>XDI$WR;4jF}ha@~wM1yE?)C9j}}$Nb;}Mc^FL^YB!@o zfTD;6v4v2XWI+SI891&dB-rCgm|IP}i_KJ8Aez++7vZfgc*VL3d(vxz=*B8Z7-0nZ ztO9p97r(AYV&VfpG1*lykyA|h@U@_gGB2VJkDtw%e<3xQcKa2PP`T}vEvrmGjnZy! zQvIS=e181r%5Wi)GF5HL_N(?Mj`tizwP%e0hZ;bmtjDh=@b?gJ@B`_h?juZaJQA4~ zWdD)hw1z_`he=*WpQZLqIDwk54z02B>2jZTWR1fjDfVjt9u5wN+iPUjuC|2qT$!*9UF^nn2Y|*K(I?wMi^{(=KR>T~-JyFu*L*X_ zbr7o{*QhPBJ(Z2yRcNHB^8wd0et6}LgrSHsnl=hp>{|VcmQr-6m*ms<9Q)4HOXEHi zI`KNp^MoZbLgw6caIv;*bJAFo^f`rWbpX*^!8|fS@Gn>33#v50kKXaUe#HJWb~n|-I@io5ZJ-kAJ@lgZb2>o&LR_n-n=xKQUES|XKJ zhzHWEe&)k&*N8Trp~E2F(z3|S&8^01B;fkzck0bds@IO@Qr=~oUrFj;uf((q&?@U< zVPUDT+%29rald`5%u9&_`scH+CZ?u$H%oWg)g{Hn2k62VzZ`}Hr=l2s4Sr=C2YSt` z!K;JjV$Gvg4DdBjt0CQwo&Ihz0xo(KnYtM>ZCO?9wFi~S#oPtZ$33qUD<_0pYrfrS zcEo7D*|Xt>vzLVYbBgZms_<=wy}qgG#@WIQ zCf0fyC7IN)xLzHVP68PJTtx3K3ksRFyw?*L-YZcTU-$zOUoxDI_Q3S7j|t1^>R+i) zV`+rt2q1eboL@NSCwtBV?wH-D&=4XwpTxv0$@rwUs!WZ3SEZ4oRbR$k+562lhuggq zGaCQFLX=g9Hv$kxTppWOP_GG=vTDPX!t%Co&5o4jJrv(lkw}zXT;Pg;z6gPc7RkN> zHtX!t2WIBy&Ua?C{q+`A7AlA;J!9rBL8(X7EFAw8Dr-{xNeu>GSx^G2T3=vibRYGq z3cH&kiccc{@>AFRC|rXuHYv%m?zSt^QKaAr%|Anwy;uahg^4kINWn zeAzoLaff2bO;Qbn3D$H))jcUDTaqR%(R4H^`#V4d6$A6=-fC3N@K_!#OcONywdrCd z#6%6n89opMXphs+Gn&FwGcP828!G0$$cC11+Ri^{xjk6|;xQWIo{!usezMDw7?DN* zXpqE2OJB$tTN~#pzY>EiAKzcESm=cBj@?)4& zMO2M_njQFA^@N}KGYCV6XJ_l~Bg|M^fpS$Q+EK2N69YdwTgF5z{sWOSCimkK?E4si z!OXn9b*eKfD^EKJbuA^0^$2H9;$8BV)8UHaHE|}B#=a(Pb^tATPhVdCSr%Jpz2`^o zQ-CFr?=MBOYw2?{<#U7FNZld0T_(HLS7Jb|@2k33*a3sK*I@_YRIzS&hd~`e_i{8= zHkoe%$hiG`Uj#@Gtoi8^5;%Fvl8c{%Ad0){A zBP9web&6Qe=b7U1mvC%K>S_MCP{r`?&WMt$& z5Xj9Z)N3#GZ){Hrl=$?vSU>Se$6%W_4Da@W-f`xKn=bYh;NlgUEcu*^l%~I9<3N7Y zxID45KWcR0^f}72XH#+8+VVJ%ZMwUl9~tZFL4QxdAmj=z7d-(ET0hJ?s)(pFS&}nR zBmShmc;k1T*O)7#x|*T%$t{ziRCCoGXov2# znGwS*(fOIa`}(rDD~aMeIkxijbb2-jrZL;Rco`BNEG|tmaXGQu`czOmUW?qqcuzN5 z&0Y3{)G|VqfFqJjWp1+AfAGp!wVB`dTb8*q1tMcaI_eNw*`At@w&6n$V_=WEee|eC> zG>02*$_UPI`6H!Gl4VsJ70*AYzMZH!)p2e-Me$9>R9NN9yw^j8PvU}L{VAnsGi@$o zc>Jv&e3Ok%5B-oaHVx$O`3liJ`VR(7aT212H?~%#mv?9fn-(dA3ukF^9;z@#G8Xy_ zkBROTCkti<`Ki8c)V#P1QX??&IgFx1q5wXdAT#GcS(3D9In@*fg)HO*cX)#|q|oPQ zNXyApzqEJF{i$AUs-?2-d%YPh7`m~jWfNT-;hWt%=$gcrS|&Sc82|0Q%yz?Ix7@3&5SAr` z_?{LABO1see@6V%HgB3mCkLsUlkw5Wsx$ph`rEPVF*!45`xIFB^>$*j+(}QviSp>8 ztP$T)XQFs;d{6l7&}()Dw&Rd+)zlvnFP;?&Xn9;*e3g&+#8tzo# z5s)=xKVg`|Y5a7Xq=|jjxM+(8(u)-tuVE2HXQgCl89Y6)TPLLI5_=jsa63|u^E&Yo zyXxce@9ow2E@rQ<(vTC!%FmdUxeM3J=#rNzqqnm&1V2!loR7z%Pdpgc)s_TAJ027! zsO2^Wz;Y_#5oUvqYoXlXK!-;z;<;#$9 zASS*4bQ9B1xL@+PL7SX$I#sL4n8QFGSm~Xi9Aukm2otpjAft}{LbprQ?3ZN)CQGR| z?F?w@dtYS*ynLMPRZ@+uTP)6Qq9>=UIMeG^{LY1b0t13F2&t1_5l?0xH1i`X7H%#2 zgbCDU2sDr|9p236-j^8Eb92CLh-IEIQOOXf!!;hN%AcEA8c$s~Qx#Vl1yXR4y zl~=^%kGK!JsrD=6f3bm|_?=@VsG#T(iUM`na`gfvVIyi__?KCZBw| zb7a56sNA^sXKQuseu6d2}F8chN{hBkX4p5L-Jn?cz5*oGvta>5N4jWI$sf2 zoe@O3aUTC>8=0~S-LTNaIjd-@F0n7Mw>-^G6|iFSNr;zLC9a`Vl_rrGgPl$7LQ)I7 zC8@-}qzc^t#R#rp#B>>uM_&Y+gO)8~&A*fM6#RHup$qhB*d zY{4SE5`0`6MJs}5Sr!9o6@UGG*QOvh6bQzN=$>=3o$}HAd^t@`O=4jyZv(?MoT};pFaIY4amYo*U*fh!|czIo(;G6#m z2BPESccTkNaTD??zen?U^y6Noh2XSd%H8kOVD#{~_MqULbZzJ46aqD&v=o_{=b&9G zz(LxzE3qyxQ1lQqUY6ihVIZ_-Nxk4X&d8xGTPVa_J1i$EC?!!1W<4gaJX*6TWu0k|`&4*%@wy9U)$b>Awt+~B zTOiguxCg`@p#sovFOw?EZfqK6G;~z<+3CU`=nQy6d~82jlSX;aCFoYt%MBFwE^8!v zhXO133o*DKdiZmW$Q~89i;sN~+_FYXgd4*`IYW1nLN{w$W1>e3qQE*X$v0=je+6hC zBlVGFRddOOXa?a8*RJb=Y!9rf88d1_;(N}8-sBEKubE?FG)~FWj~thZPm;{27PMZg z0KMPX|Afp9QYX7twp(!GQ2_+`f7n&{UwINs_d(}ugApneKl(E7!{cFR@3x0T9sxS4!a9FU-m0dm(b1b;7%VaYaO`*-WX zX|#$HFH7I-Y%9XFC|B_2gJCpy&Z;L?h!Kb7$o$(rlD4o5$AU16M-8%d9mq06jV{)nP=jmT6T6KB>&@ ztKr;dSHEH70n+ob6@J3sKAZl5MS}q^;`#@7LU7=kf%tbhw=Me^NsuW)B4B;Fkx;) z3r7AUf|qND{_`s|D!4zN{%tKnXTRk{lsc+uZmT?smz?CjZx#miOI`rY!u^JWETbG= zU0AE1VlgW@fyDQ|TKZgpu*ai>KrX0>Kl3w^+6>liJU}PMeb@Qz-Doa z%$F@yE%$H#Ec?W909*moqVu2qKQ{iyYV7p!D9YY>2#!Tl3IwwClb)_n)h7fl?50v1 zm)CGIKB^DwXaKZP@OJ_iqO~AE+vU&!wgtWi?86>KIkob~vMxR5nXDr=@+v|(4DvAk ztJT&?H4G5fMAbD{zqJbNDrSjceRsvs6ildhgjZitW(t#Y@4t-A^vgPyqI5uFynurA z3;{>td!d3S> z72v56ClLOtE1G^AJO!aeLb%zeVOHx+$LCeNft@jwzFJ~L_GCouE5GA+d}e$;$Nik% zCA}WXyh~}bOv$yXLKvJ`|4<_ma^%2f02Q60=fldefanL`d0+f{H(8LrDOR-MT7AfK zxfk&I*oanF{qQk2)2Sk-j~t8 z>N6lb$KVgN-pn9BuWHq5xnCc(UK`6UNI~2z${gbcm#8QxJg8#9I{Y%gJpr3ZFh}r3 z5?Pt=&3Xgt7}dc36k!AEbb#*$cCg5~o3YGL3wiSx?Y&ybuZ*0b0fj*j(2ZodkF8N zbl(G60y|UaJ1Bs#X?W)Jr0?wt{2gyn86X3$%-aqD+@kDLCHKG!`?n5zj?tlW;i(a= z?1=FCY2+l@|5Msm$3@k)>kcAfff9n0DBYvdAfX`L4FZFpba#hJNGUJUEje^I42poH z)XQ;x6e6ypZ%XdS?gKPy4Ri8b>Gi$6J_I)fySyeZZPe0?4vx3 zkdXHfbX+&JoK{HmN*O`__Sy1ic>t$@hAJ6$k~;xnQB#4Bf69c$Hy&(OO4lw0XovIH znm&1Fysqi;_pH5C8Nx@m>N}>)MUA1LRc^H*Le5Ogi-EsOb4xoPdIQpc_jH&5%nh04 zBI}rewBzb|z-$SMW~una(2CmVUYja$!%Z5(rw3q4t(57-={kX4`b;GAU>sM9r=xMd z^$tz_dIPLhg}qnLEt=%aP1rUf7BNg|Q3f>{NF`JldefJ*v-6lUIkj=*UK6Z#E~AdY z#SmU+5w!L-@NK~0qFkJ`;uY;Sq0`c@TU$Z((wQ#qMRwsKd8N;0JsJ(8Q?VSUM*Umz zg+9Zt0ee&lv<*=AXW@rsQ#Rr+Hw)L;5#;UM>ia(@KRuD{Wd&0h(*4)5a30TR&@?;^ zO41&rOwO1zGrnyXU>dt9EKzu~Yhf!pdzr4q>$h->dIuLfi@CMd_fu+}n(d5jmtV|p ze;w++J1{`9Uwyypi*z;%$mnf2?a!H_VwOz>rqc=N{*r1Or>}d49rXZgTPu-+@n?+? z=^e7Xhf^6OChbxA?rFO5t4BjI3AaqKLqQH2iZ)Yu?iY2B-jzx^H-5X~y*YT=a*(CN zTE<+X*5{p`yKXW)vfvX8B?-d4vS|=Pxgm?su|gJnoOt%*t+AhU(Aq?-9?qt^_aAEX zao-cM1Q8R7isqBR$ffLPLvErd^s4OMuY1qPdk(>*kSed$jooo{ck)`fr37vV9Jm{< zlaiEXhofUWhxCx*DkzQf%`6HEPF|AK)Sd{(Pk-DV-)9wpuZL)NzUj3RcQiahc zS&FzG7NLC?l6Xhe#0IM@8-H56)D0i?MyHUF=e{?~Euwcar2{thCLGoFiAluNV*69T zhPnCGF@`l#gHK}?#^E6$yFCo7G}oK&MKz}{8#1p&53d>dOVM=SO8jW>Bf+gF^-UxT z4F|blZI;4S`gMvu;&FPX*084T^C=eQCM{|YljgaNrzyFe`YDguMSMNxd#VyFUuTOr zZWY7Dihs$Ge|WhO@Vp^6Uid-jl*{kmrE1B0%b~%K=uXV5qDHIo?c-uMia>*Ooqt_s z^m5!SN#u;`YmZby^9M;HU32Ch==z|SZcp!%;FfwB@hJFU2=|_+hV(3-PIkHXFlzcq zgNBpQhU|gFskT1KXj@2&=NSD2^a~&>O)$xO2hY4co^$WN!6aL3wxo-sBv!lf4ZdR{ z;2*e?NOZGjY$_nHHm(f!PPS0U_*RnY*95O+^kpivEfRo6 zRxO*NtNtlBv5c0m!-l3a1X?k=s(uHOq@ZK?P?50m@c@(wE?X-mY)9kk-Yv1)OoeNA zkLqL1`NiQjZ)Mpjz`xvblg7)}7*P+co&M4~r06Jbp%@TeD%lRD>1Fu-q~c<{){}`W zS}3uWyU4O({OiMOyp|1PDD8xAIu7q}={Pm@gM>&jA3ISer<+RLAV^eT>voT{iG5%` zUjBBH^t<7ypS|;FROD6^W4A9NwQzrEKv1m2ch!5@E!qW^nFnpo^E)_{h`LpBj_!wD zAaB7`X0NZ&$DPX=B4>^_4~PBjB)je%J0RyanVXO2cJq-g^3fOPyH06m9sI-4)Abg# zn)d2p+|Fq$t$xXxGzV9R2fF={G$$MGQ57@CdBDWFSplK@Zq-gRX7JW>+Mhk`=EFU) zFU!2PVmfEL{Rihk9l=?C2ifH36H{HM+_1@ov=Q+;k9e=FDiUnGWP3lH?YC#+=b_nr zd?DBLKAR%cuk=_Cy++Fst2AJ2xY!M2J(n5=K6pbIc8ru?n)O>pRAu|1FA@=yZ%?4@ zTfKljri$zHLuqbB?A0EIl{A$dBpwE$5JI~NB_hYi{7b)f_WfMmv?N216G}99kdOZa zs}-IG_)2A842Cp*KJ8mE^eP)iSQHTEo#gH<@`q|G`^ui5HbD8Fak1JR`OItDi1~c( zy?9PI7w=_hXIC=mVxAN>8|Jfdq2Sg)CcQdz!BcbST{&6RqAIvcaNpqd+53PKk#vmq z)mLXOeOJAge#k42e56vLkRb|@3^f4@c9t>Mbn@ADx3)tPyvu?!578`hi zdcG4}RT(1^$VxwN<@te3OG{b~e^vG(tHvFT+>PuJ0`pciQH<~9lD&; zr(>ITqs1_DZmA;rA~?xLxWCS#p3QE_LFkkLdQ3BCFS2&@!*SWfjpvn%)9`n>$2O{l zN6RtnE>NwsvO4j6q)o%`9z_p!V=s+6!sKyBcCHP?O{?xTljS6{{y5ZxSlJK`kLEEm z-%<~n=cYXoN&VyRp$=BEr%&X4Vd>(r6r|6%qG}vt7VstN=lw@0*%OSlKU?K12xMZ; z-zNfDSxWpxW0%jXTX#E*_0Ae{O0h_}fA0WSc-MgU)Tx3X64t;e3_eTC{GX*yCJTR1 zlm|rw4lvc1lCKoIRr06mz7pP+wbqnB_P)_NYR&MArq}evjaKGr6CXu zfncSGi4>haniS{^*>s=jku~i**7O>ED>*_vjWlZnKH_}fU@hhbo@E~(HsB5toe5;()f36b6zDk#U zrRlSiFmf5!y(~Wvk8jZ(Gt&N?{kjfzxj!EWV<`(plC@M*Y<0&6+qCsHORFA9(C4og zP%Q}m-W$xoVG5q_><65OyCXhD#z=I{;P$FFm@y+z#B(eY*4s_*jF&8_F=J3hAdDLr zAM{ghkwN@j1y--SaA{T888}dx!04zByhyV(_CHuj7hJo6%laPv#@!ez?bPAtY9_7a zgFek#<9y>Zz1u6zQ+sb(zF{9{9?U$pbaE`Xo5WBtu+izdK=s(ab-tUn<41<;3{dD6 zR*C)5R?gm7+!Z?2_CK z8KMJmQX=PT0jJ43EoOChd#7$4R3Th}^%#i5Oz5}1m3u8$QydTPP7cg3ArEjAY%Um$39(v$$wU&!R2t?E5;qbiIUJ{vcbmjkhR4*_G@p``hXs zcs&U7P53|;o*7I+cUIE$HHVLl9!XS6Tc_?v(`9eEa?zsWMTuJtQof@GYbGTU)wXKtZT^~nn3-;}6aDt6$Md_@gGpM- z9sW-|drqcNWDZvi{u1ixkpUYudEYF>e&?2O3^QgicpH_E)}cYrOow+|i<5hz3VoLU zps#u^42t)Aj+-7lEApP22`*BQg9_uiwstsT%1FL0&u{!bYIWJ&;@o)pTdxj*2LNyO z@2Z)+KkxsoW9vGT9G(ZA4w3y`uPrG#k}Fctr{PVJmWss`UfQrK|(W&m`3XSkQ9l}Km!cgy-m#U+7$cZ=? zu16ARow?5inlBsXmC(C@o@)Wiecq9Q2OJG%wn-Ms;Iq&e8XDO7R=RO<&)A9X8&DQ} z*?Bf|cSU?Q3P}vGhgO-0su8W7uFn%N&k3=|ENl2yFr{7a*Da| zZ$v_@6E^<^nMuDhIK8 zH^1C-JeW7@Lr!MtE8^qR4i|&2HS9PLE%0(0E2Y+}OC_6IMZH}-(mrca){lv&siDgD zQ~U+0c91;oXJq$dr#1;DMG%nmTi^zaM6^lWxQEj-8CX40^iP+PkdQ)UFVhtGv7~u9 z&o@=cFbzKE;b!7r1fLAINr(nUN4uzaETeXv;nWa4%0^Kthwj#~+ewXA1boLz%2M69 zgt^xp1VR5PM@*VZ+td#S!P3$V5~3ab^?dr(zchUN$SGmn3JXH4?q_!EPCfLeN{ZdkKZ#B*%R6C!>CWB`%OFmDl|DG-0fDGFXg=5+q_ zQOwkTJ@4P+x@8QZCg1<>+tppQErhdZtG`2cSDY-{%1re@OTaC6*N z`=-c8kd=T-!2hovjKZK|KFbMahMsHLzx>Wo*dD$H9rk@Xsk}Sp!h*{xbXHn-fDIh_ z9#*7f53P|Kx<8_fXh!u3PZtw?KF*|Lo-%BgDgt`jlp2P4y*zie)bGJ z$9q~{SXkJ~ievr5WiFN**sT7C7V=(E#mya?C3J%_ooAEiC2E#+S2JqXS*Doco zL{E#BF`36U7q~Ptt#W3y=Zxguivzxgbky9jV|y;;C88Syxp@~N;9^*s-1LOkJ;4h; zL>z}9#&mvu`q4rLADF;YIu_mS-K0cMZ-)OkEoz;0b!6h=-jMR;pTFm0!Bb1ox!ik1 z_Rb{CGuM&;;@^~ZQkS;jdaplPCL*>Hzk54jGDDPz$mP`4+1iV)K`%z%T%;G)h2VflH?cnLc#|~hJm9&km=1SvM(-1IA`orwjaF6zhZT|g za&R;;Qrd2HG9`}}1pfgLOdY|hb4_jzAmaf5I`XI3^EZX8L0>r@82oieWdTA+I}l|o6n~TelX}DpH(Di z7g@H9$3GuN5^9k`Aafx)r=h^Ufs>NNg#PWOdJ{swM$Se?dYHVI52{&9^4T?@w-udI zitOyqKFh{EV8oaaL%pD3>fBgY-}@M86Z#o?I3s|$ zf#MYH2QzgzR-l##$$$q_pc`!hauVH2aLHL9$>Vd#i!Mj`{N4 zd1NAb^A?Z7cPHp}lB3et#R?J|-Je9w+8h8q^f&Cd4ut!;sj10U8>fTjQlHxP$npGu z!2xAbmQ=>vd#OswsIPCD9ti$*Dm%SUs%C*!tYWjv7ik>-xSs`6{hsI{>!(J}S;%sp zWq7I#=bG=ja=oHOtJa}YayNyn#F_eUJq2ydMke!_s?zCS37;MSbnqvx?`r@8r0HoS(j^#^w<*E3u7?bmJ%jUWMeaRJuHnh zb-0I;WXi}ZnBo|{+pcT#Ms7u#63~;VAywKR6VubtIli5tel_y_B*jlh?D4dKQn6w4 z0$CJzXSrbGGGXM`_vI4UiZEG76n1u6$3N?hqMALH(q?mdXc}3NLK^=kCI>2nY=7Tt zQotn6mxisgdhaHLd057lPqCVHtI3+U9|XT;SdVqKr|m5BtsGbPJD0N#pJq)d)Cp_R z=s3AA|M&U<;&Wg|(r%;93No1xcU?tHym%N7;`qw_K=l3+S;v05rM!S-F0`X)w zBnwd4FWq|?yqi^+s9f;X$gK1%$B%eJ^D^M;Zy&dbdD1eA30=x61?xi7PNr7YZ-Vxx ztpe>5E+`ZMqJ#k)DT>@bO&C{trIKLnWk=wZ`8QGo;M^? z$YN!tG&D4XIB7t<(moz}Wp}nqd$9{zGwp(2WbF7JH5kC-2{@@08ZrkKDAzT_uV{14 zLIc~?`sGS2)%5HPC^Gnnn->xjzPyB{*)dS2l-xeY5KDbGa}^sYL~^nBJtd9$o>FSC zljAY%n3B&w3)jeb?GZ+q2wiSB8nSdI{yPBT(jK?Wo^Q?iVusRez1MpAXHS>;5$IOh z^HNk*d3I4wo6pjo_%0m!n&E}#BLhQvwPZr`vW5(@L*}XpE`(mQijCzED7t|pMjXo= zvpcxslyXf{Cuo`qgapoHo5qkjQ)Du?Mn7d(&6_$s3iFHfNQuuyC2b8q#KuWE5zuf4 zjy8l{U`9nADA#P1!`n6Q*-guGGZ~%U)a$TKT#;IJ^ zLpq1fHC*1Lsct{(+yT1qCDqNj;hKOPb2AREKr;Q=p5IQ>(e2h_5b}S~H%fWIMbknw zY@|*4xO&>2$rWO1r@DNa@=^VuK)<%lVga8_&3)#q@0Xy`ejWK`m>N#*7@*P4JkNx0 zRiN2HrfrHZ^<(qH?W=0^H`4N%`9aqA@JnatEJ=6vd*9pywS%nL0qsRaPS8`VzcSQDZovJos(t)W7#rIXk5rXN&=8**fOjt+JS$5UoG#;| zOk}SCYt<#^EQ%?f4bRM^zH|^=lw(}hii^QhTe`4XQaw!^tdB0%hj+Kfn%NlneD^d1 z2YC@qW_pg*)i(N7Ovadh%O8(ti&*@4;gS^?ldc0I|8i9c`PL_EGj^N#AN)p;U?)k~ zxgFx{Fjw?|wVRe$gLLW0cju_Ry*(9lZyv(#i*!4F&{I|-%emcZCVJ@gY#xVxzF@}} z`MryCt<9YQayc4-6Ob)msB(hFu%C6|eDn|h%R6C z*$@5=6QLQoR|1z95OrG|qId&wae{!w;fS8j-zh09+&?&|LK_RPZfdu#zM@)aR&(RW zLMH*NnL7DJInx8ytv<-O$KJi|hf@RAOkW;*8w+DbQ_!jIFJeqHi*nwwXr5n120Z#$ zWce{@w%qe;!89z29fd68*=W=h`=rz4{gTNlM=Gpe4kuY{&AGm8aus^M01XK@|5kmW z+d$Hdyill4O%9i}cnYVu8+2`d(0X8kaUR*At7V#}r7T=T9Tibua5!B!0U>G1sUGJU zzhB_9|DxEGD$v`$nZ{U-!v$Jz0d(86^^90JO z(yv$fDLqvyh0FBMEHj<$U9z0jV-kzF3f5#1f~`f}UP+5AycT?n`4zi0eA$&>6jh&< z>7(6Z4;usuYdtJqzKxhX$ZMz#DMn{=TE1Y%135PE(n(24E`f1SnD06~w_c5QKQxDa zSWo9h&a}(Ms~+vb_RQ{$v6ldRGXAEyg&8SglJGc1GRR`K%bwOQM?&*( zCCFAv#~z?mJd9>>Ak(JP)*895w5pwV@8#0i+WhR7@e)&aF_O7?BA^8Q{X_Re32VNq zTQ#$`9H(O)q9f?U^ctNAm5t6<7H-Znd(H4!m___DOY?>*>#qmgekNvFobSD2Xs>mY zjTVlB_$AKeD$V{tgPXD1KqJ@7q}#Q|#>Xl%W2+jch*Z1;jr9xd-(i?+*t)l3|Et6Z zmC=jG$`-gLO`oGP;O$1AW$%3%6i#)oSj>OjD&A$}sJl~Y%hxQ)w|>MT2hV#emP*<6 z#=0b$kZnnX3}_zHo4RN9k-mP6qhqdjBD8L%n>AgWpF{+LQM)bd0}9nl7V``Op|9^N z!en?n)II3my74rmCAk*doU64wfLEbNs%$OdNfeD3Et)>nlB}|GBc5#NW=`t z0pzaJ7tzjV9>Y=q&*(ouLK7P)( z0T7vW93u;o+y4eM-LkgVC%og_aC24m?<5IZ2t@?`5 z(ZazZ$KuR5kaUyF34^jf*mCRd%L;4$f$xFhMwtG|MP)VOeFUCa5A0@0Zx;?^wZ6Bv zxBf4Xft`Mbp1#m#d;j1Cm{%9kas4$#cmRF{WU(f*wYy+L@y^J>JN+{fl+mt|R4^@> zt6mH)r2j2|nl?5zTmqGHQ9TrNIM)4=mPJJ~F^{Sc`;~r1DuZ`M-k1&SIDQxn%f<}m zEhLUe4wFkyyItfABLrx@c=#6XSr`&R#N&J}PG)Dv$KmX!MYEV9+hIJB5p1HHS0SEZZ&{M=)>{8F`GG7>wVawqX+dv_cZ|NS>Y?HeMY!#V&-5{yks z;dh%H05>u?PP5a|Z49OAnz4xzB~r`7xN8>c1ROO7QDODyk(9PqZjAP8#wbUCZbpNn z0$>QA6TX7nGUafUQPd@z$`u?E5}v(Bk{^EpjlGuA^zTKNAk3Tpo`bu-1F9$>aQcI` zWR|WV1k%yQUef4@TJ8mBl(=F+2`tay(m-hAw4Bp;Z_Sl4a_}GVv?U75_O#4Dn~%Hb zh9XXZNaE5&Q9azyrhPRQQb(Z_WkddCHY_A#|JPZ&-D!KtX;(?Iu$z+VmsxbmY@Pl5 zm9sDbYAo@?tjqA7<=Vvi8>PUk{tJwdo=%#UK4IIEihgEi%30G}tX3Y!g^C#!y`+|V zv-NWw9~?X3kk#(FG1OJ{f~Ugjw_61_F)B$+&_?1!?wQ+fUI6p1K2S5Exdg&p^OPke z|AA1}c>TgS|JlYn`lG51D)4l#e}yHNA#0nf~EE+a;U(v^(aj4?3*^)V!**_r+lHEdkkK3>l@phsB! zj=SR4(XIXf3o#HQB&HKR&)+>GBLdi*tLZR_Zn$lGFqv!bBWvBX!yX}ZbQm2$#ve;T zIm1R#r*9}7^0g8%fVie>_m7Uvy?&j$C+Tw!qXssjP$3yUgZ&ehZKLU~Xb(W{keG z*Rx<*SZnnYZI=eTl zjqRSxr!V6qBqiI`&^I)D6sh9?N+1f@g6v|UcU-bduOby;G%*yxdoq#?rKMJ7`qv=V z2L|;|sbT0oSM%hL1CaWN{|u?zm>H(kn<3DoeMt9|h#2Bde1`k}FB3r;0BjOj{=>hw zJgIJ~Vzt#8!Vw_6pK;B9(aJ~k(43>H=MRs+i9Z0oI-;9-))Rbm^n>vs27t@-H~FnP zwa8XfO%?eDb#y=Zn!P^2z~*Z6ri*bPk>pyfVB!BNn~4n-{}N^JD^sYQSfY3ffR00$ z$w4IrkpI+eS3FO}{{S9BlsGW|KU^wj;s2(YBG2aaey9rh>RGw7Hu{ok0-ko2L(Qb= zz+y@7QQ|o7Uc)%WNA$*fBN~AJ+e($AFEx3^dx;!yLtN5-wpF#Uhal6-M7aDb%!VG= z82Z*=0$;jOm7$My-=mqW(pstj*-@h79nt$|@+pBPc&W_P$6}Z|d<4rVF!@B}?)D+~ zg|YaR(Eo`E=s~~nvQiJy5YbN_Ui|~LacjgvQZ&yktrbQ5GLC-=$EC1dr4Q>&IPU*0 z;&lC$Erov#)-GX$X5^|>M1@Ea4~{ki(#W5 z_90Q%W;`=0Qa`}TCcQ6PN@Pv@JLf39)qFnge%37bsk3H1m%oAk08^*i^#Z-B0L!Ne zm*nq|rwIl6MpN=o2+mK*3HCL>zzaH03&A|X>!RWv2aYY zoJOiMR`j4z)HcyuVcDz>)KibDsvCRBKM`byo2KPbc4gh{db77jq# z4)*Tp*3E7!H(&Ni~cp?A=%~YtKY4}sq)C#Hai*4E&v?#Qe4@ra4NV7wh z_sbK@tSfZH@i%p`g%B=lSmuCh6>T&H?WfP+9z6A^%PQy>KY0?zOAZ$!meIp89Ulvk zwL7+w6{aRmwi*$VqlSr6w;w2ubX}IxP_97*qXMSk;7Fryv(~^QnVEa^7Xzj{se`3dq6!N0AUb f`#;MScP?-ngT(02RCw5I5+eIjNwWBb(Yt>G|90jD literal 0 HcmV?d00001 diff --git a/zh-cn/application-dev/security/figures/huks_keymaterial_struct.png b/zh-cn/application-dev/security/figures/huks_keymaterial_struct.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa3e3c4e7df818c757cf0f66b224560bc6d9110 GIT binary patch literal 34634 zcmd?RbySqm+cr9ghysF0w-VAIAsr$kARyf-jC6OGNSAbjlyoz6cQ->xOLq(%-!uAq zzxAGTzVp}j{c+YhvzBWfo|)NuKli@(y|4Sa?ro5qjQDdjVl)s4^!$?qSOEljOa}rz z(tP>|xRY=+gEJbhUkhXE~SnQ)Gc} z$!Nd1%Tu_@mvJA{dKx$2`u2ZT+4BS&QZEsS$u)iBn!^}&K z*)EP^ka8_{62?SWtu@1KeYVo5qr|u8Yg;^BLsM(FlvHNUvr@@+t&$?1kS-~*$X&0_ z#rPZ2z_9p&>Zk(T=bL*uGv-bG7te?XEBVm!E8fOirxb5${hb6+ID4)J{W@6f2b>o8 zs^&M8ID=Wwn?5`mtyuYnDFGd>W^pG9fyDH6(<1kq3PbIZ? zS(n!~z2_d*W0f{WArc}xw5z?>6OVTx)&G{&5QBnflEIL;)pI>Po(hr*Zj^B&B<1p#h15V9F^&w59AM(L4vUA z_q&2LDC48h#Zg28ch;t6_Y_)0b`TAM0uBLF_CHI1lk&DlH9=9B?N%XwKI5B7FA(A6+}^jM(JJtxB;Jk{xvLN@GH z&dHNaCl4`ze=3gAq9ont;KxU_*7_D`4KbJ`mVf!0u2HY&h&!u6mt8o zyQ!wixP{%M8sU9zNQhqicK{oR`I7d8OwmA^XPeCo-(pOs#=%J2^n& zV&!tQcVIQ%471JOZc^n4d6KV%`V%)}{fzBLgWN8De9tpG)?RG-vU6I;}WvD^WuQKZh%bKFldF*!HgvhBkk;uw)F*q|0I(14?+g6X* z3+k%iD&TU(Bz<{O^$@111y|Bsu_?E~J)Zl}U0Hi1egIAW_bu%s5MgyZ=TRBg zZ0zq8&T`z~N+rj8!;0has9yCOeph@_>IyKqQc17Ac`06AyfKR&FD=OkOVG`8e4cV0 zG`tgD&4nRX{!V{|n{s zn@s%o_I9GP`@yf20_kET;a(6i==ABc=}~H(oxn7Ay}oa(@`jc00s|sy6fvjDlfeR@SR6@R-`~);p82{>Mxl^WR-!lTzptk;Tw~3+K6}e=IO-pypydSl{-C!+dcIjkJ_hP{EbgTT%s7<$-A^~?=Du1?1ey&X zm7mvL?B%{Cfd*%elwn8Ye7J5~owXk{-{QXMBzFA#DUXwBqz2A=Uw!&b?pfCx-=Uk> zC%p5E8A=SbI%S8(%r7K!J`}~FBy&j_zs4a+`ZD<_@_g3PsIuwqsjsW`p5RpdVy#t6 zC|H{-dHg;f>Pg&F7hAEuL`jYlWPYbo0Lz#dx^id`cydOB1ajV@c`9&j@O0?hmyd=L z(>uD|yuF};Gne~!SXl;t3wbg#^Rc0DAcNFycn6^k>Oe(s=2@7)%$@>wp*X(#Grz#5 zpBB+58Hq1l#CG}L${18dtl3l|nGq@ASFF1$2ZkE5cBt|<7Jr))nSJrjwu#w%oMhJA z_@q6F*-F{Utp`G{VfUs=1FaLHzw;$B zIQG+x)0{>B3;)*~DUeDjfh@_fx_39?jm1sMabXdTYyK}FhY%nh8R)OsG@&llx4>V4 zKpj4<$e`ss{n{XObD0LcrUj)S$Y6eTP}5tz&v{>Y(rOOPJAqv+>E$!O>yPA;5N(>Y zs;V?OCKA@oehsvOxaS%y+>&Hcm?qq0bg}H4$A*N;>}a*2q-;8oIdx!5rHI1Nt`IR_ z5pgW33v$7M3gpA#2*-=cg_-Gy9%-#F)5eo~-X@%ZG{j-s6puPocSRC;Wbm%ve3&J^ zwhVjNw=g64Zi6Ke3k!|#nhrvPmWd`4ejoZpTSb$EOBw8>n5p!Mf5cd$%Au(D!^knN zU~ou$$}{C|5g=Y81DLMWW|{QMuRj~vLCqnL)mOs_dm@D}hsa&5^>fLC{q5GZoRcfE zu8($Iq2Sj^Pq{YFhP|EXKem&`@{w7{zUzERUoG;=yjmyq?QZNAcZ!Ji$R;i&&FfA+ zN~2CW^T3tM0?1hqtVO>%I~!%e-wHX#Gv*4K8o%A4{Z5*j88LLhuKfiJ z0}LHDq~c)YZVye8>yj`pbvoJ<3zvb_5_&@qOY0gmL|{^W*sSn$zrHTNe%*k2_{0&T z!}wF}f@~3$X(})d7IuZn_$+6x)K#_}EKvBjcw0VgkT*mGT7;Ipl(VU@vHDArWiLbw zWjZl)(4}eiMb?nF2sqr~qq6$bwbqQ*2N)rvB@06#Gqn^rZcJdkF`GR_jWssTS?oqG#@6?R z1E_V(8{u(*^t5N^6_HINhPxYNj{dp~4)b}z3Nqh4gp4j%v?8lWT>cEv&?()$aclg# z%`FexaM$yw7v$2^m1M)9HC_1@g>F`k%27VKJqf?0)fu10V=tRkXe#__CBxy)SJKkO zE67g#Jc={Emp*Q^JC>$O&Q|Y+p9{uniy3st+H0I1s33FsBn{SnD;EoG_lB~CT8E!K z^;;HaZm;(zn09M)7!~W$WPN(Qja>h1}g*$(eVQ4 z(nTX3{l;++j;$B zwA7xS50hA0oZ*;Ma>e3tC9l>qdvG;$a_pLg3ljOiR)6bcsf0GEef15e5RS`+F=qMM zcHnpYvAH9EyyBUQ^RBXIjkdD5uF~R`ECciBx3Hkmp^}&9ALL?lK5!%?eYuX9q@^_L z@qeSkAfJ;c`MGFhUE)EGa2&sgc{69`fRXwU$oVNEPGEBo|KSN=dvn&O1Bca0Z9MO= zhVkmb%h%#+MQsuVl_LZQ-++VXHJk()QB6Cfs^sSy6#MJ653V%vwUvn(utnoWef#lv z*VO{1YICdx(FKTcx|F2fgh!&Jvjw^Z8EN$d&Y%3~H}LD9|7vo=&FJS}XC2rq6yWhO z{1{>qjo37`Jf;Ve*6des(0Y2?ndXDkj>-a8Fp+wp1dsxQE}I2E#b&$|gd>F4PlD^m9S&??_={jNso zH}s3ODDhOBEhT67BRcb}K6tv*z-MbpFfSz~j1(}gJ`_9#Ru76-C=Y&D4^e>g zbzmf<)nEx{$>RTls5^4@%4OY5USl$)GvMe4HT?j-#F&vFIuu($Ww~85NeyaUaaMnX zr5uMA3*EQK9ILX^x8qk2+^7*3XYB;vcfo${yIJq#e`k1RXXcAQuBOsWOGXvoyYE>9 zah*-W#g9PZ{!&{R`0XneT%NB}$HlDjcKsSHm?~#?zE%!nFlXghe$}+W$>`S-Y7vXI zSN_9OZu*R_$c+U34o>Ie@V8A;sN4dx7zsoej6>{P`?ZVf%uPY^qf8x!GiUS8)J8Rsy0Q~}OdCyh{N zV1zc)R!7oCAhPwbCv{nat&Sg>Fi}0arte<#%}VOaD@ppuoM5yj}Ea zeiiLQedn6dZi}PakS~M^$_`C(#-r!w)tPc!4FO(6;RJnbk{@?tY~$AWSZrQ_TJ`uM zeFB)iqq;-S|FGgXSP3OgR9UG$&iDY%P%@o9&ELKHZf5+F)d4JfO@v@7Dra4%*x}6# zLwv4h1V(r zaT3WiRX>8Be|~G~6L?0XdT!f*0SbRIeKXlK-^IYHLDF1(b8r{Vc4^?jS!pq`%(l9K zv}fK}6=OQ=2c;GJy#iA<#}M0gRIt|1$AwhMJc;M$@$GrP>p@Tu=JC6*FV1ecO^GkaGQ~>v&L@iL3_j|KMiaoy z6^FN0WCY?e-^JG%hdw$^NT_e!z%{UFGEq`$n8+Oxw@O`q0+RWKEO|<_Fc9U&Qmi8w zILaR~wLvEptiOF&7U4Wy1Hgx7n6!ETqkRKAA(cyiRSgPP3S{@9@of*fP4_b!3!UZW zE;6fdsJ$3!vEIs<>c+YCMI4X30>*X2`+szElFCwc@zJkKkpEYmXF6{xb zcL2evk~oqzj;8W}(`8Zteyq(Q=*_p!5cX*2eDcCUW&LijJd4K)0zs=Sd05w#=W>;Q zeIWW0dXIO*bR530u=Ffa6RAcqzoLj(SkIR>>o3K@Lrp>Kh|>5oNs-Pl-g%cS;|mo9 zEE7{Dgzq0>owHyxTx}<}#r*yDRw0gtl7aTI`V^{D1;HCa<+x0Fz4&)F4Rz!5Luo8{ zbw*{>*pW<^oUTa;0Ad_L`Gnsd1g31zTTO_Yw`4xT~LbtGYc*C59l-m=O0l9CL6oIUktxK zzC%e=O9_;qr*%h`MMqFb)`2qm%WXiqro=F%2*7LwopcFF61ukTO1MmHF1)U6gn(I2 z1Dzx0cM43R26IhLx#uTlcjjhMN?s4;bzrD-CAl^^AjMRLPD- z9c@7xmszNv7Jf?nA(YvRS>1d6yzDWe%b92#o?5L~zq(t;LnfvTfeUap7cQj&UGK5f zEVK@aE~T7SURspRe9>mOE7grZP;_3xQR8GZPpc0lk{L7zi2aMu^z$X{c;~^iHVp9y zj8;W-2o;@$?M|^_h*o?#_??GKXTTye95m$`OxRui3m4IGYgtNbtFPXN?Ak%S!m}dM zydi^O)eH8*fs)EO9Cd%Bg<7gsRxjM>R{qqyCIJ|+1xt%sPdFigNtAy%D+pGrh z&fN}}u4cMH3M`CDi@(OlA7@?6asGo&0HJAn;5+?q7Tca-2n4Q7%!>Kxmp}7|5K^kY zRULqi{?b;3^HLJAtH9U|BiTu|7q;z-shEGO*s&{Q1qSv%d;N>ddnLXi?893cTZ}$D zx6h(Xk?n>P1hAJInXmpkcv*j`H<d||?&WfDQCgRA|$mRx73acrwBRGs@XV`8Eav@FtUBnSU< zV+*zyh^NCTfBmargBAPT>F_K6HPSYejv)BnIVrO;t%Qp5e#)+3h|Am8aYHP}p$vN` ze-*sJYC&PIPO zqBb3Vh&>TR&riM~x+77`Lq?1^+z*GuIrPAwPcR;P)%6A7v&w0&p}hT3nQGVkp|3=< ztc))0-HQ_|!h5$I4c>~K#F$v2o6n4Z)e`>wzFaj;(2Z-44Qg0y zL?#p^b(m4Rt)}=WoO6SG@-D*dS$Mf>_I%cgC%f`XwC9;}OkEA(D>M5Jh;ev}9^0b+ zl>k82bTS+E^@y7QtNrTe?*(Y&_5W8dp3@%50WA6t)Yu;=Qb@|dVl-1Xrbv58B|5;! zy*H2gD~(1lZ+})SOT`2Q1_BXXU`+uWo4Eo-4<}HjCmyD6AQJ=_#otI%xUJ7rSrI=X zz!W;N7fPud-TQAeZb(ijS;=4_EN*VP~)VZQj=3n+rrdHFSZ zvwLqNL6PK`HPr$(ucqRlM~zP@GU+3&CWHxfc(+y%^XcqLqDoktlS%9hXHFSe4Pk~ssb1D3>K;|g=LOBRD*bnsRvrP&pvkG(oz z^>wm_BQ#a#jh_TBzFyjE_2E@bO(>bLh|f;8NuOtxlln4ORGL)dWZUT54hG95lz#kMIw%Yci^>!pau~yfq?%57vV*YZ1{t{anEfH zsa4FeiiJR+quUqGfCF()u4a1`}iT9!5Ox$G59b0AQeEF#lg_UaSWQ>S%? z1&DQ}*|Xu=0(eR8W|EYYP&)S)(M|{3LoU$E)_sXc_sWmJQ`UX}a|*GST5wU|2HQna zX9;3bGY(P9Gm|yi*0?<$Zg~#|L#D}>q}ajIuip3F@PDGDtPJ?BX}-@y-m8fU@@{+K z?3~ji&^2U*{W+b)@iQ=lXEigKgA&rQez{t3EyfW=a|RAcMyDE<$Swr z_Yuh2*feoKwnR_|BaeuZQoX*s?wzA&7F5(C_R-bzcU;O}4`@z@RGPIzKS>*~eE=6o z8kO`fyvhJ9TSaOI5B8ec*Mdv~iy_LYe?XCnZOZmBfzPa4vHIHM?d6UMRT2s%V>`2f z@sFb5&UczFm6i3{p-rNGZlC<;1{5#3P(jWjPXz?9`?xj1+4T|mk3l>clp*}snT~8w z$>c&R`+;8lf=@;{cKjiIAIxp@;|32IAME4Fu_{pUkf+%16Y(-boRUJMZEWw)<|827 zBg3J;X~wy1!E=|WV-X1#0kzNh&`60 z83OL4KHOxcV!JH_rt|>Voe^CqTCpp0NAC6e~X!z_HS`$Qtf}@Rm zqo=tKB7|QG1@^_JN%T0IU6UYzyzfMpf%S}e2A020&DjSlj5WeFHZidDiD%dE7ZNH8L|(tDD+$>|0WjyR^LIIY0UJhB z8;KQchRWkqC*A>O8}Ah~d;4B#AlroPdQxaq-wPrTnAaqqaeA1DEMU5QjJnBvUelrYt+)T!Y2Vp=qw844-n0|@kG1Q7;W<%)EUh3n2Qbu3H__c)hw zrN7$b)@>zZ_VYrNZ^ls1RN9>17?q3`!_2({DL1SgyB*~`{C>D+$6?B_Sn=p%-2`F} z24jM(zx-{@rvh`o%!xp&5h9>KAvio$wNDK0k^1D#Ye5 zT@}x0eO)5RCfD4aRUS*7-jzz^YI!6>vK@pK;((vbz6#&_<$5;m9m- z2CG^0I3t8f*7D;F zJ=MRl8zdw2_#aYP=KbHhs9SkVUVsaKcJlLO#TWXYl4E(Bd;L;A zbP|s|*3H~w%1jtyqb&ZUvVUZFB{^uEnXh0U9;cP$4O;xyk#%b$n!wlB@`mav<>tiB zrKWg>$vmk6a<{Cyz@Y(b>uN(5B32tO(RTc zSFv5?rBisD;=f%_VEN;6hNNBHm_h&*y_J{G%FtKbFs93w>(2G_#Jh2FImWNmpbKB) z{ER1jcOI#cE)-r=Qh4kaTQ%M2_W*7Y-)S&)riZI63IPCX1UT{mVW3&YQ4uw~)E5(Ypz_pWRkGQmJ3BesIPCg{ z?99BOedN65e|Y1H?CL*wgKwr)H1@{ z;yg3449td3gS8y8=@k5(-#DrbHHiB^kNaok(KpnNs7-(sD?1}5rTupsL+1&(-~qY&pFqCgB-!hQcI zfCA9a{~O>q|HLeFof}-RYX)qKio5ve;WPK4@ucD*c=HYEQ0-6@juSgoAh6F4FY2Tv zX*mj8j7SZ8Ac2;7-JKCdU*1%*!=YL_4(9WGB#nA5uE#RKPIqpr=U1)rpNP9Ps9YC? zvGviE=%GWjpbY3Qt+){mgpS!YOsKT9pp%ZZj&ROubIv+h!5a9+K(d z(JubaSJ#Btks7!__;xLCs)08t4y1TopEsLk7K9D?F`jB_o~}{dJLP?89@?HizrgF` zwwf43KWhb&Li-oad#ViyuGi<`$BJNdEsaCZ(i~sa>e7C}`RDIUJO|DjHJe@B?0rP{ zkU--2H8a6Nq(OM|00PrSA3TXk>d(#j8 z{9$Oe3HwMn-r&WIEIh2yj<+^KsxMzylB65U_vY$6XNqw+nO|^nb*N!tlV|tW6yw3& z6g1T9w(TnHW69l?@5F-kpHTVqW0GXc8+NPakFnwh5N*JVpFsq4BpTq~hdUpEsfD#N zCTfP%_yYIx$$;bj%F|J6YngB zidvl4rUZ(GRdswHYBcymz+k25VaZKL{Aj~2{SryRjMJqW? zji=8nk)B24mQIA_h@_J5!=lS4Jep&lBm8s9Er66pM~AUUuUfwf20&}`!cg9k8M?_dbh{1^7d7ytU8K4k=+4f+x1{6f)Sn#2<* zO;DMCWs{rlC1Nx5HC-P7!?9Kbke69G*#%vw$>=FwZ3 zL~#;6YoCpL%^dnT+|;Ub<{2^>6xLoX=0iUJ>$LdjS?`?WvW}p&g4*g zx!t6iJN?-wCROQ{;i>w8e+Fas3vI2e{NvmW%&`W4u~@Q5B#@zi!n1qL*v`{Ls>#6HSWNvNe(gCM>++3F8efg&KlUOqj|M3wTh zM*Mm*VPiN`dlPa~Pf`sN9Obq4Wp->G^+KvGQ=W$#xAchS@q=1lt2M2%rX+<-78LqM zr&fjQ7H3fOEoBZ=!;z@2wBN*{4K9+zWcR1oN}uSN0s-kP^s|%f&?>%W`$U>LS_6On zJS6umkmP-_0wPMZM&oR1Q~$S5%^oHL5HFXUJR}B7%}umV!W}Y_JTnIHF9RU9FX!EY#$`X^Fz|#=>^ZLbNmArsFk7TTGPRp)rk;Wy1alEu6Ta=E?F2;Ac4 z%SJw;x9dOe+7x-)4V}3ROb-kMvJ?1C-PA{XCrg9dHLBA&#d$485J()e#(41S`lL=; z)F7?&$-PWe6>nBaORh)p)OZ`CQ&1p~*nPa)uA2WYAK8_*@n;#xsp4o+pMZGE-**4+<=+lK77W1aA71yHUiY1}LdV;( zUe}80*OLK4-{)aRDgJibc?y%Xy}pvY-*UVeK4wMjctAntAOwDDG&eLf^t_w)x>NOB zSM`LedcmE%E}Y`y;_mO39z14ZzpeE|BRHCRA!ZhT-F{12R~@CCymCmU^!oQ>>J&vl z#dhYZ9YQ_Q{|g|_PMZ!r4#%>@y{>b;u5(yD=u>5=ZoNHtFO?Y8k7%kZ;g};YfGRc8 z9M@PQgi6OB!l0*Ha>E3$O;eawQ14*&5|DwSH0lVc`l(IGi{wO05zCVsw@_M)v zk057eW%arm&?R?tSa#+)Y8ADxR1_$#si;+od(N#y>0kAWXxfTf{YbfJgtvTxdEjVj zM`Rl3Cjo+%^7golc({y^if8dSOfS*0rcuIukg)_3fYK-y)*PO+*HP%aG&-vi zR>26OOVQO)uM*Bs`flnvrYyEI6>^03x1qbM;|MPS4vxz5^5eu2cf%P6pR9vr#bUx{ zjoiF6fBhEO!6QTVU@?H1s&EOIRrwH`>Gx&IFGt|`7oUhNem+tqM?%}!*@CdJ4NKfIzb?HNWRn=LTl{alx+PkjD7x4i?Cz*-If`uTPydX z^I919;^o|Q^EvCo39Hu;s@KuJmDjxdGUCsNBdmub__VI@%xU{GyHTG2hV_BtsK}M! zqitsv`5&|&u7r5$cn6*?Xe-E1Zk!{j;4 zmg}iQ_f39SSgMSX#FP(YZ5cHl$3{Pw>fCKiDJ`So9NtpVNdLpbx& z!~KQV^+bB}^-iH^J{Bpu&SP@q=jPMuZ`~bOyFi%Of4NXf%ImB2hpWY~rJKbIp}TEa z3QvROTAnR7AmRap^0>zygk{$s&T@?O#qS>ncfJ{`szc<6w|psr$nSwy+TTR~fBT{* z1*o?cN*^JJozzoBx&E+xtPk*V!aSLGUBMQ14JkxyH8;M|0~P~9(9kP|ddExUCqSP^ z?IWJ&l^LnY&%$3~M~-jdI2%U>VaF5N1dA#uEa+5-b5tzK+{F26@sO7t z#dPT||7q$0LaO!yYlmZ&e}8Z>o!F-*#F#))c4^V1+A*m(HO#E4RUJ8qL)G9eXX9?x zyBr#KJi-aEa`T#3y>APigMQ{4+!CFd6*dXPM#51S=4niA*tMxLHTV)Dg8)9NXof5O zAbP(SncJ~2L5Sf%Fo>&=nLY<_l{|Bfuf0nX^oKSQ?0BA2XJt z3673JcUt;39O%8b(yG_>H0}NJG3BGae*I2&Ch3WVfH_%708xr1z8}Gao(=ZrXvXn8 z27S5w%K^ONZh_kJ<_XRuQB1oTBxXC;|3rOK{FhQ=e{bR7c1>!;FTqSkdd!~{xzO&f z(M|CuExXgk$Y8Jq*vt-w)oKL9Nyl~4h}l+YrWb}DmADZ=411vPa>Xy_O=j~Pf~D0V zY*`!es?3K5x7{1Cu*xk^z}il9`t8@lZbb0lI}0W(_a*1)lp-^l1lUB|*SRlD_HVRL z%a!O&zcUV5Q0j|*-A5T)MW!KGw9+9U2#Kz!UYtxTR_zBL6ZY6-&d_(wnlC6U;p`yl zzWc^31v5CxeO`auUs)(Oix?XKk`Jjb=Q?I_e@UaJkKw@kqo&*iN?;?k0eF%%)xbdf zz`&45t)HmLg_`%u9uHDAgZJ~X1B$r-v3cNWRR+v{5dsHq=#Q8|0KRMFiNtWI(g=~b zB?076PLEamAOqhIK!Q$DNR$_WZ3?ZQU#Zu@Cm&>NZgrx{h2BJQP{pgJc5(NJZ#0Ym zJSU4v(x?4CktQl{s^!!42lvD7{r!Db&*P8)C1jbsNzpND7J~4S^5D#}W1hm80T=qN z&qd-LVfNyj0|g4r(wzwDiLJT_E?9w|O`VzH`ff%p(3rFL7}%?tSuS51+!+I0$vUn@ z14609WsRj<*=hi45Ou?cVDTTAVoAc}DKnw(m=E4AevYM5=Ly8xpc9Sg5y=N=Bgp2` zq-PC)1?y{|gEwjgb3Z3SO)($d)AEBbJ|p}9u*43Ymo;^2-`TlyXRrnHn^}|o>7Kx? z)l@^X+}Q$ZldPBH##`ANVBJ$Udhgf5?H8zL2xg`-fKV zMkdw$Ff{E=)ZZtwP>}rh78xwAIl+h^09z5MLz}#6SZA?1m^IkVk^9gxF?qU$9Fu-f&gYN^tQ32Wc-!#$}I?5}<-C@o#z>5qSy=1(~AXf+MlCW_R%loq~ zsVZCbhG+%1Go*JKVgvI@Dy~Tcr4eHr-`>9E3jA{uK$>)V2n7NY|3sYOT{n~ifVTW% zL;D{Q%p~m6Te}Elu1oDLRdS#F!n~-@F`>s%-oEo7bM*pl0&4hm0TlO-kL=HkL+otMOaX+X)9T#@4uXUF?-q&Zl5@Xb5gq#)H}gFn}QhD=%{2n3!}pE6#Oc>x-#hVQX{9?g5RH~*rS>s2OFp}&^~!gaW2ou zf-F*$41h4w_e*8zEh%r2QPj)CGz`SChP)y5hh;`j1$2}EXR5pXGq>j1;uqP)zfevA zu+AH2y7R5(JCwyj0&$|gpk?YjW!0dx-=QW>?^Q}?G&(pKH4^QQ&=yShnbN50opb5a zSpe&&q2-UFM$BpGwwcVU>V7$2+?T3)_Yj`NRvZS}4kw?5+|0w)j@=I{?(9!FIr1QF z>R;AR_?1Sl8JU{6F>vOJ1QguJNm4%yC+sT}%qO=|A3Al=ihEB_Fg~(&PZ9Q7EME^0 zzFoP!y+sI=YiC*vfK*OHK9^!$JH3V(w`d^#`QD@y-oWHL|?$Whg;R**~V zEY5nzuxNB^VhtfA068>Sql+sfa)M+wL~9vW(BfiSI`EH`_TH!3@oS&;T?k~Z%2Oje zZa-8|H~Y7~nYB-o+*G#^dZ@OSEWiBMgPoZT#Wn56GO27KY83q8Dk+n{SUR))j$S4t zC0*y}Ueok++t%G4G#uWaht=ivdD5V}3Shccp%sG;$z$yPGIKK0lQSp#i%UUtUQ+{*g z4gPBEE!&U$31jq`E$ALQOlBpq(2x&@Xl!mFghFIXyKGS6oC^}0tryOXAgJ@{!3^}n zp>wC>XD%;mv&4bI?wN?EUa5l=YlsvvWuhHO5yQ`IGi-$LGbb?O8+AYVU0*mGD@r8_ z;t?eVKC(*lvgKeF{5g}%A}j=Wbcl{p9yZ^dGLwHIaKZ0B6=yny&0D;0VKUBtaIN_==j@B` z%7l%6rHLC|TUSM#4#8??c&`bf9{=E_v~lJ2OYC=O-*U~!3J^KL-W|SZfO@gcD6q@6 zk;H8h9C8xWz5n^{ovn-1Ji8&$4R%74)6J(Jr>rlqfk4!zJ`4aj6GiD1gY2tZ;2@Lw zq+z^5NVL?T$H2n%d(XAG^Gn5yx?XH~<~6Up1(s)nzdqa()lb-4W!r_+uomkidTnkH zWy)Wmflq440cv!ZzHDyid?vq6!Zz21h0L9BYFG-quWx?a=2Fbsp0}SiNj^RyQpzE1 zY>kwqFU!3hvv2dtL`K!X+^Zz0;EB;UBvKQjk+i#0;fGV{H)|55h-@vl)az~+Tvb-~ zLxQ@I=N(XB2I%4K@&&3c_MnQW zN(HYg#rwwoMx2L^!6^o#4T$CMUb5JXL9xYo#v!hR0DlWcsWI! z&1N7U=!-FBrJ+^v`taSXAMH0z0yRYnX6>AJ)Sv z;c>Ri+fxD}#cMg`Z#f;Z^5nqfW7+<*4%T<8iX1VS_;Y?mq0aILm)Ri%N6IERKJNU* z;}?-}`ctesqr{iT3iLABn?~y0VXFEtp+v#(w6{^&=4&HaQKlt-EbWnJpZId%5UA~j zoO4#*_qf|2#5WyVfKN>ziSI7at{J3>jd+0*Gz#!5V51V9hAM|O^Iu~7`qCteutvf9 zR`_R{U?!nvzK!c(jg(2lE`z8GCk5FN$6PWiRst24)XIgJI(`m-LfXB;QAD;%s~Pl^ zI$wVZ>2kC};@%JIk7M(ShacOqZ`ScOyr}n0mb2dT0Fvqwl|lkmo!?D)sDl`V)-u~3 za{w|fWK#%a3Nd~$`^!7Z4_wEu{~d>)vghnvy8a{cT;?Y1XB$^#%$yt(q}PHm+sW09 z;M^a_ts<`~$wuD^IM_i!+x4p0Hh#;#m+j%E@OJmtZN5Vt48&D?XA@fbI!rCdj}0 zYPNfD*^8>WPR1e5SW!En6~jSwpBUoACFAja8qG(aDQ`&6;aH|JmYb2NF;lRrNVS## z*tSz+O)EpNJ&N3AgDidCSBzHynQjB8Z1+hitmlaxO&f>mdebe>g*!W_XByavR8&a|{If`dCDk{8&kM{5+ROv0l8AkF<3xKj;Di335&KH3p4r+paTvoUvZ$h0MxM|`N zlVv_kkHsE7~=;@eS{Le#lh}o1I&PHc&5}h_w%slp#`*mmtZKU6EdL60 z0yg{}VFA2Y-ef*^){4IYM~7|&LN3{P3_(jLF+#6q4=NuO0bET{l6-oD}E~oLIv^tBvU9!-4Df?y{K83 zzB32WN7h#eI#@A>Yo^Uyso^g$2mW!qW(Y{lVb4wfF+;K)b8dZ z<9WL9bsON{_nG3n?J|yP4m{x-Q*NseLttohwdl{pqiKQS{>zv1;hJrN>I@^iQzOPN zF5=sy#LssGkJ&f?M1lJ|?^{-%FUn(OdR@axDRSzcz)}CZp8ZFppnt|(JffSD?3-2L zAr!#bwq$Z)Iab)L!$L0mj~a@(DDs#bLwBDi?Zg8e$6@-P_^GxC_XdqTM-0F9{toau z5(MdT?8>Ljji{SyAgE+se`3w~*XpmXSFpTh8jhz_bp>yeTG>|H2><#Y&sg)r-Qk6$ z`yG55tL2S#=)VF%?>m#4`Ecg++ds6zCy@~tIqcS2|Mki2@89<3S6#ZdT^W;)!?FKK z_GMU0cLz=n#}p4^<;Nkqx2tjt$p6#F1G>7p7Q>zjd{OO4nEETkhG!s}zH;|8Fz713 z!%4pca}twD#x4{7hcfc5(kMRkyl6mhd1Vz9ZMthgVg z9{Xk`$Nn33={H&ov3!Hqt!#e(xT&KQ9S^wjrzoL|x{>D%l-3ykd4SDh^p+#^!@zvwY>uXmYQgTP_<(bxhR+@>P*7drR(!Cx>{A7FEq=@k8 z&41{wCv_e-=RE+$3oKxc(x&q{fVy@ka+ayJ7Z_*h{zi4b94O2aRr0)n9dyKQMgVCG zl?DU6zwM1!0YDFV6l$cLkt}InryncSB;+*SbMmaFGZcXT7gSMB$$(UeafG@zpCYhujx9pr9HoA z2sYeAE!s)lHGy(RQmULH9jW%|NnWv~Z2Fq{=H6n`F{=12a(mQ&kv?XAxkWcRbyD_D z-S|xV!k7qknUF1N_rTC+Q8l>y=P`|y#~yBi$Kjmg;fT+AR&ArH3)LDoSB+&#*K;Ni zkNgkQ^&p9Rola-xgO~68HHbjYLC}zVzaOI*#v=)T=HtI{$6xgpHTz6v9*IZ;d+^&d zA!~j~M6FqTpmA3VpW~fr;4iB~Cb1%#KZ6qR6!-d8psp-T_cf))D@qnITOV@s63Z)9 zcvQS4^aP0#sHd}8D)OJ_ktvWXhvAq^BBv6S+KScOLnI;CqjPG}WzM$bk2y7B{R z51y-lz%P8OMKU)t_tpqTkhkjGz~S$=_57TXx5cBk{Dzpa|8(ujMHTM!YhM5d;CZkX zUqArW?ev~W^b8C&LV{|_cFUkXs=UznC&&YRm;+2nEVYHwhq<*L)gEB>POH7Ulo13B zP|P=B&f88hfqICsMLAqh7nwu?(LM*p zo4HU(l?mXP3&)2<6cCTKseD4NSp?a|HdTldd3biPl}Z#6b7pT%2wPA-lUSgH@-|SI zpnsq+a7l4a%58fvJ{Sw+7u8_^;q@r{B1~Ghu?pZ2Yi+kx7-b`bes*7R*&tD7${VuQ z=mD$;qi|X0)rCf^@6NHY@U{0M14dqamiHB029chGk#b22nvT3dhV}f`56L<|!R2n5 zvKs`5yF;Mj?kRSDS6?N16OB0ywhtgtdAD{@MSf%!d=FG&_;Ne-0skEkk2Q2~7Nx#9 z1F8;)qQ9eK7R5I4*I~GuUOUq9bI1-ZJ1f0ST|z!0fEOXe@JJ2U*aOZh*kqvB?rN&$ z>tlI0u1_TYqqnb)tLl5#Jm^>c7R6-h5q|2bByGuf(rQyH<47$FDZji1+hj2)x zQ@Rh`aOjYnb@2Y~@6ODfnLBsxXZVW^d+)W@-fOM*ectDJ-%Y}Qo}}qwfs)||)1*{V zoSO{JM&AAdaofZB{X#)TgYYyS0YQ1Pb&@bowuuefFZP!A6eB!ZrC0zinPvrOMc#i> zrsDBb&_x>C@*{|vm8shd^?hebw=-?Iy)YF^DT{!AvK{g_w}wHUxJWQLjCsUEYi#YkOnYg{h?!R}``hf?+di#_etTRC>S9(4@u+~?FHQa4u;uU2f$ zJ!qY2BO+YB%&!Py)*sdb>IID5H{7R7*k@zkqxv(9q%H0F{Hkk@#<3s|9B&EMD0DWd z=v}g1#V$vte@?MM?&Foa*q`Rc;C~2Z=oN-d2>r~BdOKjazX}8rQIC7Z?}O#tPnT;_ z?Okcj+v1W?)j=e-OF2@}nv65v=Bw3msSq#5^E4jkx9N(Uz5eu9u#kN{XrpuT5Z0lI zae;Ksam)RUzI~Ylu^^fcbcR6uIw|1bDR9eHQKCInJzY!4OXF{JN(&@Y~Wzxkx=%BZ54@830f;_A6P`6l{+ zQLXS8H*1Ned~I!XwborfSl7)mF{NDt{l<#ZJ6Jijs?L{s_Jf%gOX@oR{p+Hn4_m1d z9P`sgJgRdln`wUoSv%IWoJN4vp z9CPUC~_-vp>8iCz3AVy9vmmvDcFj&RpTPkC9?Ty912u;ixh4hM$ zR%ENGr=R0f`p%7xFP^~r{ld=9A2swmpze9Vj=!)s&sWdcXO!3qs9mdm(`gQV(`%4Z zqEEhjgDk6_VYnl=Aq{B1>0>8z!bj&R)k!>L`pTt7_^`wC=Co*Gcck`{f?C^?v#1ud zW^YC3xTAVR2wp7AQ*mKbI=W{PcHkdqPnf)734QmQe$!*)y+sdftIm!F=6!be);1Noa2US%J?xbCM~}aYoOcMz z!#cX0D9;&5akZV;Rb4QHCTj*N}X)Uguh@iRJV_vd-y=`@}$dXt0O~kUDPxj zF1&NLzItB1#6HYb`>36}(0bPFvDq+D;;yIxx|xM4!)doW_wVxCs!Q|OdT{B7PQ(wg z(v#_2Ht4NbI3J5yV?nSRzXb;eYgN8er@BnqI-jP@c4U#+Kd7fnI*+RyMl|#YZ0nL` ze>)B~9dZtQVq>Y**v9tEX{AH>rn!G39r-k;#xYp-W zvDPju>2J#*9 zlZ1Kp;zk$KupsG3iSuK)5Njtzr-^9)&u0WilB9Cq2M#!|zZRNVi7cLcM_nYekDQ0>4Kf6s4Un>uZVf{`@#@O+Nc~prR5uD4MvR8<`DP4%ZJUwz_I#s*IM8x@v#4 zq|9lJI&exOE{tzrh@n*M?t#`tCY@bG^5B0|&Zf|=J<6*&&8e7ARi&oFmxsgk2 z7q^ko0nC9RBEBeja}$M;D|p70sx#0i`h%lIzQIXexlfE4^JLzl458uY6Imj# z%cGl4n@-3r17B67Vdx?1`a{m^5GS1a*M>IFnYN2uPs84DHBW8rS7nyhjY|4+G1leZ znUal4T`69qvSw5;_~#`eD(`HbKi*b|(+Z4SDpbm`%S#@964mxo5HmQ4easHzRe3K< z9ctnFzw<_+`nn5Y+EjqNlsUy!nzHlF#^pr(K-FGtJ3LnVT5+lM{b+ z+mJ#w0(>o|ykKm?ea>5F=Ni7Yldi8f)zu{U!#Jqj-o~RVmg-H=hPv|mi?KswxO!M# ziTCPv@=rQ+3HVNSyVlOTlH-oo0?LF`s^dJi>q4MGaP|9FGn;;;DTkhDs{CIE z&I0A9HrmQrTVzNremR#?G*D;N57K1Wpz6aokB8W~eUkg*B!<+{0SPXzL>IUS6a~a6 zBag_YtK;!hl;)Q<42%lubBYUQ;&S5k^VeOK<)&*MB9*qac4@#w*jCP&bpc(Tcz)tk zsdRXmlDdDpm!+F$%UN42KtA7Cm9b@9NXVlQJwe2PLaUX82DdFB&ej4MDD>MCgf5{Q z9S->uykj}?e?i>vr4ddt?v7$1LTcRUF{d-ZY0=Q%wK!g`b3}=EK`0xj$$2(A|`-`z(?24ca|6TyHAUq^;AUF_9r7nc7`I; zy4BVLZ3KVdnCFgKj+o2Qov1lV0 z{Gse}y-OED8~2&0?CvLB^sJqauB~U4`W$dZhfvk-zq35>uq<8FcAYET>KKuaaFy2c zmdQmmf9%1AJDbkCwys4E!9&1Iquf*e5O#^<5Y$wVEoN`IYjLf?qg4rHwd-1f2Q?Om z%`a}4*KX2nPqq%F;Oz9ChCUXogI)&Q@AOXoo*i)+O#;bHk=Jp1gYNKeiClP?T(#XX z;)bAN(5oKiq4X>sgnlcDuG!hZ4z|&6KeO$8X1X9YxfQeSDXkY;rJih+k32nFpJ%%m zI}V(B&dJt2&E#q(W(G$dImA**XL&B*Y+buKl?zM~yAGcQIkS^(|d*c?kT#f3W0S5klYtM z+*5W-*)NV@=Tk|h)?GDwhG$ECv7p&{MKaB zUYVLxF$>T=N4oV^N7&qX^T=TJNC~^c36sCPL(=zUa?guk{3K{!8?qXCGUX|J-DW$U%r%qFztq)vECHIPtIQLgF)iBK@j~IY0e;ft~m8 zPI9&vgaegx``!2tCzQnIhx1tM^6fhb-V`fzZ%Ym5%kAji<0R{gu0*|rj3U8sUL}7& zYyrM0>4gPB@<93&rDn(_`(7v_0Yf#-J3wp-Rc)`%9T>S6S1Oxt9$%p$Td)7hEp zfm=IU|Fh@zX1|++#d82VM1%SHv%}(Z%!Hs)57_ip&At7wt=6uPMM^sNb9u2Q=~lB? z04(*mR?L-kd2~DxRluyOHzcmwjl;AAITQ)&tYi}D-A#Q$qqe?QIUw*;wA4-{pQ8>| z%2j0(bRuQ%Peq570;Qs@^3>S(x3H->czx14NK`o0-M-)f3|?O=RAA|c4V4wDW9ztm zWAp5RgHS&N9^gvW-^c72cQ)9^jD{PD-8c@J-wjfk$gm)mO$m z@N*7oO2XmcI_&iq-jSK|cT18Q=(s!F8nU=GtXlHeHxZF78VzPMyVdp3W&ENa3 z^sz?vWaCt9Sq5TI|E|S|@_CilZ=Bs*ocvPh@2`0=?YlYCn?932Ybk9pXX!JY))$1X z{;Wir%*FD^CD+$SK6cw7$5-U%2V)APY1OM9JQ>fE*HJ+u4|<3SJEX_Y(Jr3h<4mhJ zC%d&w955T{s^O6={cEuNjs%V(107|9b8g2T2lw&x?k{$|r`zk4PcF2><14+!cJ#R& zKU>z>RCVS1?yPjL;5+xP1E6XMSmCX-!knV013C{QExijl^AMUygzft?sgwOKmo-8YJ3(MQZGu9PNC|*t5+$w9 z>4xDkPV<`O`kGX$qx-GV#~o8hn}iSUM*!GCKdH4wo))tJJ6oN!te@<%YLkpBvmpO=*yp>wx8T=f+`?*puK|RrMpJL1}uLd(aNk)+2s-n)V~Y8=-nEv zQ(RZu;5q$vMEwVWJ_V@S>&x7_Cy(GMmkmN2Yhj`%NCHo&g0(_9!0*GrW3#)No|CF^ zTFpF&->O5&3S#)Y8+DS6NGlAu0*6vVZ8=rJ`XQG`mlrKZ!+D-S_o{zDQ=!FUSU*Nq zdvwuyxel-5Q2G5H((omHmFFVb9WfjX!P!um`4)Ua!@Z?pCu3s4#P$6OgthZPSquIy z72%}l9Q<`Xq%ZShPiR|QE5U@`oBB%ju>|DkC(HA3&pTxZ9xDPyJpYHieew@-I4D7c|uOl<~Ueko6y(fp*+s5-lSe-eWo znFHb~r7}7Y9$N9E6t@IEUBROGisC^j{GhLvsN&DxqUX@g;_hi$B}#?zH86qF`(pL> zNCyd43$rZ*R`sd2*JUeQ~4kr*(09K*jH~&-sq*JqDj|9E?8x16fOh z8`2B{liBK`TuI}*kwuZObjJd*hOXe;MnR&R*NpJauf;F*iRk5Mi>X^NGhpFB8sy$R z^{O>vs2ISof?g$m7@(LC5Elc7tN$g3Y0$mvQ58^W?b?h@V*^CMQ7<&irm(VXNV~e_ zAkq2}XsJkDP-p$-hB`RWUeU&7YUWt$m{IyA1oSR!}R>{(Y4UrF1hJ2wdOefa&9i^5AW56 zTd1rKl71WK2)p+8bEl9qSj>kE=H@zJ20ruZ)r=y-i0$qTle$>0UugCf22OsH? z*tlqrz<*-}8f71xKn=}=ieF5p_6GRnBSeimB7?-0c5Z{qKR$xb7t=6(7TmI+-Y-ayU9?+JWzQHLXTyxj;L&z=z|hnLNfrNly7w#L%dqF8BkVLZ zd)E*1*c!i4JKHdiy~&s74-qR!k{4R1rH)A;A%J;spU>b~ZCkFiwKGsU?a_ieL6CkA zLYc{{9#|dF(Dak&tSL6Rd~c1|JE>*{<8544ilsE2mS((8Dp4!n|>B0r@AgH{nLE4qt=eC*qDUCst7eSRP%HwjZR-+>2Ce9^=KqFX1Z}T z1xb*U#WGEJTZlKLfOjYKoJ&Z*+UfM;asCjmioAnK$B0^VMn+C!nHuJOyJ3HlSbU#l z^ES^=Hc=}+j{XHF3z&@+&|dcvm1MzKWRaBU+^1ru`1!9i;Pi&;J`HKity&Z~9Td-lN6!lk;{5Q8n+?d%Y{!`h~?$<)Kyrv8s7GE`dfcgTHP z&!yKden`LA{~8UIv7~`KP?4xkcurCc75>%8sexs1X^=w#TtqW8lK(@v*tEATY_9{P z#bUH7MVH{JLg#jgH?`qA-7ME3hu1&|wxMoN_<_3|je2hu4+zX|m;n#O3noPs=TI_( z13ARKI>m9)hV?r|;BYlP#H$+k@|GT*FKZC?iUkUzFwINSnbHdz3@b7=I3TUukW*(JP zXAMu$&?sF+?*#q+-&z`t`i+IL69`nl)AOPV&BbzCiK|0S4w}@v!&xYSl-Mab(210a zTY|YfA_jb92RsI?vV;`^n*sRr`EmPGxFEin#Q z|95RkT7qV%ktF+m?otKj6Ajr+QCg~nVDtI$)=j(C&(GmTG?e%mHTv0NOAH_TN2 zFARkeddP5Q-cE7FmgJ^jKd609>clcMxz2DYAxxfM5PSNw5iif&kmr;En22dih80lA z2;INqv93M6>mrCR)YK2Ne7i^mylYmb5HCH2sHMLCtvxns@H^e z8FIk;ZZhR?ezozqnLp_902@x-sgzmLoDe`LQu;L;ftoAH5C}0y0y-&#!{H(q^CB1f z1M(JAq#O8=JuWMsP#-*LzEQN|A*Ti+w(|l65-z2)&SN$#KTF;DnN0+6&5V5u04MQ}F<|L(l|Jj(G!Lx1rNdepm4}c$%GyJl&!>{ARW2vQlhPoY+A*y!r_akOZ7{DDw^X*6UXiD(AOV z_q~3i1BrEpmUM3+!C|^ea6;T!L5-&|e-JlS*s#F~Quzs4&py&*-B-IiR zx`j=+EKMFzjY)ZJ5cs{w<^J?vacqNw1-%chXBBG?EJVH+)UH6up>rbbCBqIuS zk>WF-WUUy;B`FWYEVGij$Te3#=_R`;=jDrrvPoNn>$$Vl&*3rH{gp^Fx6vCk^qh;d zhTF7A(rSaYimGMU(5@?6f)*jGd337^YSOjBN1$($%fKv)fKNCF&_jQTU_m4W999(U z?e~WA)USVP;5hH+Sgbw4h&w?FVzNd5(+%1oP!F)x(3XwtEHw|ntub0o0{Gt+)tp@X zM9HeHhQ_DsJNV0Y-OZUnv;}D3}D0^H>)U){|^W+gy|4hSR z!b6?kgq8aJG*Nh@ zY?zFasrYqsbYwlU{dMGLTl&M?IjSdwLt;udpf8jxZ_<@pG=?l#EX#OCC_`_MoD)GH zaFO!?ky_jN7Sm){l-Fm2+=rDs{BySYi6(OmIhVxNf$YFU*sf`))cH1o1_Y*(GxbGMygVw{Bp--|g) zOU#|Gm-C*f9htHa1$_A82f=&Z`{n7VS=LdF%Ga8C27rd^+&ua+MsUNUy5m z#Qn-S=jm5eURu4~-3F90`gFW~tHkA@+it|D1Xra# zNi$W?T0$(R)gXgdg_|7a)(TzVcFjM2B!hgEzo;3Fw0n0(u@*mvSq2EyM4G=O1yK*j zQL0abnlpeq27AYGrzr8hJOmQJG^t27s)D$V-ymaR`Y0v|?7X8w6Ruv5z%LNX+y5gk zC=t1Y&1!XkfszyL4dWHe7GDmMT#2-9fwMQL{-Pd=`4`{aKjXj~ch3IKhapuxO6Rg6 zq;>4H0XXT(X-Mgo8DrP6=K(dtObcB@Wn_ER^;|s~<}_CIffw}*iD9N+A_!bmtesAx z%9w`-bINlxUvrkW?2AGBBK}~<$Xr$cd;BxrC0PMu?S*{}c+4T?FNhO&~AJ_uIpB zWChmP!1x-+v?_0TwP9>uyh0T9M-5iez-EG97E=+}#Q)Yc&%WQlr9KP<_hA#Nerb#vxyPtlsyo z%469{w8?3$eIwZq1cyZXKaWJQs!Yy`YW-rO=^rm65@BTl-RvxOa7dN)71fG&AjlGR zKkSldo4>Qf#gI1!%J{;!$(Bg+66FZszuM5U5b&yc*3g>)I#irL-oSQi|h15u8N!ivALfZvle0{kA~{6BsVllp|0 zTJCd*LA)?UNtT_U%N>@Y3F3@4m-n`|wny4ZA#3uiE>?s)V^ny)&4%?;Yi0DFbG?7z zy@eXkMF=V0bBd-cHcP7mih>X?cIXQl8icI*EUO>r&@E)JUsDpvkW1MH04;PiYA4TwfURqSeE9-as3Y@T5w7wp(KKKuH`=@U98;3ytXL#2r#x z1A0jq(H~L2P=!y@yz+;%ff=R&-+}@@hvcyU==NYtA9r`@kWSDUiCXM>miFEPQ@o$3 z^>llONR_<+jab%Or~6l11+&v!lYdRvhZp>uoXX)X#96L7j|t){IhK8=|L!Gwi}y<) zYv4L`C^;$nIcvaFh`QH`MKb~8OZx#tW%*)f(_%HA3@vmq zXAh=wggQX22<@yMAGhgqoAlOuJlWk2&Sa0W^e&w)iwz;Z($lDPE!t7>J1`b*&ou{% z95>>d_b1T|3Kw=SQAvkp(F&hR1{TMyvNX%tKKP!|C@S%=l zFg<%{M{g<~^u2AhJJ=M5u<56Vs-rL2mAwyeDd;v86eV`{bW*D-Zjq_@xU+dB=Xu#o zK}m^mbDkD%ONgBG4akdtNh5N3wij2%96g5@`qpMC`cj5CRf4wVsn7~>e5Ke${Y&P1 zZ>^z`0b&6h+qVNW~p&J7%4 zsp7Urk>dr#Wfg-#zW1?W@3RdBRmJGu29pN@IaN`!tIDuQX#Ir6_3`)aVnszo&5wr{ z+rFN=44%8k^PxwFhZh4P=gP7a?|)s#3vBi{9efQXe9X!2>>XwisNzYp;gok7Dv(e* z$ULhaoFAGmRJ5Cg{~R|XtkDO=Ndvkkk}0LK6H)n^WQymv>&<08kJjLdwVbvG;S%_V zK)ELjIzFT?pts`>o&_*;Fc{JKAM4P^zq@&bqr=P1S}_5`{n^6%)jiG331XZ z8>KR087f0?kV7?W^(xNj27M4=e_ZTJ7GV7_lBDPB>&MALNAEsq+CLBRN=n$46c$Z? ziqzeU&=Qa8+JA~RIuj}Tll`KQ@zWRo5|i~3Ov&T1?Cg;>Y&FL8ING9$i+_%@qMePv zq5Xe>U8pkt5-b2G2n zEtxR-L_q6HFLL-xu!SanL5VYG&7XG2Wpo34q!Tg&Io&Ajy;#)0SOgM#TLh(&Qj=-r zu~ZUikt^8`*|exZ?kclY6WR8fZHE@WHF;v)ToqPx#mU*P0$FtHB{a|%b9a1HcOtU$ zWle`<(UFTA#74|bt9P7ERK@UF1N|h!1!^AQAvp-`__TsvO5pPbm4(VI%*)fA#kJ1i zh`Boj78;9|U451WqLAStcmXPy%%f`E%B>}By=jjuSKqmW!Ee?9?N~Lcaz+;&xgcJn zNs@SOEmqd|H}4m3+WmbkM)(ga_s)^-;CB^eol(F)juQ;gNKTdfMIMLVKW#QD{$8?$ z#=}knh~6j6Nc{?;nHwj^k61*MupT zpmnVD=FQLfP!Y6UmB(qNryEb)=W{P|OsGvIt-ikg=~{MS;bmw2nKW`r?>GWqaONsb zqEHMx?Nd*1LA<(#zP{(_;U*~IqF?wnyr{J}5A#V<=0i|!XT;l^mX=nOit$edMC91$ zC}RTpcsIKKEV9r&w4SUHRAPtxvwU=ROrcJ7+A50l@Y;-&z*AbbU9Aao?tF zZXXi-*~T(gh_XFFr54vC?a#2U%PuhuVEcCl?{yY~v$r1^Uezb^S_Pq=L+sAzg>3~8 zvxSV}v+CzM;(8e#iq3q)2EshgAB9o*-!R-%4i2=TkHzR+Wr_CY^^(C{ z75f`br#;^DCUK{a>=$gVeS!gJGaYOXVZ@5^n^liV(sjiR~f zG`|nj(rdV#Ow+URr&?e9mGo2QFtZBP5dOrydC^+;q|N~p-1M@_b7%o_xPf^EAkd5W z<I^Lk&ao1!o4z9iKFwzSy53&924<2*4zzleTM@_-zDjnOel@6c)o^IZ()uQpEpSr> z6@%fD63+d@Xc3Ds?72YIn2PL#0fj|uJ68Z_Tpd94c)tAIaFMCBlb~!Qvw2p zy>;cr^2WSh+9kgo!z@NH-=)M0+Wd=pJe$LCFJ$pf#{NsdVd3U{${k-N9+6Cum+kfR z+f>Q5zPQF_1ibr4?Kxmxq{+@Eh>3u$G_R&l>_R_8cO(~!^QWrEfY$Gqc@1}diy07X~)Jqp+3N4KUH zs490t)HtkSzUg3k5f=fT;nOf&C&4s`NyIU=as;V%@tg{^qZ4Xhtu*ss)Xw@9)GT^@ zxm+wY$5HOdG(R$nSw5*mOo=65YO+$2bEN>xb&~A&Ef}fkW9{cgf=7|7f3txq0_i72 zx9e@Qu^;r0=S1j*&2>tRP8pj>H9|;9m@kCHQ>!I&UxtbYbbF+5PDKLL=OS`C){6sjv%EPrJfw?4RlZ}mM`I_U4$sY=O~1{-aC&U@xK{Lp zB~KcTmff<`>I*%~)WM@{0LtNmEk@#XJ#w&jv2JCfQAY7-iHz2$PRC5EX{}8MarfFB zSn&&a^QtJOqYTg%UL0nV>tt~SVE>`(0zB@;%~LTFX#`DO0`~oM=fC>X2+TG)M(3GJ zWtz!lzE%nZbXO;F#u&iPxrr~SRWCn}ysOku5;O4}QU~JQ%%j^TY#&O1K|&)eH8L?IO z#|VG_u=Ad9uVv}G4=Lk^a^}G;_T^uF8@`gKpgUsB@>T0dEA(4|bk&`bX$6ux_@ee^v7OdP9 zDNJC)*T=_v3TFfVO8JmI`UYQU-_-Na6q9x62}-|}4_h@}G$?@$2cj|q5FMZWl8n5( zJOEILiO=U>0j~jpEMAq@0%pqnczd?Gy1MnD=UJu4dL^|-%riP>@hs5ROFs1iog7D^ zkN5rtADjcb*Z)yzp$b?yKo0|Y&Who>#gJOT$`uWEAvU8J{2QyYDx0S!NkdEj`y=62 zuZr>x|8+KD3Fa0?RXlL_2ZDec{q;W6lB}SLQdBgebfiWiGjmy5ZTKKt@Q5v~r0fV9 z$|^q65sB$O_Pzft6K7^d`Aak+B)me~Gcs=R{vK>_XpCK{`T@9-*UQDu^`EG}P^0Ir z10b3#=4;iPDXGkv#fCvYy+m-(x;e8b| zkA{9V8#92!6Yu0(ZL(eQtgXhAyaPAa*}jx!mSY^aQE7mFpZ)iA(*0i*+fO!^6q@#< zuo|vE+8dC+;-$C>MykaC6$hU3tN7Rw-S11*_XsdV3r2@9y&<%By+FIwQliV60wQ=n z1ijBrd zASzifUIJ02=9_;8zuvtmtryTVVi?V19s~-xcq=YQy2u zi6|Xl2-4qpl33Z3IUoXoM4Iv1uC>i=@?8X6YoI>-h@kukVGM3HRuu(ri;O*?3_rw8 z9k5J=lorK*;wRr7bkb+^o0j|g+IH%2TQ~Bm{kZD!AO`k;2J_!mCx#q82eT8bCQ*to zhLg2agyOT1NU-8`H|v(#MUje6*D(a$v~s20P^7AE2#dg!8hF|%hZ;XhYrulI+4WZR zm5RA4U&f*z>-y?ypo=Aa`0h9Pq$4hTsCo}YJN3zezPEAT@>4>-aoEGR3+8TWXbGRX zYR2whQ7VW+q>H7PZuiBu0^2!8)|0;CIQB{2-ghm;c_yRsx6gnN5;krdotQ#ZG zN^dW+Fsx6n{CK2A!rFhYX@|m{0>TSfYIT3;c#v$8NCMe-$6B821Mlop({g~941dB_-yI-r$axPSswkXX6;l46NC8< zR8SBVzyF8yoqy7};K~2TDd7KqoNspjnHPre1&dUGTw|i9np-0|S0sg`W+9d*%^&o+ zAqj|c{)c^E!h;nqL7DH2xLRNsF~d*u6Yndwcs**Y`%2IO^iVi|ghI|={QT?=|JJg_^s^ADpy zPpu7C6FZ_K5?rGS-_f;U12<8yh{Oi$OBg-NHK1ivF>@kTEb;J}quygj;^=i$r?AjH zR$psE2ZAW5;Zg40|4GkrE@Z?O<@dG6TOjza(Cri*D~3pnsliY1&2tUMK!D{8)(=T~ z}(P{K~Qtx0GY1=LbWwEw9AS$2E;xkOHo(v@3t}lN27M~YtlAm$|Y4)u8 zN&nx1SBj+e7EG`lIS%U%_dk@`>{80}NxE3}(cvt;IGjh0Xn+P&mYDGbuQH}cLw>7z z@IPsgfmY&Sr$-L%I^CHxQ|7gu$Mz@&?PknqNF&#vZC!jjx!7oC=BG!_pQ~p;o zrP+H{U9TH-=N`E0%q#VwAPH9x^)wbJS8x)3eZZj7cO3*5l(yqAgQNT;?oYwTly|0v;&%%4kSuFaGq_0fL z7ooIjX6w$9{}J&CMpDKCq*28|U9M9xw=q5tH62F&lnW~)U?9$q=?5c{56JvPjHqu% z_H&To3Z5`0?YOLzxIfnz(>{#;`DXX`q887!C%ho_4^pIj|Bivpc#l(cuE+`in->L2 z0d8Fufs=L0MKINBk6|a(sC-1OXbE>7b1{~h54AS?(R0wISFO~uV{ZpLhibi+=BW;(kp+IxOUn!Aj?60Nbvta8J7?VF{!UCH;A2GScbr#ARA&6V}D9oRQz!= zy;jX@2hB%d2t7RfAs_=d(SjkgA@=`9CT6wVzKFTBh@Q0^)9im%>Vmn)f9~4T`p1ez Vsz!Uk807=xt(cr>!E1xh{|y*N;rRdn literal 0 HcmV?d00001 diff --git a/zh-cn/application-dev/security/huks-appendix.md b/zh-cn/application-dev/security/huks-appendix.md new file mode 100644 index 0000000000..317d78b22d --- /dev/null +++ b/zh-cn/application-dev/security/huks-appendix.md @@ -0,0 +1,227 @@ +# 通用密钥库密码算法规格 + +## 支持的算法类型及参数组合 + +### 导入\生成密钥规格 + +| 算法                    | API级别 | 支持的密钥长度 | +| -------------- | :---------------: | ------------------ | +| AES | 8+ | 128、192、256 | +| RSA | 8+ | 512、768、1024、2048、3072、4096 | +| HMAC | 8+ | 8-1024(含),必须是8的倍数 | +| ECC | 8+ | 224、256、384、521 | +| ED25519 | 8+ | 256 | +| X25519 | 8+ | 256 | +| DSA | 8+ | 8-1024(含),8的倍数 | +| DH | 8+ | 2048、3072、4096 | +| SM2 | 9+ | 256 | +| SM3 | 9+ | 256 | +| SM4 | 9+ | 128 | + +### 加密解密 + +| 算法                    | API级别 | 备注 | +| ----------------------- | :----: | ---------------- | +| AES/CBC/NoPadding
AES/ECB/NoPadding
AES/CTR/NoPadding
AES/GCM/NoPadding
AES/CBC/PKCS7
AES/ECB/PKCS7 | 8+ | 1. CBC\ECB\CTR模式IV参数必选
2. GCM模式下Nonce、AAD、AEAD参数必选 | +| RSA/ECB/NoPadding
RSA/ECB/PKCS1_V1_5
RSA/ECB/OAEP | 8+ | | +| SM4/CTR/NoPadding
SM4/ECB/NoPadding
SM4/CBC/NoPadding
SM4/ECB/PKCS7
SM4/CBC/PKCS7 | 9+ | | + + + +### 签名验签 + + +| 算法 | API级别 | 备注 | +| --------- | :----------: | ----------------- | +| RSA/MD5/PKCS1_V1_5
RSA/SHA1/PKCS1_V1_5
RSA/SHA224/PKCS1_V1_5
RSA/SHA256/PKCS1_V1_5
RSA/SHA384/PKCS1_V1_5
RSA/SHA512/PKCS1_V1_5
RSA/SHA1/PSS
RSA/SHA224/PSS
RSA/SHA256/PSS
RSA/SHA384/PSS | 8+ | | +| RSA/NoDigest/PKCS1_V1_5 | 9+ | | +| DSA/SHA1
DSA/SHA224
DSA/SHA256
DSA/SHA384
DSA/SHA512 | 8+ | | +| DSA/NoDigest | 9+ | | +| ECC/SHA1
ECC/SHA224
ECC/SHA256
ECC/SHA384
ECC/SHA512 | 8+ | | +| ECC/NoDigest | 9+ | | +| ED25519/SHA1
ED25519/SHA224
ED25519/SHA256
ED25519/SHA384
ED25519/SHA512 |8+ | | +| ED25519/NoDigest | 9+ | | +| SM2/SM3
SM2/NoDigest |9+ | | + +### 密钥协商 + +| 算法                    | API级别 | 备注 | +| ------ | :-----------: | ------------------------------ | +| ECDH | 8+ | 协商密钥类型为ECC类型密钥 | +| DH | 8+ | | +| X25519 | 8+ | | + +### 密钥派生 + +| 算法                    |API级别 | 派生密钥及长度 | 备注 | +| ------------------------- | :-----------: | ------------ | ----------------- | +| HKDF/SHA256
HKDF/SHA384
HKDF/SHA512 | 8+ | 算法:AES、HMAC、SM4 长度:256、384、512 | 派生出的密钥可以存储到HUKS或者直接返回明文 | +| PBKDF2/SHA256
PBKDF2/SHA384
PBKDF2/SHA512 | 8+ | 算法:AES、HMAC、SM4 长度:256、384、512 | 派生出的密钥可以存储到HUKS或者直接返回明文 | + +### 密钥证明 + +| 算法                    |API级别 | 备注 | +| ------------------ | :-----: | ------------------------------------------------------------ | +| RSA | 9+ | 仅支持Padding为PSS的密钥 | +| ECC | 9+ | | +| X25519 | 9+ | | + +## 密钥材料格式 +针对不同密码算法的密钥对、公钥、私钥,HUKS定义了一套密钥材料格式。 + +### 密钥对材料 +**密钥对材料 = 密钥对材料Header + 密钥对材料原文** + +以RSA密钥为例,应用需要申请一个Uint8Array,按照RSA密钥对材料内存格式,将各个变量赋值到对应的位置: + +**图4** RSA密钥材料内存结构 + +![huks_keymaterial_struct](figures/huks_keymaterial_struct.png) + +```ts +let rsa2048KeyPairMaterial = new Uint8Array([ + 0x01, 0x00, 0x00, 0x00, // 密钥算法:huks.HuksKeyAlg.HUKS_ALG_RSA = 1 + 0x00, 0x08, 0x00, 0x00, // 密钥大小(比特):2048 + 0x00, 0x01, 0x00, 0x00, // 模数n长度(字节):256 + 0x03, 0x00, 0x00, 0x00, // 公钥指数e长度(字节):3 + 0x00, 0x01, 0x00, 0x00, // 私钥指数d长度(字节):256 + // 模数n + 0xc5, 0x35, 0x62, 0x48, 0xc4, 0x92, 0x87, 0x73, 0x0d, 0x42, 0x96, 0xfc, 0x7b, 0x11, 0x05, 0x06, + 0x0f, 0x8d, 0x66, 0xc1, 0x0e, 0xad, 0x37, 0x44, 0x92, 0x95, 0x2f, 0x6a, 0x55, 0xba, 0xec, 0x1d, + 0x54, 0x62, 0x0a, 0x4b, 0xd3, 0xc7, 0x05, 0xe4, 0x07, 0x40, 0xd9, 0xb7, 0xc2, 0x12, 0xcb, 0x9a, + 0x90, 0xad, 0xe3, 0x24, 0xe8, 0x5e, 0xa6, 0xf8, 0xd0, 0x6e, 0xbc, 0xd1, 0x69, 0x7f, 0x6b, 0xe4, + 0x2b, 0x4e, 0x1a, 0x65, 0xbb, 0x73, 0x88, 0x6b, 0x7c, 0xaf, 0x7e, 0xd0, 0x47, 0x26, 0xeb, 0xa5, + 0xbe, 0xd6, 0xe8, 0xee, 0x9c, 0xa5, 0x66, 0xa5, 0xc9, 0xd3, 0x25, 0x13, 0xc4, 0x0e, 0x6c, 0xab, + 0x50, 0xb6, 0x50, 0xc9, 0xce, 0x8f, 0x0a, 0x0b, 0xc6, 0x28, 0x69, 0xe9, 0x83, 0x69, 0xde, 0x42, + 0x56, 0x79, 0x7f, 0xde, 0x86, 0x24, 0xca, 0xfc, 0xaa, 0xc0, 0xf3, 0xf3, 0x7f, 0x92, 0x8e, 0x8a, + 0x12, 0x52, 0xfe, 0x50, 0xb1, 0x5e, 0x8c, 0x01, 0xce, 0xfc, 0x7e, 0xf2, 0x4f, 0x5f, 0x03, 0xfe, + 0xa7, 0xcd, 0xa1, 0xfc, 0x94, 0x52, 0x00, 0x8b, 0x9b, 0x7f, 0x09, 0xab, 0xa8, 0xa4, 0xf5, 0xb4, + 0xa5, 0xaa, 0xfc, 0x72, 0xeb, 0x17, 0x40, 0xa9, 0xee, 0xbe, 0x8f, 0xc2, 0xd1, 0x80, 0xc2, 0x0d, + 0x44, 0xa9, 0x59, 0x44, 0x59, 0x81, 0x3b, 0x5d, 0x4a, 0xde, 0xfb, 0xae, 0x24, 0xfc, 0xa3, 0xd9, + 0xbc, 0x57, 0x55, 0xc2, 0x26, 0xbc, 0x19, 0xa7, 0x9a, 0xc5, 0x59, 0xa3, 0xee, 0x5a, 0xef, 0x41, + 0x80, 0x7d, 0xf8, 0x5e, 0xc1, 0x1d, 0x32, 0x38, 0x41, 0x5b, 0xb6, 0x92, 0xb8, 0xb7, 0x03, 0x0d, + 0x3e, 0x59, 0x0f, 0x1c, 0xb3, 0xe1, 0x2a, 0x95, 0x1a, 0x3b, 0x50, 0x4f, 0xc4, 0x1d, 0xcf, 0x73, + 0x7c, 0x14, 0xca, 0xe3, 0x0b, 0xa7, 0xc7, 0x1a, 0x41, 0x4a, 0xee, 0xbe, 0x1f, 0x43, 0xdd, 0xf9, + // 公钥指数e + 0x01, 0x00, 0x01, + // 私钥指数d + 0x88, 0x4b, 0x82, 0xe7, 0xe3, 0xe3, 0x99, 0x75, 0x6c, 0x9e, 0xaf, 0x17, 0x44, 0x3e, 0xd9, 0x07, + 0xfd, 0x4b, 0xae, 0xce, 0x92, 0xc4, 0x28, 0x44, 0x5e, 0x42, 0x79, 0x08, 0xb6, 0xc3, 0x7f, 0x58, + 0x2d, 0xef, 0xac, 0x4a, 0x07, 0xcd, 0xaf, 0x46, 0x8f, 0xb4, 0xc4, 0x43, 0xf9, 0xff, 0x5f, 0x74, + 0x2d, 0xb5, 0xe0, 0x1c, 0xab, 0xf4, 0x6e, 0xd5, 0xdb, 0xc8, 0x0c, 0xfb, 0x76, 0x3c, 0x38, 0x66, + 0xf3, 0x7f, 0x01, 0x43, 0x7a, 0x30, 0x39, 0x02, 0x80, 0xa4, 0x11, 0xb3, 0x04, 0xd9, 0xe3, 0x57, + 0x23, 0xf4, 0x07, 0xfc, 0x91, 0x8a, 0xc6, 0xcc, 0xa2, 0x16, 0x29, 0xb3, 0xe5, 0x76, 0x4a, 0xa8, + 0x84, 0x19, 0xdc, 0xef, 0xfc, 0xb0, 0x63, 0x33, 0x0b, 0xfa, 0xf6, 0x68, 0x0b, 0x08, 0xea, 0x31, + 0x52, 0xee, 0x99, 0xef, 0x43, 0x2a, 0xbe, 0x97, 0xad, 0xb3, 0xb9, 0x66, 0x7a, 0xae, 0xe1, 0x8f, + 0x57, 0x86, 0xe5, 0xfe, 0x14, 0x3c, 0x81, 0xd0, 0x64, 0xf8, 0x86, 0x1a, 0x0b, 0x40, 0x58, 0xc9, + 0x33, 0x49, 0xb8, 0x99, 0xc6, 0x2e, 0x94, 0x70, 0xee, 0x09, 0x88, 0xe1, 0x5c, 0x4e, 0x6c, 0x22, + 0x72, 0xa7, 0x2a, 0x21, 0xdd, 0xd7, 0x1d, 0xfc, 0x63, 0x15, 0x0b, 0xde, 0x06, 0x9c, 0xf3, 0x28, + 0xf3, 0xac, 0x4a, 0xa8, 0xb5, 0x50, 0xca, 0x9b, 0xcc, 0x0a, 0x04, 0xfe, 0x3f, 0x98, 0x68, 0x81, + 0xac, 0x24, 0x53, 0xea, 0x1f, 0x1c, 0x6e, 0x5e, 0xca, 0xe8, 0x31, 0x0d, 0x08, 0x12, 0xf3, 0x26, + 0xf8, 0x5e, 0xeb, 0x10, 0x27, 0xae, 0xaa, 0xc3, 0xad, 0x6c, 0xc1, 0x89, 0xdb, 0x7d, 0x5a, 0x12, + 0x55, 0xad, 0x11, 0x19, 0xa1, 0xa9, 0x8f, 0x0b, 0x6d, 0x78, 0x8d, 0x1c, 0xdf, 0xe5, 0x63, 0x82, + 0x0b, 0x7d, 0x23, 0x04, 0xb4, 0x75, 0x8c, 0xed, 0x77, 0xfc, 0x1a, 0x85, 0x29, 0x11, 0xe0, 0x61, + ]); +``` + +其中,密钥算法的值取自枚举类[HuksKeyAlg](../reference/apis/js-apis-huks.md#hukskeyalg)。 + +- **RSA密钥对材料格式:** + | 密钥算法 | 密钥大小 | 模数n长度Ln | 公钥指数e长度Le | 私钥指数d长度Ld | n | e | d | + | :----: |:----:|:----:|:----:|:----:|:----:|:----:|:----:| + |4字节|4字节|4字节|4字节|4字节|Ln字节 |Le字节 |Ld字节 | + + +- **ECC密钥对材料格式:** + | 密钥算法 | 密钥大小 | 坐标x长度Lx | 坐标y长度Ly | 坐标z长度Lz | x | y | z | + | :----: |:----:|:----:|:----:|:----:|:----:|:----:|:----:| + |4字节|4字节|4字节|4字节|4字节|Lx字节 |Ly字节 |Lz字节 | + + +- **DSA密钥对材料格式:** + | 密钥算法 | 密钥大小 | 私钥x长度Lx | 公钥y长度Ly | 素数p长度Lp | 素因子q长度Lq | g长度Lg | x | y | p | q | g | + | :----: |:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:| + |4字节|4字节|4字节|4字节|4字节|4字节|4字节|Lx字节 |Ly字节 |Lp字节 |Lq字节 |Lg字节 | + + +- **DH密钥对材料格式:** + | 密钥算法 | 密钥大小 | 公钥pk长度Lpk | 私钥sk长度Lsk | 保留字段 | pk | sk | + |:----:|:----:|:----:|:----:|:----:|:----:|:----:| + |4字节|4字节|4字节|4字节|4字节|Lpk字节 |Lsk字节 | + + +- **Curve25519密钥对材料格式:** + | 密钥算法 | 密钥大小 | 公钥pk长度Lpk | 私钥sk长度Lsk | 保留字段 | pk | sk | + |:----:|:----:|:----:|:----:|:----:|:----:|:----:| + |4字节|4字节|4字节|4字节|4字节|Lpk字节 |Lsk字节 | + + +### 公钥材料 + +在公钥导出/导入时,密钥材料采用标准的X.509规范的DER格式封装。 + +如下是一个DER编码的ECC公钥: +```ts +let eccP256PubKey = new Uint8Array([ + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xc0, 0xfe, 0x1c, 0x67, 0xde, + 0x86, 0x0e, 0xfb, 0xaf, 0xb5, 0x85, 0x52, 0xb4, 0x0e, 0x1f, 0x6c, 0x6c, 0xaa, 0xc5, 0xd9, 0xd2, + 0x4d, 0xb0, 0x8a, 0x72, 0x24, 0xa1, 0x99, 0xaf, 0xfc, 0x3e, 0x55, 0x5a, 0xac, 0x99, 0x3d, 0xe8, + 0x34, 0x72, 0xb9, 0x47, 0x9c, 0xa6, 0xd8, 0xfb, 0x00, 0xa0, 0x1f, 0x9f, 0x7a, 0x41, 0xe5, 0x44, + 0x3e, 0xb2, 0x76, 0x08, 0xa2, 0xbd, 0xe9, 0x41, 0xd5, 0x2b, 0x9e]); +``` + +### 私钥材料 + +复用密钥对的材料格式,私钥材料的封装是把密钥对材料Header中的公钥部分的长度字段置0,同时密钥对材料原文部分拼接私钥材料即可。 + +**私钥材料 = 密钥对材料Header + 私钥材料原文** + +以RSA私钥材料为例: + +![huks_keymaterial_rsa_priv_struct](figures/huks_keymaterial_rsa_priv_struct.png) + +```ts +let rsa2048PrivateKeyMaterial = new Uint8Array([ + 0x01, 0x00, 0x00, 0x00, // 密钥算法:huks.HuksKeyAlg.HUKS_ALG_RSA = 1 + 0x00, 0x08, 0x00, 0x00, // 密钥大小(比特):2048 + 0x00, 0x01, 0x00, 0x00, // 模数n长度(字节):256 + 0x00, 0x00, 0x00, 0x00, // 公钥指数e长度(字节):0 + 0x00, 0x01, 0x00, 0x00, // 私钥指数d长度(字节):256 + // 模数n + 0xc5, 0x35, 0x62, 0x48, 0xc4, 0x92, 0x87, 0x73, 0x0d, 0x42, 0x96, 0xfc, 0x7b, 0x11, 0x05, 0x06, + 0x0f, 0x8d, 0x66, 0xc1, 0x0e, 0xad, 0x37, 0x44, 0x92, 0x95, 0x2f, 0x6a, 0x55, 0xba, 0xec, 0x1d, + 0x54, 0x62, 0x0a, 0x4b, 0xd3, 0xc7, 0x05, 0xe4, 0x07, 0x40, 0xd9, 0xb7, 0xc2, 0x12, 0xcb, 0x9a, + 0x90, 0xad, 0xe3, 0x24, 0xe8, 0x5e, 0xa6, 0xf8, 0xd0, 0x6e, 0xbc, 0xd1, 0x69, 0x7f, 0x6b, 0xe4, + 0x2b, 0x4e, 0x1a, 0x65, 0xbb, 0x73, 0x88, 0x6b, 0x7c, 0xaf, 0x7e, 0xd0, 0x47, 0x26, 0xeb, 0xa5, + 0xbe, 0xd6, 0xe8, 0xee, 0x9c, 0xa5, 0x66, 0xa5, 0xc9, 0xd3, 0x25, 0x13, 0xc4, 0x0e, 0x6c, 0xab, + 0x50, 0xb6, 0x50, 0xc9, 0xce, 0x8f, 0x0a, 0x0b, 0xc6, 0x28, 0x69, 0xe9, 0x83, 0x69, 0xde, 0x42, + 0x56, 0x79, 0x7f, 0xde, 0x86, 0x24, 0xca, 0xfc, 0xaa, 0xc0, 0xf3, 0xf3, 0x7f, 0x92, 0x8e, 0x8a, + 0x12, 0x52, 0xfe, 0x50, 0xb1, 0x5e, 0x8c, 0x01, 0xce, 0xfc, 0x7e, 0xf2, 0x4f, 0x5f, 0x03, 0xfe, + 0xa7, 0xcd, 0xa1, 0xfc, 0x94, 0x52, 0x00, 0x8b, 0x9b, 0x7f, 0x09, 0xab, 0xa8, 0xa4, 0xf5, 0xb4, + 0xa5, 0xaa, 0xfc, 0x72, 0xeb, 0x17, 0x40, 0xa9, 0xee, 0xbe, 0x8f, 0xc2, 0xd1, 0x80, 0xc2, 0x0d, + 0x44, 0xa9, 0x59, 0x44, 0x59, 0x81, 0x3b, 0x5d, 0x4a, 0xde, 0xfb, 0xae, 0x24, 0xfc, 0xa3, 0xd9, + 0xbc, 0x57, 0x55, 0xc2, 0x26, 0xbc, 0x19, 0xa7, 0x9a, 0xc5, 0x59, 0xa3, 0xee, 0x5a, 0xef, 0x41, + 0x80, 0x7d, 0xf8, 0x5e, 0xc1, 0x1d, 0x32, 0x38, 0x41, 0x5b, 0xb6, 0x92, 0xb8, 0xb7, 0x03, 0x0d, + 0x3e, 0x59, 0x0f, 0x1c, 0xb3, 0xe1, 0x2a, 0x95, 0x1a, 0x3b, 0x50, 0x4f, 0xc4, 0x1d, 0xcf, 0x73, + 0x7c, 0x14, 0xca, 0xe3, 0x0b, 0xa7, 0xc7, 0x1a, 0x41, 0x4a, 0xee, 0xbe, 0x1f, 0x43, 0xdd, 0xf9, + // 私钥指数d + 0x88, 0x4b, 0x82, 0xe7, 0xe3, 0xe3, 0x99, 0x75, 0x6c, 0x9e, 0xaf, 0x17, 0x44, 0x3e, 0xd9, 0x07, + 0xfd, 0x4b, 0xae, 0xce, 0x92, 0xc4, 0x28, 0x44, 0x5e, 0x42, 0x79, 0x08, 0xb6, 0xc3, 0x7f, 0x58, + 0x2d, 0xef, 0xac, 0x4a, 0x07, 0xcd, 0xaf, 0x46, 0x8f, 0xb4, 0xc4, 0x43, 0xf9, 0xff, 0x5f, 0x74, + 0x2d, 0xb5, 0xe0, 0x1c, 0xab, 0xf4, 0x6e, 0xd5, 0xdb, 0xc8, 0x0c, 0xfb, 0x76, 0x3c, 0x38, 0x66, + 0xf3, 0x7f, 0x01, 0x43, 0x7a, 0x30, 0x39, 0x02, 0x80, 0xa4, 0x11, 0xb3, 0x04, 0xd9, 0xe3, 0x57, + 0x23, 0xf4, 0x07, 0xfc, 0x91, 0x8a, 0xc6, 0xcc, 0xa2, 0x16, 0x29, 0xb3, 0xe5, 0x76, 0x4a, 0xa8, + 0x84, 0x19, 0xdc, 0xef, 0xfc, 0xb0, 0x63, 0x33, 0x0b, 0xfa, 0xf6, 0x68, 0x0b, 0x08, 0xea, 0x31, + 0x52, 0xee, 0x99, 0xef, 0x43, 0x2a, 0xbe, 0x97, 0xad, 0xb3, 0xb9, 0x66, 0x7a, 0xae, 0xe1, 0x8f, + 0x57, 0x86, 0xe5, 0xfe, 0x14, 0x3c, 0x81, 0xd0, 0x64, 0xf8, 0x86, 0x1a, 0x0b, 0x40, 0x58, 0xc9, + 0x33, 0x49, 0xb8, 0x99, 0xc6, 0x2e, 0x94, 0x70, 0xee, 0x09, 0x88, 0xe1, 0x5c, 0x4e, 0x6c, 0x22, + 0x72, 0xa7, 0x2a, 0x21, 0xdd, 0xd7, 0x1d, 0xfc, 0x63, 0x15, 0x0b, 0xde, 0x06, 0x9c, 0xf3, 0x28, + 0xf3, 0xac, 0x4a, 0xa8, 0xb5, 0x50, 0xca, 0x9b, 0xcc, 0x0a, 0x04, 0xfe, 0x3f, 0x98, 0x68, 0x81, + 0xac, 0x24, 0x53, 0xea, 0x1f, 0x1c, 0x6e, 0x5e, 0xca, 0xe8, 0x31, 0x0d, 0x08, 0x12, 0xf3, 0x26, + 0xf8, 0x5e, 0xeb, 0x10, 0x27, 0xae, 0xaa, 0xc3, 0xad, 0x6c, 0xc1, 0x89, 0xdb, 0x7d, 0x5a, 0x12, + 0x55, 0xad, 0x11, 0x19, 0xa1, 0xa9, 0x8f, 0x0b, 0x6d, 0x78, 0x8d, 0x1c, 0xdf, 0xe5, 0x63, 0x82, + 0x0b, 0x7d, 0x23, 0x04, 0xb4, 0x75, 0x8c, 0xed, 0x77, 0xfc, 0x1a, 0x85, 0x29, 0x11, 0xe0, 0x61, + ]); +``` + diff --git a/zh-cn/application-dev/security/huks-guidelines.md b/zh-cn/application-dev/security/huks-guidelines.md index 4e5cd92fe3..fb0922ed99 100644 --- a/zh-cn/application-dev/security/huks-guidelines.md +++ b/zh-cn/application-dev/security/huks-guidelines.md @@ -1,186 +1,361 @@ -# HUKS开发指导 -HUKS(OpenHarmony Universal KeyStore,OpenHarmony通用密钥库系统)向应用提供密钥库能力,包括密钥管理及密钥的密码学操作等功能。HUKS所管理的密钥可以由应用导入或者由应用调用HUKS接口生成。 +# 通用密钥库开发指导 + +## 生成新密钥 + +HUKS提供为业务安全随机生成密钥的能力。通过HUKS生成的密钥,密钥的全生命周期明文不会出安全环境,能保证任何人都无法接触获取到密钥的明文。即使生成密钥的业务自身,后续也只能通过HUKS提供的接口请求执行密钥操作,获取操作结果,但无法接触到密钥自身。 + + +**开发步骤** + +生成密钥时使用[huks.generateKeyItem(keyAlias,options,callback)](../reference/apis/js-apis-huks.md#huksgeneratekeyitem9)方法,传入keyAlias作为密钥别名,传入options包含该密钥的属性集,传入callback用于回调异步结果。关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 + + + +1. 确定密钥别名; +2. 初始化密钥属性集:通过[HuksParam](../reference/apis/js-apis-huks.md#huksparam)封装密钥属性,搭配Array组成密钥属性集,并赋值给[HuksOptions](../reference/apis/js-apis-huks.md#huksoptions)(properties字段),其中必须包含[HuksKeyAlg](../reference/apis/js-apis-huks.md#hukskeyalg),[HuksKeySize](../reference/apis/js-apis-huks.md#hukskeysize),[HuksKeyPurpose](../reference/apis/js-apis-huks.md#hukskeypurpose)属性; +3. 将密钥别名与密钥参数集作为参数传入,生成密钥。 + + > **说明** > -> 本开发指导基于API version 9,仅适用于ArkTS语言开发 - -### **前提条件** +> 存储的 keyAlias 密钥别名最大为64字节 -在使用HUKS的接口开发前,需要引入HUKS模块 +**代码示例:** ```ts -import huks from '@ohos.security.huks' +/* + * 以下以生成DH密钥的Callback操作使用为例 + */ +import huks from '@ohos.security.huks'; + +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let keyAlias = 'dh_key'; +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_DH +} +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE +} +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_DH_KEY_SIZE_2048 +} +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 +} +let huksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) +} + +/* + * 生成密钥 + */ +function generateKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { + return new Promise((resolve, reject) => { + try { + huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throw (error); + } + }); +} + +async function publicGenKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) { + console.info(`enter callback generateKeyItem`); + try { + await generateKeyItem(keyAlias, huksOptions) + .then((data) => { + console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + }); + } catch (error) { + console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + } +} + + +async function TestGenKey() { + await publicGenKeyFunc(keyAlias, huksOptions); +} ``` -### 密钥生成 +## 导入外部密钥 +如果密钥是在HUKS外部生成(比如应用间协商生成、服务器端生成),应用可以将密钥导入到HUKS托管。HUKS支持直接将密钥明文导入到HUKS,但是明文导入会导致密钥暴露在REE内存中,一般适用于轻量级设备或低安业务。对于高安敏感业务,HUKS还提供了安全导入密钥的能力,允许业务自己生成密钥,并通过与处于安全环境中的HUKS建立端到端的加密传输通道,将密钥安全加密导入到HUKS中,确保导入传入过程中密钥不被泄露。 + +与生成密钥一样,密钥一旦导入到HUKS中,密钥的生命周期明文不出安全环境,同样能保证任何人都无法接触获取到密钥的明文。 -通过指定别名和密钥参数的方式,使用HUKS生成应用需要的密钥。生成密钥时支持动态参数指定,分为必选参数和可选参数。其中,必选参数表示在生成密钥时必须传入;可选参数表示在生成密钥时可以传入,也可以不传入。 -> 说明: -> -> 1、生成密钥时传入的参数是对密钥使用的一种约束,规范了密钥使用的范围。如果在使用时传入的参数和生成密钥时传入的参数相悖,则该参数会校验失败。 -> -> 2、如果可选参数没有在生成密钥阶段时传入,但算法又需要该参数,在使用阶段必须传入。 -**支持生成的密钥类型:** +### 明文导入 -这里罗列了密钥生成过程中支持的必选参数,包括密钥算法、密钥长度、密钥用途。详见下文算法的使用过程。 -| HUKS_ALG_ALGORITHM | HUKS_ALG_KEY_SIZE | HUKS_ALG_PURPOSE | -| ------------------ | :----------------------------------------------------------- | ------------------------------------------------------------ | -| HUKS_ALG_RSA | HUKS_RSA_KEY_SIZE_512 HUKS_RSA_KEY_SIZE_768 HUKS_RSA_KEY_SIZE_1024 HUKS_RSA_KEY_SIZE_2048 HUKS_RSA_KEY_SIZE_3072 HUKS_RSA_KEY_SIZE_4096 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | -| HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT HUKS_KEY_PURPOSE_DERIVE | -| HUKS_ALG_ECC | HUKS_ECC_KEY_SIZE_224 HUKS_ECC_KEY_SIZE_256 HUKS_ECC_KEY_SIZE_384 HUKS_ECC_KEY_SIZE_521 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | -| HUKS_ALG_X25519 | HUKS_CURVE25519_KEY_SIZE_256 | HUKS_KEY_PURPOSE_AGREE | -| HUKS_ALG_ED25519 | HUKS_CURVE25519_KEY_SIZE_256 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | -| HUKS_ALG_DSA | HUKS_RSA_KEY_SIZE_1024 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | -| HUKS_ALG_DH | HUKS_DH_KEY_SIZE_2048 HUKS_DH_KEY_SIZE_3072 HUKS_DH_KEY_SIZE_4096 | HUKS_KEY_PURPOSE_AGREE | -| HUKS_ALG_ECDH | HUKS_ECC_KEY_SIZE_224 HUKS_ECC_KEY_SIZE_256 HUKS_ECC_KEY_SIZE_384 HUKS_ECC_KEY_SIZE_521 | HUKS_KEY_PURPOSE_AGREE | -| HUKS_ALG_SM2 | HUKS_SM2_KEY_SIZE_256 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | -| HUKS_ALG_SM4 | HUKS_SM4_KEY_SIZE_128 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | +导入明文密钥时使用[huks.importKeyItem(keyAlias,options,callback)](../reference/apis/js-apis-huks.md#huksimportkeyitem9)方法,传入keyAlias作为密钥别名,传入options,其中必须包含密钥材料和密钥属性集,传入callback用于回调异步结果。关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 -在使用示例前,需要先了解几个预先定义的变量: -| 参数名 | 类型 | 必填 | 说明 | -| ---------------- | ----------- | ---- | ------------------------------------------------------------ | -| genKeyAlias | string | 是 | 生成密钥的别名。 | -| genKeyProperties | HuksOptions | 是 | 用于存放生成key所需TAG。其中密钥使用的算法、密钥用途、密钥长度为必选参数。 | +1. 确定密钥别名; +2. 封装密钥材料和密钥属性集:密钥材料须符合[HUKS密钥材料格式](./huks-appendix.md#密钥材料格式)并以Uint8Array形式赋值给[HuksOptions](../reference/apis/js-apis-huks.md#huksoptions)的inData字段;另外,通过[HuksParam](../reference/apis/js-apis-huks.md#huksparam)封装密钥属性,搭配Array组成密钥属性集赋值给properties字段,属性集中必须包含[HuksKeyAlg](../reference/apis/js-apis-huks.md#hukskeyalg),[HuksKeySize](../reference/apis/js-apis-huks.md#hukskeysize),[HuksKeyPurpose](../reference/apis/js-apis-huks.md#hukskeypurpose)属性; +3. 导入密钥。 + -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 + +**代码示例:** ```ts -/* 以生成ECC256密钥为例 */ -let keyAlias = 'keyAlias'; +/* + * 以导入AES256密钥为例 + */ + +/* 密钥 */ +let plainTextSize32 = new Uint8Array([ + 0xfb, 0x8b, 0x9f, 0x12, 0xa0, 0x83, 0x19, 0xbe, 0x6a, 0x6f, 0x63, 0x2a, 0x7c, 0x86, 0xba, 0xca, + 0x64, 0x0b, 0x88, 0x96, 0xe2, 0xfa, 0x77, 0xbc, 0x71, 0xe3, 0x0f, 0x0f, 0x9e, 0x3c, 0xe5, 0xf9 +]); + +/* + * 确定密钥别名 + */ +let keyAlias = 'AES256Alias_sample'; + +/* + * 封装密钥属性集和密钥材料 + */ let properties = new Array(); -//必选参数 properties[0] = { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_ECC + value: huks.HuksKeyAlg.HUKS_ALG_AES }; -//必选参数 properties[1] = { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 + value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 }; -//必选参数 properties[2] = { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY -}; -//可选参数 -properties[3] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT }; let options = { - properties: properties + properties: properties, + inData: plainTextSize32 }; + +/* + * 导入密钥 + */ try { - huks.generateKeyItem(keyAlias, options, function (error, data) { + huks.importKeyItem(keyAlias, options, function (error, data) { if (error) { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); } else { - console.info(`callback: generateKeyItem key success`); + console.info(`callback: importKeyItem success`); } }); } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: importKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); +} +``` + +**调测验证** + +验证时查询密钥是否存在,如密钥存在即表示生成密钥成功。 + +**代码示例:** + +```ts +import huks from '@ohos.security.huks'; + +let keyAlias = 'AES256Alias_sample'; +let isKeyExist; + +let keyProperties = new Array(); +keyProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES, +} +let huksOptions = { + properties: keyProperties, // 非空填充 + inData: new Uint8Array(new Array()) // 非空填充 +} +try { + huks.isKeyItemExist(keyAlias, huksOptions, function (error, data) { + if (error) { + console.error(`callback: isKeyItemExist failed, code: ${error.code}, msg: ${error.message}`); + } else { + if (data !== null && data.valueOf() !== null) { + isKeyExist = data.valueOf(); + console.info(`callback: isKeyItemExist success, isKeyExist = ${isKeyExist}`); + } + } + }); +} catch (error) { + console.error(`callback: isKeyItemExist input arg invalid, code: ${error.code}, msg: ${error.message}`); } ``` +### 加密导入 +相比明文导入,加密导入步骤更多,密钥材料更复杂,此章节将展示开发过程中关键的开发流程和密钥材料数据结构。下图是加密导入的基本开发流程。 -### 密钥导入导出 -HUKS支持非对称密钥的公钥导出能力,开发者可以通过密钥别名导出应用自己密钥对的公钥,只允许导出属于应用自己的密钥对的公钥。 -HUKS支持密钥从外部导入的能力。密钥导入后全生命周期存在HUKS中,不能再被导出(非对称密钥的公钥除外)。 如果该别名的密钥已经存在,新导入的密钥将覆盖已经存在的密钥。 +**图2** 加密导入开发流程 -开发步骤如下: +![huks_import_wrapped_key](figures/huks_import_wrapped_key.png) -1. 生成密钥。 -2. 导出密钥。 -3. 导入密钥。 -**支持导入的密钥类型:** -AES128, AES192, AES256, RSA512, RSA768, RSA1024, RSA2048, RSA3072, RSA4096, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512, ECC224, ECC256, ECC384, ECC521, Curve25519, DSA, SM2, SM3, SM4. -**支持导出的密钥类型:** +**接口说明** -RSA512, RSA768, RSA1024, RSA2048, RSA3072, RSA4096, ECC224, ECC256, ECC384, ECC521, Curve25519, DSA, SM2. +根据开发流程,在导入加密密钥过程中,需要依次调用HUKS的生成密钥、导出公钥、导入加密密钥、删除密钥接口。 +| 接口名 | 描述 | +| -------------------------------------- | ----------------------------| +|generateKeyItem(keyAlias: string, options: HuksOptions, callback: AsyncCallback\) : void| 生成新密钥| +|exportKeyItem(keyAlias: string, options: HuksOptions, callback: AsyncCallback) : void| 导出密钥对的公钥| +|importWrappedKeyItem(keyAlias: string, wrappingKeyAlias: string, options: HuksOptions, callback: AsyncCallback) : void|导入加密密钥| +|deleteKeyItem(keyAlias: string, options: HuksOptions, callback: AsyncCallback) : void|删除密钥| -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 +需要注意的是,导出密钥接口返回的公钥明文材料是按照**X.509**格式封装,导入加密密钥接口中的密钥材料需满足**LengthData-Data** 的格式封装。具体,应用需要申请一个Uint8Array按照以下表格中的顺序依次封装。 + +**表2** 加密密钥材料格式 -在使用示例前,需要先了解几个预先定义的变量: +| 内容 | 业务公钥长度Lpk2 | 业务公钥pk2 | k2加密参数AAD2长度LAAD2 | k2加密参数AAD2 | k2加密参数Nonce2长度LNonce2 | k2加密参数Nonce2 | +| :--: |:----:|:----: |:----: | :----: | :----:|:----:| +|长度| 4字节 |Lpk2字节| 4字节 | LAAD2字节 | 4字节 | LNonce2字节 | +| 内容 | k2加密参数AEAD2长度LAEAD2 | k2加密参数AEAD2 | k3密文长度Lk3_enc | k3密文k3_enc | k3加密参数AAD3长度LAAD3 | k3加密参数AAD3 | +|长度| 4字节 |LAEAD2字节| 4字节 | Lk3_enc字节 | 4字节 | LAAD3字节 | +| 内容| k3加密参数Nonce3长度LNonce3 | k3加密参数Nonce3 | k3加密参数AEAD3长度LAEAD3 | k3加密参数AEAD3 | **密钥明文材料长度** 的长度Lk1'_size | 密钥明文材料长度k1'_size | +|长度| 4字节 |LNonce3字节| 4字节 | LAEAD3字节 | 4字节 | Lk1'_size字节 | +|内容|k1'密文长度Lk1'_enc| k1'密文k1'_enc| | | | | +|长度| 4字节 |Lk1'_enc字节| | | | | -| 参数名 | 类型 | 必填 | 说明 | -| -------------- | ----------- | ---- | ------------------------ | -| exportKeyAlias | string | 是 | 生成密钥别名。 | -| importKeyAlias | string | 是 | 导入密钥别名。 | -| huksOptions | HuksOptions | 是 | 用于存放生成key所需TAG。 | -| encryptOptions | HuksOptions | 是 | 用于存放导入key所需TAG。 | +**开发步骤** -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +这里主要展示涉及调用HUKS的开发样例(使用ECDH密钥协商套件),部分在业务本地执行的步骤不在这里展示详细样例。 +1. 转换成HUKS格式的密钥材料 +2. 生成加密导入用途的密钥 +3. 导出公钥材料 +4. 封装加密导入密钥材料 +5. 导入封装的加密密钥材料 +6. 删除用于加密导入的密钥 -**示例:** +**代码示例:** ```ts -/* 以导出RSA512密钥及导入DH2048密钥、RSA512密钥、x25519密钥、ECC256密钥为例 */ +/* + * 以下以SM2密钥的Callback操作验证为例 + */ import huks from '@ohos.security.huks'; -function StringToUint8Array(str) { - let arr = []; - for (let i = 0, j = str.length; i < j; ++i) { - arr.push(str.charCodeAt(i)); - } - return new Uint8Array(arr); -} +/* + * 确定密钥别名 + */ +let importAlias = "importAlias"; +let wrapAlias = "wrappingKeyAlias"; +let exportKey; -function Uint8ArrayToString(fileData) { - let dataString = ''; - for (let i = 0; i < fileData.length; i++) { - dataString += String.fromCharCode(fileData[i]); - } - return dataString; -} +/* + * 加密导入用途的密钥材料原文:转换成HUKS ECC-P-256密钥对格式的密钥材料 + */ +let inputEccPair = new Uint8Array([ + 0x02, 0x00, 0x00, 0x00, // 密钥算法:huks.HuksKeyAlg.HUKS_ALG_ECC = 2 + 0x00, 0x01, 0x00, 0x00, // 密钥大小(比特):256 + 0x20, 0x00, 0x00, 0x00, // 坐标x长度(字节):32 + 0x20, 0x00, 0x00, 0x00, // 坐标y长度(字节):32 + 0x20, 0x00, 0x00, 0x00, // 坐标z长度(字节):32 + // 坐标x + 0xa5, 0xb8, 0xa3, 0x78, 0x1d, 0x6d, 0x76, 0xe0, 0xb3, 0xf5, 0x6f, 0x43, 0x9d, 0xcf, 0x60, 0xf6, + 0x0b, 0x3f, 0x64, 0x45, 0xa8, 0x3f, 0x1a, 0x96, 0xf1, 0xa1, 0xa4, 0x5d, 0x3e, 0x2c, 0x3f, 0x13, + // 坐标y + 0xd7, 0x81, 0xf7, 0x2a, 0xb5, 0x8d, 0x19, 0x3d, 0x9b, 0x96, 0xc7, 0x6a, 0x10, 0xf0, 0xaa, 0xbc, + 0x91, 0x6f, 0x4d, 0xa7, 0x09, 0xb3, 0x57, 0x88, 0x19, 0x6f, 0x00, 0x4b, 0xad, 0xee, 0x34, 0x35, + // 坐标z + 0xfb, 0x8b, 0x9f, 0x12, 0xa0, 0x83, 0x19, 0xbe, 0x6a, 0x6f, 0x63, 0x2a, 0x7c, 0x86, 0xba, 0xca, + 0x64, 0x0b, 0x88, 0x96, 0xe2, 0xfa, 0x77, 0xbc, 0x71, 0xe3, 0x0f, 0x0f, 0x9e, 0x3c, 0xe5, 0xf9 + ]); -function Uint32ToUint8(value) { - let arr = new Uint8Array(4 * value.length); - for (let i = 0, j = value.length; i < j; i++) { - arr[i * 4+3] = (value[i] >> 24) & 0xFF; - arr[i * 4+2] = (value[i] >> 16) & 0xFF; - arr[i * 4+1] = (value[i] >> 8) & 0xFF; - arr[i*4] = (value[i]) & 0xFF; - } - return arr; -} +/* + * 封装密钥属性参数集 + */ +// 生成加密导入用途的密钥的属性集 +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_ECC +}; +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 +}; +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_UNWRAP +}; +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 +}; +properties[4] = { + tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, + value: huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR, +}; +let huksOptions = { + properties: properties, + inData: inputEccPair +}; -async function publicGenKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} +// 待导入密钥的属性集:AES256 +let importProperties = new Array(); +importProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES +}; +importProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 +}; +importProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT +}; +importProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC +}; +importProperties[4] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE +}; +importProperties[5] = { + tag: huks.HuksTag.HUKS_TAG_UNWRAP_ALGORITHM_SUITE, + value: huks.HuksUnwrapSuite.HUKS_UNWRAP_SUITE_ECDH_AES_256_GCM_NOPADDING // 使用“ECDH+AES256GCM”加密导入套件 +}; +let importOptions = { + properties: importProperties, + inData: new Uint8Array(new Array()) +}; -function generateKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { +// 导出加密导入用途的公钥 +function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -188,89 +363,111 @@ function generateKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { } }); } catch (error) { - throw (error); + throwObject.isThrow = true; + throw(error); } }); } -async function publicExportKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) { +async function publicExportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { console.info(`enter callback export`); + let throwObject = {isThrow: false}; try { - await exportKeyItem(keyAlias, huksOptions) - .then((data) => { + await exportKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); + exportKey = data.outData; }) .catch(error => { - console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function exportKeyItem(keyAlias: string, huksOptions: huks.HuksOptions): Promise { +// 此处用导入密钥来模拟“生成加密导入用途的密钥” +function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { - huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { + huks.importKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); - } else { + } else { resolve(data); } }); } catch (error) { - throw (error); + throwObject.isThrow = true; + throw(error); } }); } -async function publicImportKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) { +async function publicImportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { console.info(`enter promise importKeyItem`); + let throwObject = {isThrow: false}; try { - await importKeyItem(keyAlias, huksOptions) - .then((data) => { + await importKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { console.info(`callback: importKeyItem success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: importKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function importKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { +// 执行加密导入 +async function publicImportWrappedKey(keyAlias:string, wrappingKeyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback importWrappedKeyItem`); + var throwObject = {isThrow: false}; + try { + await importWrappedKeyItem(keyAlias, wrappingKeyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: importWrappedKeyItem success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: importWrappedKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: importWrappedKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + } +} + +function importWrappedKeyItem(keyAlias:string, wrappingKeyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { - huks.importKeyItem(keyAlias, huksOptions, function (error, data) { + huks.importWrappedKeyItem(keyAlias, wrappingKeyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { - resolve(data); + resolve(data); } }); } catch (error) { - throw (error); + throwObject.isThrow = true; + throw(error); } }); } -async function publicDeleteKeyFunc(keyAlias: string, huksOptions: huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); - try { - await deleteKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function deleteKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { +// 删除加密导入用途的密钥 +function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { @@ -281,549 +478,314 @@ function deleteKeyItem(keyAlias: string, huksOptions: huks.HuksOptions) { } }); } catch (error) { - throw (error); + throwObject.isThrow = true; + throw(error); } }); } -//导出RSA密钥 -async function testExportRsa() { - let exportKeyAlias = 'export_rsa_key'; - /* 集成生成密钥参数集 */ - let exportProperties = new Array(); - exportProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_RSA - } - exportProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT - } - exportProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_512 - } - exportProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_ECB - } - exportProperties[4] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_PKCS1_V1_5 - } - exportProperties[5] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - } - let huksOptions = { - properties: exportProperties, - inData: new Uint8Array(new Array()) +async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback deleteKeyItem`); + let throwObject = {isThrow: false}; + try { + await deleteKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: deletKeeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } +} - /* 生成密钥 */ - await publicGenKeyFunc(exportKeyAlias, huksOptions); +async function ImportWrappedKeyNormalTest() { + console.info(`enter ImportWrapKey test`); + /* + * 生成加密导入用途的密钥(此处使用导入进行模拟) + */ + await publicImportKeyFunc(wrapAlias, huksOptions); - /* 导出密钥 */ - await publicExportKeyFunc(exportKeyAlias, huksOptions); - await publicDeleteKeyFunc(exportKeyAlias, huksOptions); -} - -//DH密钥 -let g_dhPubData = new Uint8Array([ - 0x8a, 0xbf, 0x16, 0x67, 0x1b, 0x92, 0x4b, 0xf2, 0xe0, 0x02, 0xc5, 0x1f, 0x84, 0x00, 0xf8, 0x93, - 0x0f, 0x74, 0xe7, 0x0f, 0xba, 0x78, 0x30, 0xa8, 0x2d, 0x92, 0xef, 0x9b, 0x80, 0xeb, 0x76, 0xea, - 0x26, 0x74, 0x72, 0x63, 0x6a, 0x27, 0xc3, 0x8f, 0xcf, 0xbe, 0x82, 0xa2, 0x8b, 0xdc, 0x65, 0x58, - 0xe3, 0xff, 0x29, 0x97, 0xad, 0xb3, 0x4a, 0x2c, 0x50, 0x08, 0xb5, 0x68, 0xe1, 0x90, 0x5a, 0xdc, - 0x48, 0xb3, 0x6b, 0x7a, 0xce, 0x2e, 0x81, 0x3d, 0x38, 0x35, 0x59, 0xdc, 0x39, 0x8a, 0x97, 0xfe, - 0x20, 0x86, 0x20, 0xdb, 0x55, 0x38, 0x23, 0xca, 0xb5, 0x5b, 0x61, 0x00, 0xdc, 0x45, 0xe2, 0xa1, - 0xf4, 0x1e, 0x7b, 0x01, 0x7a, 0x84, 0x36, 0xa4, 0xa8, 0x1c, 0x0d, 0x3d, 0xde, 0x57, 0x66, 0x73, - 0x4e, 0xaf, 0xee, 0xb0, 0xb0, 0x69, 0x0c, 0x13, 0xba, 0x76, 0xff, 0x2e, 0xb6, 0x16, 0xf9, 0xfc, - 0xd6, 0x09, 0x5b, 0xc7, 0x37, 0x65, 0x84, 0xd5, 0x82, 0x8a, 0xd7, 0x5b, 0x57, 0xe3, 0x0e, 0x89, - 0xbe, 0x05, 0x05, 0x55, 0x2e, 0x9f, 0x94, 0x8a, 0x53, 0xdc, 0xb7, 0x00, 0xb2, 0x6a, 0x7b, 0x8e, - 0xdf, 0x6e, 0xa4, 0x6d, 0x13, 0xb6, 0xbc, 0xaa, 0x8e, 0x44, 0x11, 0x50, 0x32, 0x91, 0x56, 0xa2, - 0x22, 0x3f, 0x2f, 0x08, 0xbb, 0x4d, 0xbb, 0x69, 0xe6, 0xb1, 0xc2, 0x70, 0x79, 0x15, 0x54, 0xad, - 0x4a, 0x29, 0xef, 0xa9, 0x3e, 0x64, 0x8d, 0xf1, 0x90, 0xf4, 0xa7, 0x93, 0x8c, 0x7a, 0x02, 0x4d, - 0x38, 0x1f, 0x58, 0xb8, 0xe4, 0x7c, 0xe1, 0x66, 0x1c, 0x72, 0x30, 0xf3, 0x4c, 0xf4, 0x24, 0xd1, - 0x2d, 0xb7, 0xf1, 0x5a, 0x0f, 0xb8, 0x20, 0xc5, 0x90, 0xe5, 0xca, 0x45, 0x84, 0x5c, 0x08, 0x08, - 0xbf, 0xf9, 0x69, 0x41, 0xf5, 0x49, 0x85, 0x31, 0x35, 0x14, 0x69, 0x12, 0x57, 0x9c, 0xc8, 0xb7]); -let g_dhPriData = new Uint8Array([ - 0x01, 0xbc, 0xa7, 0x42, 0x25, 0x79, 0xc5, 0xaf, 0x0f, 0x9c, 0xde, 0x00, 0x3b, 0x58, 0x5c, 0xd1, - 0x1d, 0x7b, 0xcf, 0x66, 0xcd, 0xa9, 0x10, 0xae, 0x92, 0x2d, 0x3c, 0xb7, 0xf3]); -let g_dhX509PubData = new Uint8Array([ - 0x30, 0x82, 0x02, 0x29, 0x30, 0x82, 0x01, 0x1b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x03, 0x01, 0x30, 0x82, 0x01, 0x0c, 0x02, 0x82, 0x01, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, - 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, - 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, - 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, - 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, - 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, - 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, - 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, - 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, - 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, - 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, - 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, - 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, - 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, - 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, - 0x61, 0x28, 0x5c, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x01, 0x02, 0x02, - 0x02, 0x00, 0xe1, 0x03, 0x82, 0x01, 0x06, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8a, 0xbf, 0x16, - 0x67, 0x1b, 0x92, 0x4b, 0xf2, 0xe0, 0x02, 0xc5, 0x1f, 0x84, 0x00, 0xf8, 0x93, 0x0f, 0x74, 0xe7, - 0x0f, 0xba, 0x78, 0x30, 0xa8, 0x2d, 0x92, 0xef, 0x9b, 0x80, 0xeb, 0x76, 0xea, 0x26, 0x74, 0x72, - 0x63, 0x6a, 0x27, 0xc3, 0x8f, 0xcf, 0xbe, 0x82, 0xa2, 0x8b, 0xdc, 0x65, 0x58, 0xe3, 0xff, 0x29, - 0x97, 0xad, 0xb3, 0x4a, 0x2c, 0x50, 0x08, 0xb5, 0x68, 0xe1, 0x90, 0x5a, 0xdc, 0x48, 0xb3, 0x6b, - 0x7a, 0xce, 0x2e, 0x81, 0x3d, 0x38, 0x35, 0x59, 0xdc, 0x39, 0x8a, 0x97, 0xfe, 0x20, 0x86, 0x20, - 0xdb, 0x55, 0x38, 0x23, 0xca, 0xb5, 0x5b, 0x61, 0x00, 0xdc, 0x45, 0xe2, 0xa1, 0xf4, 0x1e, 0x7b, - 0x01, 0x7a, 0x84, 0x36, 0xa4, 0xa8, 0x1c, 0x0d, 0x3d, 0xde, 0x57, 0x66, 0x73, 0x4e, 0xaf, 0xee, - 0xb0, 0xb0, 0x69, 0x0c, 0x13, 0xba, 0x76, 0xff, 0x2e, 0xb6, 0x16, 0xf9, 0xfc, 0xd6, 0x09, 0x5b, - 0xc7, 0x37, 0x65, 0x84, 0xd5, 0x82, 0x8a, 0xd7, 0x5b, 0x57, 0xe3, 0x0e, 0x89, 0xbe, 0x05, 0x05, - 0x55, 0x2e, 0x9f, 0x94, 0x8a, 0x53, 0xdc, 0xb7, 0x00, 0xb2, 0x6a, 0x7b, 0x8e, 0xdf, 0x6e, 0xa4, - 0x6d, 0x13, 0xb6, 0xbc, 0xaa, 0x8e, 0x44, 0x11, 0x50, 0x32, 0x91, 0x56, 0xa2, 0x22, 0x3f, 0x2f, - 0x08, 0xbb, 0x4d, 0xbb, 0x69, 0xe6, 0xb1, 0xc2, 0x70, 0x79, 0x15, 0x54, 0xad, 0x4a, 0x29, 0xef, - 0xa9, 0x3e, 0x64, 0x8d, 0xf1, 0x90, 0xf4, 0xa7, 0x93, 0x8c, 0x7a, 0x02, 0x4d, 0x38, 0x1f, 0x58, - 0xb8, 0xe4, 0x7c, 0xe1, 0x66, 0x1c, 0x72, 0x30, 0xf3, 0x4c, 0xf4, 0x24, 0xd1, 0x2d, 0xb7, 0xf1, - 0x5a, 0x0f, 0xb8, 0x20, 0xc5, 0x90, 0xe5, 0xca, 0x45, 0x84, 0x5c, 0x08, 0x08, 0xbf, 0xf9, 0x69, - 0x41, 0xf5, 0x49, 0x85, 0x31, 0x35, 0x14, 0x69, 0x12, 0x57, 0x9c, 0xc8, 0xb7]); - -//x25519秘钥 -let g_x25519PubData = new Uint8Array([ - 0x9c, 0xf6, 0x7a, 0x8d, 0xce, 0xc2, 0x7f, 0xa7, 0xd9, 0xfd, 0xf1, 0xad, 0xac, 0xf0, 0xb3, 0x8c, - 0xe8, 0x16, 0xa2, 0x65, 0xcc, 0x18, 0x55, 0x60, 0xcd, 0x2f, 0xf5, 0xe5, 0x72, 0xc9, 0x3c, 0x54]); -//x25519公钥 -let g_x25519PriData = new Uint8Array([ - 0x20, 0xd5, 0xbb, 0x54, 0x6f, 0x1f, 0x00, 0x30, 0x4e, 0x33, 0x38, 0xb9, 0x8e, 0x6a, 0xdf, 0xad, - 0x33, 0x6f, 0x51, 0x23, 0xff, 0x4d, 0x95, 0x26, 0xdc, 0xb0, 0x74, 0xb2, 0x5c, 0x7e, 0x85, 0x6c]); - -//rsa密钥 -let g_nData = new Uint8Array([ - 0xb6, 0xd8, 0x9b, 0x33, 0x78, 0xa2, 0x63, 0x21, 0x84, 0x47, 0xa1, 0x72, 0 x3d, 0x73, 0x10, 0xbd, - 0xe9, 0x5d, 0x78, 0x44, 0x3d, 0x80, 0x18, 0x12, 0x60, 0xed, 0x29, 0x3e, 0xc7, 0x23, 0x0d, 0x3f, - 0x02, 0x59, 0x28, 0xe2, 0x8f, 0x83, 0xdf, 0x37, 0x4b, 0x77, 0xce, 0x5f, 0xb6, 0xcd, 0x61, 0x72, - 0xee, 0x01, 0xe2, 0x37, 0x4d, 0xfd, 0x4f, 0x39, 0xcf, 0xbd, 0xff, 0x84, 0x57, 0x44, 0xa5, 0x03]); -let g_eData = new Uint8Array([0x01, 0x00, 0x01]); -let g_dData = new Uint8Array([ - 0x35, 0x63, 0x89, 0xed, 0xbd, 0x8b, 0xac, 0xe6, 0x5c, 0x79, 0x8d, 0xea, 0x8d, 0x86, 0xcb, 0x9c, - 0xa8, 0x47, 0x62, 0x96, 0x8a, 0x5e, 0x9c, 0xa8, 0xc1, 0x24, 0x7e, 0xa6, 0x95, 0xfe, 0xe6, 0x1e, - 0xc0, 0xf3, 0x29, 0x76, 0xbb, 0x4d, 0xe4, 0xbc, 0x78, 0x64, 0xe1, 0x79, 0xcd, 0x8a, 0x45, 0xac, - 0x5c, 0x88, 0xea, 0xb4, 0x10, 0xd8, 0x90, 0x65, 0x7b, 0x94, 0xe8, 0x87, 0x30, 0x2a, 0x04, 0x01]); -let g_pubData = new Uint8Array([ - 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0x9e, 0x93, 0x57, 0xc4, 0xab, 0xde, 0x30, - 0xc5, 0x3f, 0x3b, 0x33, 0xa6, 0xdc, 0x4a, 0xdb, 0xbf, 0x12, 0x9e, 0x5d, 0xc4, 0xba, 0x0e, 0x15, - 0x06, 0x41, 0xd8, 0x96, 0x43, 0xca, 0xc5, 0xea, 0x9f, 0xdd, 0xa0, 0x2a, 0xf1, 0x53, 0x46, 0x14, - 0x36, 0x7a, 0xab, 0xbc, 0x92, 0x1b, 0x07, 0xc6, 0x9a, 0x7d, 0x0c, 0xd0, 0xa0, 0x0f, 0x31, 0xd5, - 0x38, 0x84, 0x6c, 0x08, 0xcb, 0x9b, 0x10, 0xa6, 0x4d, 0x02, 0x03, 0x01, 0x00, 0x01]); - -//ecc密钥 -let g_eccXData = new Uint8Array([ - 0xa5, 0xb8, 0xa3, 0x78, 0x1d, 0x6d, 0x76, 0xe0, 0xb3, 0xf5, 0x6f, 0x43, 0x9d, 0xcf, 0x60, 0xf6, - 0x0b, 0x3f, 0x64, 0x45, 0xa8, 0x3f, 0x1a, 0x96, 0xf1, 0xa1, 0xa4, 0x5d, 0x3e, 0x2c, 0x3f, 0x13]); -let g_eccYData = new Uint8Array([ - 0xd7, 0x81, 0xf7, 0x2a, 0xb5, 0x8d, 0x19, 0x3d, 0x9b, 0x96, 0xc7, 0x6a, 0x10, 0xf0, 0xaa, 0xbc, - 0x91, 0x6f, 0x4d, 0xa7, 0x09, 0xb3, 0x57, 0x88, 0x19, 0x6f, 0x00, 0x4b, 0xad, 0xee, 0x34, 0x35]); -let g_eccZData = new Uint8Array([ - 0xfb, 0x8b, 0x9f, 0x12, 0xa0, 0x83, 0x19, 0xbe, 0x6a, 0x6f, 0x63, 0x2a, 0x7c, 0x86, 0xba, 0xca, - 0x64, 0x0b, 0x88, 0x96, 0xe2, 0xfa, 0x77, 0xbc, 0x71, 0xe3, 0x0f, 0x0f, 0x9e, 0x3c, 0xe5, 0xf9]); -let g_eccPubData = new Uint8Array([ - 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, - 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xa5, 0xb8, 0xa3, 0x78, 0x1d, - 0x6d, 0x76, 0xe0, 0xb3, 0xf5, 0x6f, 0x43, 0x9d, 0xcf, 0x60, 0xf6, 0x0b, 0x3f, 0x64, 0x45, 0xa8, - 0x3f, 0x1a, 0x96, 0xf1, 0xa1, 0xa4, 0x5d, 0x3e, 0x2c, 0x3f, 0x13, 0xd7, 0x81, 0xf7, 0x2a, 0xb5, - 0x8d, 0x19, 0x3d, 0x9b, 0x96, 0xc7, 0x6a, 0x10, 0xf0, 0xaa, 0xbc, 0x91, 0x6f, 0x4d, 0xa7, 0x09, - 0xb3, 0x57, 0x88, 0x19, 0x6f, 0x00, 0x4b, 0xad, 0xee, 0x34, 0x35]); - -//导入DH2048密钥 -async function ImportDhTest(alg, keyType) { - let importKeyAlias = 'import_dh_key'; - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_DH - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_DH_KEY_SIZE_2048 - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, - value: huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - huksOptions.properties[0].value = alg; - huksOptions.properties[3].value = keyType; - - //对比密钥类型 - if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR) { - /* 非公钥拼接huksOptions.inData字段,满足以下格式: - * keyAlg的类型(4字节) + key_dh的长度(4字节) + - * g_dhPubData的长度(4字节) + g_dhPriData的长度(4字节) + - * reserved的大小(4字节) + g_dhPubData的数据 + g_dhPriData的数据 - */ - // PAIR - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_DH, huks.HuksKeySize.HUKS_DH_KEY_SIZE_2048, g_dhPubData.length, g_dhPriData.length, 0]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - - let strXData = strMaterial.concat(Uint8ArrayToString(g_dhPubData)); - let strData = strXData.concat(Uint8ArrayToString(g_dhPriData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY) { - //私钥 - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_DH, huks.HuksKeySize.HUKS_DH_KEY_SIZE_2048, 0, g_dhPriData.length, 0]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - - let strData = strMaterial.concat(Uint8ArrayToString(g_dhPriData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY) { - //公钥 - huksOptions.inData = g_dhX509PubData; - } + /* + * 导出加密导入用途密钥的公钥材料 + */ + await publicExportKeyFunc(wrapAlias, huksOptions); - await publicImportKeyFunc(importKeyAlias, huksOptions); - await publicDeleteKeyFunc(importKeyAlias, huksOptions); -} + /*---------------------------------------------------------------------------------------------- + * 此处省略业务本地生成ECC密钥对、业务本地ECDH密钥协商、业务本地生成密钥加密密钥K3、业务本地加密K1'和K3的流程 + *----------------------------------------------------------------------------------------------*/ + + /* 封装加密导入密钥材料:参考加密导入 + * 拼接importOptions.inData字段,满足以下格式: + * PK2长度(4字节) + PK2的数据 + AAD2的长度(4字节) + AAD2的数据 + + * Nonce2的长度(4字节)+ Nonce2的数据 + AEAD2的长度(4字节) + AEAD2的数据 + + * K3密文的长度(4字节) + K3密文的数据 + AAD3的长度(4字节) + AAD3的数据 + + * Nonce3的长度(4字节) + Nonce3的数据 + AEAD3的长度(4字节) + AEAD3的数据 + + * K1'_size的长度(4字节) + K1'_size + K1'_enc的长度(4字节) + K1'_enc的数据 + */ + let inputKey = new Uint8Array([ + 0x5b, 0x00, 0x00, 0x00, // ECC-P-256 公钥长度(X.509规范DER格式):91 + // ECC-P-256 公钥 + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xc0, 0xfe, 0x1c, 0x67, 0xde, + 0x86, 0x0e, 0xfb, 0xaf, 0xb5, 0x85, 0x52, 0xb4, 0x0e, 0x1f, 0x6c, 0x6c, 0xaa, 0xc5, 0xd9, 0xd2, + 0x4d, 0xb0, 0x8a, 0x72, 0x24, 0xa1, 0x99, 0xaf, 0xfc, 0x3e, 0x55, 0x5a, 0xac, 0x99, 0x3d, 0xe8, + 0x34, 0x72, 0xb9, 0x47, 0x9c, 0xa6, 0xd8, 0xfb, 0x00, 0xa0, 0x1f, 0x9f, 0x7a, 0x41, 0xe5, 0x44, + 0x3e, 0xb2, 0x76, 0x08, 0xa2, 0xbd, 0xe9, 0x41, 0xd5, 0x2b, 0x9e, + + 0x10, 0x00, 0x00, 0x00, // AAD2长度:16 + // AAD2 + 0xbf, 0xf9, 0x69, 0x41, 0xf5, 0x49, 0x85, 0x31, 0x35, 0x14, 0x69, 0x12, 0x57, 0x9c, 0xc8, 0xb7, + + 0x10, 0x00, 0x00, 0x00, // Nonce2长度:16 + // Nonce2 + 0x2d, 0xb7, 0xf1, 0x5a, 0x0f, 0xb8, 0x20, 0xc5, 0x90, 0xe5, 0xca, 0x45, 0x84, 0x5c, 0x08, 0x08, + + 0x10, 0x00, 0x00, 0x00, // AEAD2长度:16 + // AEAD2 + 0x43, 0x25, 0x1b, 0x2f, 0x5b, 0x86, 0xd8, 0x87, 0x04, 0x4d, 0x38, 0xc2, 0x65, 0xcc, 0x9e, 0xb7, + + 0x20, 0x00, 0x00, 0x00, // K3密文长度:32 + // K3密文 + 0xf4, 0xe8, 0x93, 0x28, 0x0c, 0xfa, 0x4e, 0x11, 0x6b, 0xe8, 0xbd, 0xa8, 0xe9, 0x3f, 0xa7, 0x8f, + 0x2f, 0xe3, 0xb3, 0xbf, 0xaf, 0xce, 0xe5, 0x06, 0x2d, 0xe6, 0x45, 0x5d, 0x19, 0x26, 0x09, 0xe7, + + 0x10, 0x00, 0x00, 0x00, // AAD3长度:16 + // AAD3 + 0xf4, 0x1e, 0x7b, 0x01, 0x7a, 0x84, 0x36, 0xa4, 0xa8, 0x1c, 0x0d, 0x3d, 0xde, 0x57, 0x66, 0x73, + + 0x10, 0x00, 0x00, 0x00, // Nonce3长度:16 + // Nonce3 + 0xe3, 0xff, 0x29, 0x97, 0xad, 0xb3, 0x4a, 0x2c, 0x50, 0x08, 0xb5, 0x68, 0xe1, 0x90, 0x5a, 0xdc, + + 0x10, 0x00, 0x00, 0x00, // AEAD3长度:16 + // AEAD3 + 0x26, 0xae, 0xdc, 0x4e, 0xa5, 0x6e, 0xb1, 0x38, 0x14, 0x24, 0x47, 0x1c, 0x41, 0x89, 0x63, 0x11, + + 0x04, 0x00, 0x00, 0x00, // “密钥明文材料长度”的长度(字节):4 + // 密钥明文材料的长度:32字节 + 0x20, 0x00, 0x00, 0x00, + + 0x20, 0x00, 0x00, 0x00, // 待导入密钥密文长度(字节):32 + // 待导入密钥密文 + 0x0b, 0xcb, 0xa9, 0xa8, 0x5f, 0x5a, 0x9d, 0xbf, 0xa1, 0xfc, 0x72, 0x74, 0x87, 0x79, 0xf2, 0xf4, + 0x22, 0x0c, 0x8a, 0x4d, 0xd8, 0x7e, 0x10, 0xc8, 0x44, 0x17, 0x95, 0xab, 0x3b, 0xd2, 0x8f, 0x0a + ]); + importOptions.inData = inputKey; -//导入ecc256密钥 -async function ImportEccTest(alg, keyType) { - let importKeyAlias = 'import_ecc_key'; - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_ECC - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, - value: huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - huksOptions.properties[0].value = alg; - huksOptions.properties[3].value = keyType; - - //对比密钥类型 - if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR) { - /* 非公钥拼接huksOptions.inData字段,满足以下格式: - * keyAlg的类型(4字节) + key_ecc的长度(4字节) + - * g_eccXData的长度(4字节) + g_eccYData的长度(4字节) + - * g_eccZData的长度(4字节) + g_eccXData的数据 + - * g_eccYData的数据 + g_eccZData的数据 - */ - //PAIR - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_ECC, huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256, g_eccXData.length, g_eccYData.length, g_eccZData.length]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - - let strXData = strMaterial.concat(Uint8ArrayToString(g_eccXData)); - let strYData = strXData.concat(Uint8ArrayToString(g_eccYData)); - let strData = strYData.concat(Uint8ArrayToString(g_eccZData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY) { - //私钥 - huksOptions.properties[3].value == huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_ECC, huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256, 0, 0, g_eccZData.length]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - - let strData = strMaterial.concat(Uint8ArrayToString(g_eccZData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY) { - //公钥 - huksOptions.inData = g_eccPubData; - } + /* + * 导入封装的加密密钥材料 + */ + await publicImportWrappedKey(importAlias, wrapAlias, importOptions); - await publicImportKeyFunc(importKeyAlias, huksOptions); - await publicDeleteKeyFunc(importKeyAlias, huksOptions); + /* + * 删除用于加密导入的密钥 + */ + await publicDeleteKeyFunc(wrapAlias, huksOptions); } +``` -//导入rsa512密钥 -async function ImportRsaTest(alg, keyType) { - let importKeyAlias = 'import_rsa_key'; - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_RSA - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_PSS - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_512 - } - properties[5] = { - tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, - value: huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - huksOptions.properties[0].value = alg; - huksOptions.properties[3].value = keyType; - - //对比密钥类型 - if (huksOptions.properties[5].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR) { - /* 非公钥拼接huksOptions.inData字段,满足以下格式: - * keyAlg的类型(4字节) + key_rsa的长度(4字节) + - * g_nData的长度(4字节) + g_eData的长度(4字节) + - * g_dData的长度(4字节) + g_nData的数据 + - * g_eData的数据 + g_dData的数据 - */ - //PAIR - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_RSA, huks.HuksKeySize.HUKS_RSA_KEY_SIZE_512, g_nData.length, g_eData.length, g_dData.length]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - let strNData = strMaterial.concat(Uint8ArrayToString(g_nData)); - let strEData = strNData.concat(Uint8ArrayToString(g_eData)); - let strData = strEData.concat(Uint8ArrayToString(g_dData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[5].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY) { - //私钥 - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_RSA, huks.HuksKeySize.HUKS_RSA_KEY_SIZE_512, g_nData.length, 0, g_dData.length]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - let strNData = strMaterial.concat(Uint8ArrayToString(g_nData)); - let strData = strNData.concat(Uint8ArrayToString(g_dData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[5].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY) { - //公钥 - huksOptions.inData = g_pubData; - } - await publicImportKeyFunc(importKeyAlias, huksOptions); - await publicDeleteKeyFunc(importKeyAlias, huksOptions); -} +**调测验证** -//导入x25519密钥 -async function ImportX25519Test(alg, keyType) { - let importKeyAlias = 'import_x25519_key'; - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_X25519 - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_CURVE25519_KEY_SIZE_256 - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, - value: huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - huksOptions.properties[0].value = alg; - huksOptions.properties[3].value = keyType; - - //对比密钥类型 - if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR) { - /* 非公钥拼接huksOptions.inData字段,满足以下格式: - * keyAlg的类型(4字节) + key_x25519的长度(4字节) + - * g_x25519PubData的长度(4字节) + g_x25519PriData的长度(4字节) + - * reserved的大小(4字节) + g_x25519PubData的数据 + - * g_x25519PriData的数据 - */ - //PAIR - let Material = new Uint32Array([huks.HuksKeyAlg.HUKS_ALG_X25519, huks.HuksKeySize.HUKS_CURVE25519_KEY_SIZE_256, g_x25519PriData.length, g_x25519PubData.length, 0]); - let u8Material = Uint32ToUint8(Material); - let strMaterial = Uint8ArrayToString(u8Material); - let strXData = strMaterial.concat(Uint8ArrayToString(g_x25519PubData)); - let strData = strXData.concat(Uint8ArrayToString(g_x25519PriData)); - huksOptions.inData = StringToUint8Array(strData); - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY) { - //私钥 - huksOptions.inData = g_x25519PriData; - } else if (huksOptions.properties[3].value === huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY) { - //公钥 - huksOptions.inData = g_x25519PubData; - } - await publicImportKeyFunc(importKeyAlias, huksOptions); - await publicDeleteKeyFunc(importKeyAlias, huksOptions); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testExportRsa') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(() => { - testExportRsa(); - }) +验证时查询密钥是否存在,如密钥存在即表示生成密钥成功。 - Button() { - Text('testImportDh') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(() => { - ImportDhTest(huks.HuksKeyAlg.HUKS_ALG_DH, huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY); - }) +**代码示例:** - Button() { - Text('testImportRsa') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(() => { - ImportRsaTest(huks.HuksKeyAlg.HUKS_ALG_RSA, huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR); - }) +```ts +import huks from '@ohos.security.huks'; - Button() { - Text('testImportX25519') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(() => { - ImportX25519Test(huks.HuksKeyAlg.HUKS_ALG_X25519, huks.HuksImportKeyType.HUKS_KEY_TYPE_PRIVATE_KEY); - }) +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let keyAlias = 'importAlias'; +let isKeyExist; - Button() { - Text('testImportEcc') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(() => { - ImportEccTest(huks.HuksKeyAlg.HUKS_ALG_ECC, huks.HuksImportKeyType.HUKS_KEY_TYPE_PUBLIC_KEY); - }) +let keyProperties = new Array(); +keyProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES, +} +let huksOptions = { + properties: keyProperties, // 非空填充 + inData: new Uint8Array(new Array()) // 非空填充 +} +try { + huks.isKeyItemExist(keyAlias, huksOptions, function (error, data) { + if (error) { + console.error(`callback: isKeyItemExist failed, code: ${error.code}, msg: ${error.message}`); + } else { + if (data !== null && data.valueOf() !== null) { + isKeyExist = data.valueOf(); + console.info(`callback: isKeyItemExist success, isKeyExist = ${isKeyExist}`); + } } - .width('100%') - .height('100%') - } + }); +} catch (error) { + console.error(`callback: isKeyItemExist input arg invalid, code: ${error.code}, msg: ${error.message}`); } ``` -### 安全导入 -基于密钥协商和中间密钥二次加密的方式,业务调用方和HUKS各自协商出共享的对称密钥来对中间密钥、待导入密钥进行加解密。从而实现密文导入后,在HUKS中对导入密钥进行解密再保存。对明文密钥的处理仅在HUKS 安全环境中,保证密钥明文生命周期内不出安全环境。 +## 常见密钥操作 -开发步骤如下: +**场景概述** -1. huks中生成用于加密导入协商的密钥。 -2. 导出该密钥的公钥,协商出共享密钥。 -3. 生成中间密钥材料并加密密钥。 -4. 导入密钥。 +为了实现对数据机密性、完整性等保护,在生成/导入密钥后,需要对数据进行密钥操作,比如加密解密、签名验签、密钥协商、密钥派生等,本章节提供了常用的密钥操作的示例。本章节提供的示例都没有设置二次身份访问控制,如设置了密钥访问控制请参考[密钥访问控制](#密钥访问控制)用法。 -**支持的密钥类型:** +**通用开发流程** -AES128, AES192, AES256, RSA512, RSA768, RSA1024, RSA2048, RSA3072, RSA4096, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512, ECC224, ECC256, ECC384, ECC521, Curve25519, DSA, SM2, SM3, SM4. +HUKS基于密钥会话来操作数据,使用密钥时基于以下流程: +1. **初始化密钥会话[huks.initSession()](../reference/apis/js-apis-huks.md#huksinitsession9):** 传入密钥别名和密钥操作参数,初始化一个密钥会话并获取会话句柄。其中密钥操作参数中必须包含对应密码算法所必须的参数,包括密码算法、密钥大小、密钥目的、工作模式、填充模式、散列模式、IV、Nonce、AAD等。如果密钥设置了访问控制属性,还需要其他参数具体[密钥访问控制](#密钥访问控制)。此步骤必选! +2. **分段操作数据[huks.updateSession()](../reference/apis/js-apis-huks.md#huksupdatesession9):** 如数据过大(超过100K)或密码算法的要求需要对数据进行分段操作,反之可跳过此步。此步骤可选! +3. **结束密钥会话[huks.finishSession()](../reference/apis/js-apis-huks.md#huksfinishsession9):** 操作最后一段数据并结束密钥会话,如过程中发生错误或不需要此次密钥操作数据,必须取消会话[huks.abortSession()](../reference/apis/js-apis-huks.md#huksabortsession9)。此步骤必选! -> **注意** -> -> - 生成公共密钥时,要设置参数HUKS_TAG_PURPOSE = HUKS_KEY_PURPOSE_UNWRAP -> - 参数HUKS_TAG_IMPORT_KEY_TYPE = HUKS_KEY_TYPE_KEY_PAIR -> - 安全导入密钥时,参数集须加上参数HUKS_TAG_UNWRAP_ALGORITHM_SUITE, 值为HUKS_UNWRAP_SUITE_X25519_AES_256_GCM_NOPADDING或者HUKS_UNWRAP_SUITE_ECDH_AES_256_GCM_NOPADDING -> - 存储的 keyAlias 密钥别名最大为64字节 +### 加密解密 +```ts +/* + * 以下以SM4 128密钥的Callback操作使用为例 + */ +import huks from '@ohos.security.huks'; + +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let srcKeyAlias = 'sm4_Key'; +let IV = '0000000000000000'; +let cipherInData = 'Hks_SM4_Cipher_Test_101010101010101010110_string'; +let encryptUpdateResult = new Array(); +let handle; +let updateResult = new Array(); +let finishOutData; + +/* 集成生成密钥参数集 & 加密参数集 */ +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM4, +} +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, +} +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, +} +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, +} +properties[4] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +let huksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) +} -在使用示例前,需要先了解几个预先定义的变量: +let propertiesEncrypt = new Array(); +propertiesEncrypt[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM4, +} +propertiesEncrypt[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT, +} +propertiesEncrypt[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, +} +propertiesEncrypt[3] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +propertiesEncrypt[4] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, +} +propertiesEncrypt[5] = { + tag: huks.HuksTag.HUKS_TAG_IV, + value: StringToUint8Array(IV), +} +let encryptOptions = { + properties: propertiesEncrypt, + inData: new Uint8Array(new Array()) +} -| 参数名 | 类型 | 必填 | 说明 | -| -------------- | ----------- | ---- | -------------------------------- | -| importAlias | string | 是 | 密钥别名。 | -| wrapAlias | string | 是 | 密钥别名。 | -| genWrapOptions | HuksOptions | 是 | 用于存放生成加密协商key所需TAG。 | -| importOptions | HuksOptions | 是 | 用于存放导入加密key所需TAG。 | +/* 修改加密参数集为解密参数集 */ +propertiesEncrypt.splice(1, 1, { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, +}); +let decryptOptions = { + properties: propertiesEncrypt, + inData: new Uint8Array(new Array()) +} -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +function StringToUint8Array(str) { + let arr = []; + for (let i = 0, j = str.length; i < j; ++i) { + arr.push(str.charCodeAt(i)); + } + return new Uint8Array(arr); +} -**示例:** +function Uint8ArrayToString(fileData) { + let dataString = ''; + for (let i = 0; i < fileData.length; i++) { + dataString += String.fromCharCode(fileData[i]); + } + return dataString; +} -```ts -import huks from '@ohos.security.huks'; +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { + return new Promise((resolve, reject) => { + try { + huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); +} -async function publicExportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback export`); +async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; try { - await exportKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); - if (data.outData !== null) { - exportKey = data.outData; - } + await generateKeyItem(keyAlias, huksOptions, throwObject) + .then((data) => { + console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { +function initSession(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { + huks.initSession(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -831,30 +793,37 @@ function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise< } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicImportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter promise importKeyItem`); +async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback doInit`); + let throwObject = {isThrow: false}; try { - await importKeyItem(keyAlias, huksOptions) + await initSession(keyAlias, huksOptions, throwObject) .then ((data) => { - console.info(`callback: importKeyItem success, data = ${JSON.stringify(data)}`); + console.info(`callback: doInit success, data = ${JSON.stringify(data)}`); + handle = data.handle; }) - .catch(error => { - console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); + .catch((error) => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doInit failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: importKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function updateSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.importKeyItem(keyAlias, huksOptions, function (error, data) { + huks.updateSession(handle, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -862,30 +831,36 @@ function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicImportWrappedKey(keyAlias:string, wrappingKeyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback importWrappedKeyItem`); +async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { + console.info(`enter callback doUpdate`); + let throwObject = {isThrow: false}; try { - await importWrappedKeyItem(keyAlias, wrappingKeyAlias, huksOptions) + await updateSession(handle, huksOptions, throwObject) .then ((data) => { - console.info(`callback: importWrappedKeyItem success, data = ${JSON.stringify(data)}`); + console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: importWrappedKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: importWrappedKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function importWrappedKeyItem(keyAlias:string, wrappingKeyAlias:string, huksOptions:huks.HuksOptions) { +function finishSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.importWrappedKeyItem(keyAlias, wrappingKeyAlias, huksOptions, function (error, data) { + huks.finishSession(handle, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -893,27 +868,34 @@ function importWrappedKeyItem(keyAlias:string, wrappingKeyAlias:string, huksOpti } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); +async function publicFinishFunc(handle:number, huksOptions:huks.HuksOptions) { + console.info(`enter callback doFinish`); + let throwObject = {isThrow: false}; try { - await deleteKeyItem(keyAlias, huksOptions) + await finishSession(handle, huksOptions, throwObject) .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + finishOutData = data.outData; + console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { @@ -924,206 +906,157 @@ function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -let importAlias = "importAlias"; -let wrapAlias = "wrappingKeyAlias"; -let exportKey; +async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback deleteKeyItem`); + let throwObject = {isThrow: false}; + try { + await deleteKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: deletKeeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + } +} -let inputEccPair = new Uint8Array([ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0xa5, 0xb8, 0xa3, 0x78, 0x1d, 0x6d, 0x76, 0xe0, 0xb3, 0xf5, 0x6f, 0x43, - 0x9d, 0xcf, 0x60, 0xf6, 0x0b, 0x3f, 0x64, 0x45, 0xa8, 0x3f, 0x1a, 0x96, 0xf1, 0xa1, 0xa4, 0x5d, - 0x3e, 0x2c, 0x3f, 0x13, 0xd7, 0x81, 0xf7, 0x2a, 0xb5, 0x8d, 0x19, 0x3d, 0x9b, 0x96, 0xc7, 0x6a, - 0x10, 0xf0, 0xaa, 0xbc, 0x91, 0x6f, 0x4d, 0xa7, 0x09, 0xb3, 0x57, 0x88, 0x19, 0x6f, 0x00, 0x4b, - 0xad, 0xee, 0x34, 0x35, 0xfb, 0x8b, 0x9f, 0x12, 0xa0, 0x83, 0x19, 0xbe, 0x6a, 0x6f, 0x63, 0x2a, - 0x7c, 0x86, 0xba, 0xca, 0x64, 0x0b, 0x88, 0x96, 0xe2, 0xfa, 0x77, 0xbc, 0x71, 0xe3, 0x0f, 0x0f, - 0x9e, 0x3c, 0xe5, 0xf9]); +async function testSm4Cipher() { + /* 生成密钥 */ + await publicGenKeyFunc(srcKeyAlias, huksOptions); -let properties = new Array(); -properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_ECC -}; -properties[1] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_ECC_KEY_SIZE_256 -}; -properties[2] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_UNWRAP -}; -properties[3] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 -}; -properties[4] = { - tag: huks.HuksTag.HUKS_TAG_IMPORT_KEY_TYPE, - value: huks.HuksImportKeyType.HUKS_KEY_TYPE_KEY_PAIR, -}; -let huksOptions = { - properties: properties, - inData: inputEccPair -}; - -let importProperties = new Array(); -importProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES -}; -importProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 -}; -importProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT -}; -importProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_CBC -}; -importProperties[4] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE -}; -importProperties[5] = { - tag: huks.HuksTag.HUKS_TAG_UNWRAP_ALGORITHM_SUITE, - value: huks.HuksUnwrapSuite.HUKS_UNWRAP_SUITE_ECDH_AES_256_GCM_NOPADDING -}; -let importOptions = { - properties: importProperties, - inData: new Uint8Array(new Array()) -}; - -async function importWrappedKeyItemTest() { + /* 进行密钥加密操作 */ + await publicInitFunc(srcKeyAlias, encryptOptions); - console.info(`enter ImportWrapKey test`); - await publicImportKeyFunc(wrapAlias, huksOptions); + encryptOptions.inData = StringToUint8Array(cipherInData); + await publicUpdateFunc(handle, encryptOptions); + encryptUpdateResult = updateResult; - await publicExportKeyFunc(wrapAlias, huksOptions); + encryptOptions.inData = new Uint8Array(new Array()); + await publicFinishFunc(handle, encryptOptions); + if (finishOutData === cipherInData) { + console.info('test finish encrypt err '); + } else { + console.info('test finish encrypt success'); + } - /* 以下操作不需要调用HUKS接口,此处不给出具体实现。 - * 假设待导入的密钥为keyA - * 1.生成ECC公私钥keyB,公钥为keyB_pub, 私钥为keyB_pri - * 2.使用keyB_pri和wrappingAlias密钥中获取的公钥进行密钥协商,协商出共享密钥share_key - * 3.随机生成密钥kek,用于加密keyA,采用AES-GCM加密,加密过程中需要记录:nonce1/aad1/加密后的密文keyA_enc/加密后的tag1。 - * 4.使用share_key加密kek,采用AES-GCM加密,加密过程中需要记录:nonce2/aad2/加密后的密文kek_enc/加密后的tag2。 - * 5.拼接importOptions.inData字段,满足以下格式: - * keyB_pub的长度(4字节) + keyB_pub的数据 + aad2的长度(4字节) + aad2的数据 + - * nonce2的长度(4字节) + nonce2的数据 + tag2的长度(4字节) + tag2的数据 + - * kek_enc的长度(4字节) + kek_enc的数据 + aad1的长度(4字节) + aad1的数据 + - * nonce1的长度(4字节) + nonce1的数据 + tag1的长度(4字节) + tag1的数据 + - * keyA长度占用的内存长度(4字节) + keyA的长度 + keyA_enc的长度(4字节) + keyA_enc的数据 - */ - let inputKey = new Uint8Array([ - 0x5b, 0x00, 0x00, 0x00, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, - 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xc0, - 0xfe, 0x1c, 0x67, 0xde, 0x86, 0x0e, 0xfb, 0xaf, 0xb5, 0x85, 0x52, 0xb4, 0x0e, 0x1f, 0x6c, 0x6c, - 0xaa, 0xc5, 0xd9, 0xd2, 0x4d, 0xb0, 0x8a, 0x72, 0x24, 0xa1, 0x99, 0xaf, 0xfc, 0x3e, 0x55, 0x5a, - 0xac, 0x99, 0x3d, 0xe8, 0x34, 0x72, 0xb9, 0x47, 0x9c, 0xa6, 0xd8, 0xfb, 0x00, 0xa0, 0x1f, 0x9f, - 0x7a, 0x41, 0xe5, 0x44, 0x3e, 0xb2, 0x76, 0x08, 0xa2, 0xbd, 0xe9, 0x41, 0xd5, 0x2b, 0x9e, 0x10, - 0x00, 0x00, 0x00, 0xbf, 0xf9, 0x69, 0x41, 0xf5, 0x49, 0x85, 0x31, 0x35, 0x14, 0x69, 0x12, 0x57, - 0x9c, 0xc8, 0xb7, 0x10, 0x00, 0x00, 0x00, 0x2d, 0xb7, 0xf1, 0x5a, 0x0f, 0xb8, 0x20, 0xc5, 0x90, - 0xe5, 0xca, 0x45, 0x84, 0x5c, 0x08, 0x08, 0x10, 0x00, 0x00, 0x00, 0x43, 0x25, 0x1b, 0x2f, 0x5b, - 0x86, 0xd8, 0x87, 0x04, 0x4d, 0x38, 0xc2, 0x65, 0xcc, 0x9e, 0xb7, 0x20, 0x00, 0x00, 0x00, 0xf4, - 0xe8, 0x93, 0x28, 0x0c, 0xfa, 0x4e, 0x11, 0x6b, 0xe8, 0xbd, 0xa8, 0xe9, 0x3f, 0xa7, 0x8f, 0x2f, - 0xe3, 0xb3, 0xbf, 0xaf, 0xce, 0xe5, 0x06, 0x2d, 0xe6, 0x45, 0x5d, 0x19, 0x26, 0x09, 0xe7, 0x10, - 0x00, 0x00, 0x00, 0xf4, 0x1e, 0x7b, 0x01, 0x7a, 0x84, 0x36, 0xa4, 0xa8, 0x1c, 0x0d, 0x3d, 0xde, - 0x57, 0x66, 0x73, 0x10, 0x00, 0x00, 0x00, 0xe3, 0xff, 0x29, 0x97, 0xad, 0xb3, 0x4a, 0x2c, 0x50, - 0x08, 0xb5, 0x68, 0xe1, 0x90, 0x5a, 0xdc, 0x10, 0x00, 0x00, 0x00, 0x26, 0xae, 0xdc, 0x4e, 0xa5, - 0x6e, 0xb1, 0x38, 0x14, 0x24, 0x47, 0x1c, 0x41, 0x89, 0x63, 0x11, 0x04, 0x00, 0x00, 0x00, 0x20, - 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0b, 0xcb, 0xa9, 0xa8, 0x5f, 0x5a, 0x9d, 0xbf, 0xa1, - 0xfc, 0x72, 0x74, 0x87, 0x79, 0xf2, 0xf4, 0x22, 0x0c, 0x8a, 0x4d, 0xd8, 0x7e, 0x10, 0xc8, 0x44, - 0x17, 0x95, 0xab, 0x3b, 0xd2, 0x8f, 0x0a - ]); + /* 进行解密操作 */ + await publicInitFunc(srcKeyAlias, decryptOptions); - importOptions.inData = inputKey; - await publicImportWrappedKey(importAlias, wrapAlias, importOptions); + decryptOptions.inData = new Uint8Array(encryptUpdateResult); + await publicUpdateFunc(handle, decryptOptions); - await publicDeleteKeyFunc(wrapAlias, huksOptions); - await publicDeleteKeyFunc(importAlias, importOptions); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('importWrappedKeyItemTest') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - importWrappedKeyItemTest(); - }) - } - .width('100%') - .height('100%') + decryptOptions.inData = new Uint8Array(new Array()); + await publicFinishFunc(handle, decryptOptions); + if (finishOutData === cipherInData) { + console.info('test finish decrypt success '); + } else { + console.info('test finish decrypt err'); } + + await publicDeleteKeyFunc(srcKeyAlias, huksOptions); } ``` -### 密钥加解密 - -通过指定别名的方式,使用HUKS中存储的对称或非对称密钥对数据进行加密或解密运算,运算过程中密钥明文不出安全环境。 - -开发步骤如下: - -1. 生成密钥。 -2. 密钥加密。 -3. 密钥解密。 - -**支持的密钥类型:** - -| HUKS_ALG_ALGORITHM | HUKS_TAG_PURPOSE | HUKS_TAG_DIGEST | HUKS_TAG_PADDING | HUKS_TAG_BLOCK_MODE | HUKS_TAG_IV | -| ------------------------------------------------------------ | -------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------- | ----------- | -| HUKS_ALG_SM4 (支持长度: HUKS_SM4_KEY_SIZE_128) | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | 【非必选】 | HUKS_PADDING_NONE | HUKS_MODE_CTR HUKS_MODE_ECB HUKS_MODE_CBC | 【必选】 | -| HUKS_ALG_SM4 (支持长度: HUKS_SM4_KEY_SIZE_128) | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | 【非必选】 | HUKS_PADDING_PKCS7 | HUKS_MODE_ECB HUKS_MODE_CBC | 【必选】 | -| HUKS_ALG_RSA (支持长度: HUKS_RSA_KEY_SIZE_512 HUKS_RSA_KEY_SIZE_768 HUKS_RSA_KEY_SIZE_1024 HUKS_RSA_KEY_SIZE_2048 HUKS_RSA_KEY_SIZE_3072 HUKS_RSA_KEY_SIZE_4096) | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | HUKS_PADDING_NONE HUKS_PADDING_PKCS1_V1_5 HUKS_PADDING_OAEP | HUKS_MODE_ECB | 【非必选】 | - -| HUKS_ALG_ALGORITHM | HUKS_TAG_PURPOSE | HUKS_TAG_PADDING | HUKS_TAG_BLOCK_MODE | HUKS_TAG_IV | HUKS_TAG_NONCE | HUKS_TAG_ASSOCIATED_DATA | HUKS_TAG_AE_TAG | -| ------------------------------------------------------------ | ------------------------ | ------------------------------------- | ---------------------------- | ----------- | -------------- | ------------------------ | --------------- | -| HUKS_ALG_AES (支持长度: HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256) | HUKS_KEY_PURPOSE_ENCRYPT | HUKS_PADDING_NONE HUKS_PADDING_PKCS7 | HUKS_MODE_CBC | 【必选】 | 【非必选】 | 【非必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_ENCRYPT | HUKS_PADDING_NONE | HUKS_MODE_CCM HUKS_MODE_GCM | 【非必选】 | 【必选】 | 【必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_ENCRYPT | HUKS_PADDING_NONE | HUKS_MODE_CTR | 【必选】 | 【非必选】 | 【非必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_ENCRYPT | HUKS_PADDING_PKCS7 HUKS_PADDING_NONE | HUKS_MODE_ECB | 【必选】 | 【非必选】 | 【非必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE HUKS_PADDING_PKCS7 | HUKS_MODE_CBC | 【必选】 | 【非必选】 | 【非必选】 | 【必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE | HUKS_MODE_CCM HUKS_MODE_GCM | 【非必选】 | 【必选】 | 【必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE | HUKS_MODE_CTR | 【必选】 | 【非必选】 | 【非必选】 | 【非必选】 | -| HUKS_ALG_AES | HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE HUKS_PADDING_PKCS7 | HUKS_MODE_ECB | 【必选】 | 【非必选】 | 【非必选】 | 【非必选】 | - -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 - -在使用示例前,需要先了解几个预先定义的变量: +### 签名验签 +```ts +/* + * 以下以SM2密钥的Callback操作使用为例 + */ +import huks from '@ohos.security.huks'; -| 参数名 | 类型 | 必填 | 说明 | -| -------------- | ----------- | ---- | ------------------------ | -| srcKeyAlias | string | 是 | 密钥别名。 | -| huksOptions | HuksOptions | 是 | 用于存放生成key所需TAG。 | -| encryptOptions | HuksOptions | 是 | 用于存放加密key所需TAG。 | -| decryptOptions | HuksOptions | 是 | 用于存放解密key所需TAG。 | +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let generateKeyAlias = 'sm2_Key'; +let importKeyAlias = 'importKeyAlias'; +let signVerifyInData = 'signVerifyInDataForTest'; +let handle; +let exportKey; +let finishOutData; -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +/* 集成生成密钥参数集 */ +let generateKeyProperties = new Array(); +generateKeyProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM2, +} +generateKeyProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY, +} +generateKeyProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, +} +generateKeyProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, +} +let genrateKeyOptions = { + properties: generateKeyProperties, + inData: new Uint8Array(new Array()) +} -**示例1:** +/* 集成签名参数集 */ +let signProperties = new Array(); +signProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM2, +} +signProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN +} +signProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, +} +signProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, +} +let signOptions = { + properties: signProperties, + inData: new Uint8Array(new Array()) +} -```ts -/* Cipher操作支持RSA、AES、SM4类型的密钥。 - * - * 以下以SM4 128密钥的Promise操作使用为例 - */ -import huks from '@ohos.security.huks'; +/* 集成验签参数集 */ +let verifyProperties = new Array(); +verifyProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM2, +} +verifyProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY +} +verifyProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, +} +verifyProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, +} +let verifyOptions = { + properties: verifyProperties, + inData: new Uint8Array(new Array()) +} function StringToUint8Array(str) { let arr = []; @@ -1133,33 +1066,47 @@ function StringToUint8Array(str) { return new Uint8Array(arr); } -function Uint8ArrayToString(fileData) { - let dataString = ''; - for (let i = 0; i < fileData.length; i++) { - dataString += String.fromCharCode(fileData[i]); - } - return dataString; +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { + return new Promise((resolve, reject) => { + try { + huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); } async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; try { - await generateKeyItem(keyAlias, huksOptions) + await generateKeyItem(keyAlias, huksOptions, throwObject) .then((data) => { console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function initSession(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + huks.initSession(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1167,47 +1114,74 @@ function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter promise doInit`); + console.info(`enter callback doInit`); + let throwObject = {isThrow: false}; try { - await huks.initSession(keyAlias, huksOptions) + await initSession(keyAlias, huksOptions, throwObject) .then ((data) => { - console.info(`promise: doInit success, data = ${JSON.stringify(data)}`); + console.info(`callback: doInit success, data = ${JSON.stringify(data)}`); handle = data.handle; }) - .catch(error => { - console.error(`promise: doInit key failed, code: ${error.code}, msg: ${error.message}`); + .catch((error) => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doInit failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`promise: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); } } +function updateSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { + return new Promise((resolve, reject) => { + try { + huks.updateSession(handle, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); +} + async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { console.info(`enter callback doUpdate`); + let throwObject = {isThrow: false}; try { - await updateSession(handle, huksOptions) + await updateSession(handle, huksOptions, throwObject) .then ((data) => { console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); - updateResult = Array.from(data.outData); }) .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function finishSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.updateSession(handle, huksOptions, function (error, data) { + huks.finishSession(handle, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1215,6 +1189,7 @@ function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - finishOutData = Uint8ArrayToString(new Uint8Array(updateResult)); + finishOutData = data.outData; console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.finishSession(handle, huksOptions, function (error, data) { + huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1247,30 +1227,37 @@ function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); + exportKey = data.outData; }) .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { + huks.importKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1278,157 +1265,214 @@ function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -let IV = '0000000000000000'; -let cipherInData = 'Hks_SM4_Cipher_Test_101010101010101010110_string'; -let srcKeyAlias = 'huksCipherSm4SrcKeyAlias'; -let encryptUpdateResult = new Array(); -let handle; -let updateResult = new Array(); -let finishOutData; - -async function testSm4Cipher() { - /* 集成生成密钥参数集 & 加密参数集 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM4, - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_CBC, - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - - let propertiesEncrypt = new Array(); - propertiesEncrypt[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM4, - } - propertiesEncrypt[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT, - } - propertiesEncrypt[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, - } - propertiesEncrypt[3] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - propertiesEncrypt[4] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_CBC, - } - propertiesEncrypt[5] = { - tag: huks.HuksTag.HUKS_TAG_IV, - value: StringToUint8Array(IV), +async function publicImportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter promise importKeyItem`); + let throwObject = {isThrow: false}; + try { + await importKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: importKeyItem success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: importKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } - let encryptOptions = { - properties: propertiesEncrypt, - inData: new Uint8Array(new Array()) +} + +function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { + return new Promise((resolve, reject) => { + try { + huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); +} + +async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback deleteKeyItem`); + let throwObject = {isThrow: false}; + try { + await deleteKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: deletKeeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } +} +async function testSm2SignVerify() { /* 生成密钥 */ - await publicGenKeyFunc(srcKeyAlias, huksOptions); + await publicGenKeyFunc(generateKeyAlias, genrateKeyOptions); - /* 进行密钥加密操作 */ - await publicInitFunc(srcKeyAlias, encryptOptions); - - encryptOptions.inData = StringToUint8Array(cipherInData); - await publicUpdateFunc(handle, encryptOptions); - encryptUpdateResult = updateResult; + /* 签名 */ + let signHandle; + let signFinishOutData; + await publicInitFunc(generateKeyAlias, signOptions); - encryptOptions.inData = new Uint8Array(new Array()); - await publicFinishFunc(handle, encryptOptions); - if (finishOutData === cipherInData) { - console.info('test finish encrypt err '); - } else { - console.info('test finish encrypt success'); - } + signHandle = handle; + signOptions.inData = StringToUint8Array(signVerifyInData) + await publicUpdateFunc(signHandle, signOptions); - /* 修改加密参数集为解密参数集 */ - propertiesEncrypt.splice(1, 1, { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - }); - let decryptOptions = { - properties: propertiesEncrypt, - inData: new Uint8Array(new Array()) - } + signOptions.inData = new Uint8Array(new Array()); + await publicFinishFunc(signHandle, signOptions); + signFinishOutData = finishOutData; - /* 进行解密操作 */ - await publicInitFunc(srcKeyAlias, decryptOptions); + /* 导出密钥 */ + await publicExportKeyFunc(generateKeyAlias, genrateKeyOptions); - decryptOptions.inData = new Uint8Array(encryptUpdateResult); - await publicUpdateFunc(handle, decryptOptions); + /* 导入密钥 */ + verifyOptions.inData = exportKey; + await publicImportKeyFunc(importKeyAlias, verifyOptions); - decryptOptions.inData = new Uint8Array(new Array()); - await publicFinishFunc(handle, decryptOptions); - if (finishOutData === cipherInData) { - console.info('test finish decrypt success '); - } else { - console.info('test finish decrypt err'); - } + /* 验证签名 */ + let verifyHandle; + await publicInitFunc(importKeyAlias, verifyOptions); - await publicDeleteKeyFunc(srcKeyAlias, huksOptions); -} + verifyHandle = handle; -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testSm4Cipher') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testSm4Cipher(); - }) - } - .width('100%') - .height('100%') - } + verifyOptions.inData = StringToUint8Array(signVerifyInData) + await publicUpdateFunc(verifyHandle, verifyOptions); + + verifyOptions.inData = signFinishOutData; + await publicFinishFunc(verifyHandle, verifyOptions); + + await publicDeleteKeyFunc(generateKeyAlias, genrateKeyOptions); + await publicDeleteKeyFunc(importKeyAlias, genrateKeyOptions); } ``` -**示例2:** - +### 密钥协商 ```ts -/* Cipher操作支持RSA、AES、SM4类型的密钥。 - * - * 以下以AES128 GCM密钥的Promise操作使用为例 +/* + * 以下以X25519 256 TEMP密钥的Callback操作使用为例 */ import huks from '@ohos.security.huks'; +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let srcKeyAliasFirst = "AgreeX25519KeyFirstAlias"; +let srcKeyAliasSecond = "AgreeX25519KeySecondAlias"; +let agreeX25519InData = 'AgreeX25519TestIndata'; +let finishOutData; +let handle; +let exportKey; +let exportKeyFrist; +let exportKeySecond; + +/* 集成生成密钥参数集 */ +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_X25519, +} +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE, +} +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_CURVE25519_KEY_SIZE_256, +} +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, +} +properties[4] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +properties[5] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, +} +let HuksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) +} + +/* 集成第一个协商参数集 */ +let finishProperties = new Array(); +finishProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, + value: huks.HuksKeyStorageType.HUKS_STORAGE_TEMP, +} +finishProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_IS_KEY_ALIAS, + value: true +} +finishProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES, +} +finishProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, +} +finishProperties[4] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, +} +finishProperties[5] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, +} +finishProperties[6] = { + tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, + value: StringToUint8Array(srcKeyAliasFirst+ 'final'), +} +finishProperties[7] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +finishProperties[8] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_ECB, +} +let finishOptionsFrist = { + properties: finishProperties, + inData: StringToUint8Array(agreeX25519InData) +} +/* 集成第二个协商参数集 */ +let finishOptionsSecond = { + properties: finishProperties, + inData: StringToUint8Array(agreeX25519InData) +} +finishOptionsSecond.properties.splice(6, 1, { + tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, + value: StringToUint8Array(srcKeyAliasSecond + 'final'), +}) + function StringToUint8Array(str) { let arr = []; for (let i = 0, j = str.length; i < j; ++i) { @@ -1437,33 +1481,47 @@ function StringToUint8Array(str) { return new Uint8Array(arr); } -function Uint8ArrayToString(fileData) { - let dataString = ''; - for (let i = 0; i < fileData.length; i++) { - dataString += String.fromCharCode(fileData[i]); - } - return dataString; +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { + return new Promise((resolve, reject) => { + try { + huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); } async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; try { - await generateKeyItem(keyAlias, huksOptions) + await generateKeyItem(keyAlias, huksOptions, throwObject) .then((data) => { console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function initSession(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + huks.initSession(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1471,47 +1529,74 @@ function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter promise doInit`); + console.info(`enter callback doInit`); + let throwObject = {isThrow: false}; try { - await huks.initSession(keyAlias, huksOptions) + await initSession(keyAlias, huksOptions, throwObject) .then ((data) => { - console.info(`promise: doInit success, data = ${JSON.stringify(data)}`); + console.info(`callback: doInit success, data = ${JSON.stringify(data)}`); handle = data.handle; }) - .catch(error => { - console.error(`promise: doInit key failed, code: ${error.code}, msg: ${error.message}`); + .catch((error) => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doInit failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`promise: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); } } +function updateSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { + return new Promise((resolve, reject) => { + try { + huks.updateSession(handle, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); +} + async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { console.info(`enter callback doUpdate`); + let throwObject = {isThrow: false}; try { - await updateSession(handle, huksOptions) + await updateSession(handle, huksOptions, throwObject) .then ((data) => { console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); - updateResult = Array.from(data.outData); }) .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function finishSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.updateSession(handle, huksOptions, function (error, data) { + huks.finishSession(handle, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1519,6 +1604,7 @@ function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - updateResult = updateResult.concat(Array.from(data.outData)); + finishOutData = data.outData; console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { - huks.finishSession(handle, huksOptions, function (error, data) { + huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1551,27 +1642,34 @@ function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); + exportKey = data.outData; }) .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { @@ -1582,227 +1680,165 @@ function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -let AAD = '0000000000000000'; -let NONCE = '000000000000'; -let AEAD = '0000000000000000'; -let cipherInData = 'Hks_AES_Cipher_Test_00000000000000000000000000000000000000000000000000000_string'; -let srcKeyAlias = 'huksCipherSm4SrcKeyAlias'; -let updateResult = new Array(); -let encryptUpdateResult = new Array(); -let decryptUpdateResult = new Array(); -let handle; -let finishOutData; - -async function testAesCipher() { - /* 集成生成密钥参数集 & 加密参数集 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_128, - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_GCM, - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) +async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback deleteKeyItem`); + let throwObject = {isThrow: false}; + try { + await deleteKeyItem(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: deletKeeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } +} - let propertiesEncrypt = new Array(); - propertiesEncrypt[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - } - propertiesEncrypt[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT, - } - propertiesEncrypt[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_128, - } - propertiesEncrypt[3] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - propertiesEncrypt[4] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_GCM, - } - propertiesEncrypt[5] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, - } - propertiesEncrypt[6] = { - tag: huks.HuksTag.HUKS_TAG_ASSOCIATED_DATA, - value: StringToUint8Array(AAD), - } - propertiesEncrypt[7] = { - tag: huks.HuksTag.HUKS_TAG_NONCE, - value: StringToUint8Array(NONCE), - } - propertiesEncrypt[8] = { - tag: huks.HuksTag.HUKS_TAG_AE_TAG, - value: StringToUint8Array(AEAD), - } - let encryptOptions = { - properties: propertiesEncrypt, - inData: new Uint8Array(new Array()) - } - - /* 生成密钥 */ - await publicGenKeyFunc(srcKeyAlias, huksOptions); - - /* 进行密钥加密操作 */ - await publicInitFunc(srcKeyAlias, encryptOptions); - - encryptOptions.inData = StringToUint8Array(cipherInData.slice(0,64)); - await publicUpdateFunc(handle, encryptOptions); - encryptUpdateResult = updateResult; - - encryptOptions.inData = StringToUint8Array(cipherInData.slice(64,80)); - await publicFinishFunc(handle, encryptOptions); - encryptUpdateResult = updateResult; - finishOutData = Uint8ArrayToString(new Uint8Array(encryptUpdateResult)); - if (finishOutData === cipherInData) { - console.info('test finish encrypt err '); - } else { - console.info('test finish encrypt success'); - } - - /* 修改加密参数集为解密参数集 */ - propertiesEncrypt.splice(1, 1, { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - }); - propertiesEncrypt.splice(8, 1, { - tag: huks.HuksTag.HUKS_TAG_AE_TAG, - value: new Uint8Array(encryptUpdateResult.splice(encryptUpdateResult.length - 16,encryptUpdateResult.length)) - }); - let decryptOptions = { - properties: propertiesEncrypt, - inData: new Uint8Array(new Array()) - } - - /* 进行解密操作 */ - await publicInitFunc(srcKeyAlias, decryptOptions); +async function testAgree() { + /* 1.生成两个密钥并导出 */ + await publicGenKeyFunc(srcKeyAliasFirst, HuksOptions); + await publicGenKeyFunc(srcKeyAliasSecond, HuksOptions); - decryptOptions.inData = new Uint8Array(encryptUpdateResult.slice(0,64)); - await publicUpdateFunc(handle, decryptOptions); - decryptUpdateResult = updateResult; + await publicExportKeyFunc(srcKeyAliasFirst, HuksOptions); + exportKeyFrist = exportKey; + await publicExportKeyFunc(srcKeyAliasFirst, HuksOptions); + exportKeySecond = exportKey; - decryptOptions.inData = new Uint8Array(encryptUpdateResult.slice(64,encryptUpdateResult.length)); - await publicFinishFunc(handle, decryptOptions); - decryptUpdateResult = updateResult; - finishOutData = Uint8ArrayToString(new Uint8Array(decryptUpdateResult)); - if (finishOutData === cipherInData) { - console.info('test finish decrypt success '); - } else { - console.info('test finish decrypt err'); - } + /* 对第一个密钥进行协商 */ + await publicInitFunc(srcKeyAliasFirst, HuksOptions); + HuksOptions.inData = exportKeySecond; + await publicUpdateFunc(handle, HuksOptions); + await publicFinishFunc(handle, finishOptionsFrist); - await publicDeleteKeyFunc(srcKeyAlias, huksOptions); -} + /* 对第二个密钥进行协商 */ + await publicInitFunc(srcKeyAliasSecond, HuksOptions); + HuksOptions.inData = exportKeyFrist; + await publicUpdateFunc(handle, HuksOptions); + await publicFinishFunc(handle, finishOptionsSecond); -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testAesCipher') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testAesCipher(); - }) - } - .width('100%') - .height('100%') - } + await publicDeleteKeyFunc(srcKeyAliasFirst, HuksOptions); + await publicDeleteKeyFunc(srcKeyAliasSecond, HuksOptions); } ``` -### 密钥签名验签 - -签名:给我们将要发送的数据,做上一个唯一签名;验签: 对发送者发送过来的签名进行验证 。 - -开发步骤如下: - -1. 生成密钥。 -2. 密钥签名。 -3. 导出签名密钥。 -4. 导入签名密钥。 -5. 密钥验签。 - -**支持的密钥类型:** - -仅HksInit对paramSet中参数有要求,其他三段式接口对paramSet无要求 - -| HUKS_ALG_ALGORITHM | HUKS_ALG_KEY_SIZE | HUKS_ALG_PURPOSE | HUKS_ALG_PADDING | HUKS_TAG_DIGEST | -| ------------------ | ------------------------------------------------------------ | ---------------------------------------------- | ----------------------- | ------------------------------------------------------------ | -| HUKS_ALG_RSA | HUKS_RSA_KEY_SIZE_512 HUKS_RSA_KEY_SIZE_768 HUKS_RSA_KEY_SIZE_1024 HUKS_RSA_KEY_SIZE_2048 HUKS_RSA_KEY_SIZE_3072 HUKS_RSA_KEY_SIZE_4096 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | HUKS_PADDING_PKCS1_V1_5 | HUKS_DIGEST_MD5 HUKS_DIGEST_NONE HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | -| HUKS_ALG_RSA | HUKS_RSA_KEY_SIZE_512 HUKS_RSA_KEY_SIZE_768 HUKS_RSA_KEY_SIZE_1024 HUKS_RSA_KEY_SIZE_2048 HUKS_RSA_KEY_SIZE_3072 HUKS_RSA_KEY_SIZE_4096 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | HUKS_PADDING_PSS | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | -| HUKS_ALG_DSA | HUKS_RSA_KEY_SIZE_1024 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | 【非必选】 | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | -| HUKS_ALG_ECC | HUKS_ECC_KEY_SIZE_224 HUKS_ECC_KEY_SIZE_256 HUKS_ECC_KEY_SIZE_384 HUKS_ECC_KEY_SIZE_521 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | 【非必选】 | HUKS_DIGEST_NONE HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | - -Ed25519的签名验签是在算法引擎中做的HASH操作,因此该算法的三段式接口处理较特殊: - -Update过程只将inData发送到Core中记录在ctx中,不进行Hash计算,最后在finish操作时,对inData组合后的数据进行签名、验签计算. - -| HUKS_ALG_ALGORITHM | HUKS_ALG_KEY_SIZE | HUKS_ALG_PURPOSE | -| ------------------ | ---------------------------- | ----------------------------------------------- | -| HUKS_ALG_ED25519 | HUKS_CURVE25519_KEY_SIZE_256 | HUKS_KEY_PURPOSE_SIGN HUKS_KEY_PURPOSE_VERIFY | - -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 - -在使用示例前,需要先了解几个预先定义的变量: +### 密钥派生 +```ts +/* + * 以下以HKDF256密钥的Promise操作使用为例 + */ +import huks from '@ohos.security.huks'; -| 参数名 | 类型 | 必填 | 说明 | -| ----------------- | ----------- | ---- | ------------------------ | -| generateKeyAlias | string | 是 | 生成密钥别名。 | -| importKeyAlias | string | 是 | 导入密钥别名。 | -| genrateKeyOptions | HuksOptions | 是 | 用于存放生成key所需TAG。 | -| signOptions | HuksOptions | 是 | 用于存放签名key所需TAG。 | -| verifyOptions | HuksOptions | 是 | 用于存放验签key所需TAG。 | +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let srcKeyAlias = "hkdf_Key"; +let deriveHkdfInData = "deriveHkdfTestIndata"; +let handle; +let finishOutData; +let HuksKeyDeriveKeySize = 32; -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +/* 集成生成密钥参数集 */ +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES, +} +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DERIVE, +} +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256, +} +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_128, +} +let huksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) +} -**示例:** +/* 集成init时密钥参数集 */ +let initProperties = new Array(); +initProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_HKDF, +} +initProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DERIVE, +} +initProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256, +} +initProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_DERIVE_KEY_SIZE, + value: HuksKeyDeriveKeySize, +} +let initOptions = { + properties: initProperties, + inData: new Uint8Array(new Array()) +} -```ts -/* Sign/Verify操作支持RSA、ECC、SM2、ED25519、DSA类型的密钥。 - * - * 以下以SM2密钥的Callback操作使用为例 - */ -import huks from '@ohos.security.huks'; +/* 集成finish时密钥参数集 */ +let finishProperties = new Array(); +finishProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, + value: huks.HuksKeyStorageType.HUKS_STORAGE_PERSISTENT, +} +finishProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_IS_KEY_ALIAS, + value: true, +} +finishProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_AES, +} +finishProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, +} +finishProperties[4] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | + huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, +} +finishProperties[5] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, +} +finishProperties[6] = { + tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, + value: StringToUint8Array(srcKeyAlias), +} +finishProperties[7] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +finishProperties[8] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_ECB, +} +let finishOptions = { + properties: finishProperties, + inData: new Uint8Array(new Array()) +} function StringToUint8Array(str) { let arr = []; @@ -1812,22 +1848,7 @@ function StringToUint8Array(str) { return new Uint8Array(arr); } -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { @@ -1838,28 +1859,33 @@ function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback doInit`); +async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; try { - await initSession(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback1: doInit success, data = ${JSON.stringify(data)}`); - handle = data.handle; + await generateKeyItem(keyAlias, huksOptions, throwObject) + .then((data) => { + console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); }) - .catch((error) => { - console.error(`callback1: doInit failed, code: ${error.code}, msg: ${error.message}`); + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function initSession(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { +function initSession(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { huks.initSession(keyAlias, huksOptions, function (error, data) { @@ -1870,27 +1896,34 @@ function initSession(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { - console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); + console.info(`callback: doInit success, data = ${JSON.stringify(data)}`); + handle = data.handle; }) - .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + .catch((error) => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doInit failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function updateSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { huks.updateSession(handle, huksOptions, function (error, data) { @@ -1901,28 +1934,33 @@ function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - finishOutData = data.outData;; - console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); + console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { +function finishSession(handle:number, huksOptions:huks.HuksOptions, throwObject) : Promise { return new Promise((resolve, reject) => { try { huks.finishSession(handle, huksOptions, function (error, data) { @@ -1933,62 +1971,37 @@ function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); - exportKey = data.outData; + finishOutData = data.outData; + console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); + if (throwObject.isThrow) { + throw(error); } else { - resolve(data); + console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); } }); - } catch (error) { - throw(error); - } - }); -} - -async function publicImportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter promise importKeyItem`); - try { - await importKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: importKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: importKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); } catch (error) { - console.error(`callback: importKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { - huks.importKeyItem(keyAlias, huksOptions, function (error, data) { + huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { if (error) { reject(error); } else { @@ -1996,6 +2009,7 @@ function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); @@ -2003,1471 +2017,563 @@ function importKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { console.info(`enter callback deleteKeyItem`); + let throwObject = {isThrow: false}; try { - await deleteKeyItem(keyAlias, huksOptions) + await deleteKeyItem(keyAlias, huksOptions, throwObject) .then ((data) => { console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: deletKeeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} +async function testDerive() { + /* 生成密钥 */ + await publicGenKeyFunc(srcKeyAlias, huksOptions); -let signVerifyInData = 'signVerifyInDataForTest'; -let generateKeyAlias = 'generateKeyAliasForTest'; -let importKeyAlias = 'importKeyAliasForTest'; -let handle; -let exportKey; -let finishOutData; + /* 进行派生操作 */ + await publicInitFunc(srcKeyAlias, initOptions); -/* 集成生成密钥参数集 */ -let generateKeyProperties = new Array(); -generateKeyProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM2, -} -generateKeyProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY, -} -generateKeyProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, -} -generateKeyProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, -} -let genrateKeyOptions = { - properties: generateKeyProperties, - inData: new Uint8Array(new Array()) -} - -/* 集成签名参数集 */ -let signProperties = new Array(); -signProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM2, -} -signProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN -} -signProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, -} -signProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, -} -let signOptions = { - properties: signProperties, - inData: new Uint8Array(new Array()) -} - -/* 集成验签参数集 */ -let verifyProperties = new Array(); -verifyProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM2, -} -verifyProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY -} -verifyProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, -} -verifyProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_SM2_KEY_SIZE_256, -} -let verifyOptions = { - properties: verifyProperties, - inData: new Uint8Array(new Array()) -} - -async function testSm2SignVerify() { - /* 生成密钥 */ - await publicGenKeyFunc(generateKeyAlias, genrateKeyOptions); - - /* 签名 */ - let signHandle; - let signFinishOutData; - await publicInitFunc(generateKeyAlias, signOptions); - - signHandle = handle; - signOptions.inData = StringToUint8Array(signVerifyInData) - await publicUpdateFunc(signHandle, signOptions); - - signOptions.inData = new Uint8Array(new Array()); - await publicFinishFunc(signHandle, signOptions); - signFinishOutData = finishOutData; - - /* 导出密钥 */ - await publicExportKeyFunc(generateKeyAlias, genrateKeyOptions); - - /* 导入密钥 */ - verifyOptions.inData = exportKey; - await publicImportKeyFunc(importKeyAlias, verifyOptions); - - /* 验证签名 */ - let verifyHandle; - await publicInitFunc(importKeyAlias, verifyOptions); - - verifyHandle = handle; - - verifyOptions.inData = StringToUint8Array(signVerifyInData) - await publicUpdateFunc(verifyHandle, verifyOptions); - - verifyOptions.inData = signFinishOutData; - await publicFinishFunc(verifyHandle, verifyOptions); - - await publicDeleteKeyFunc(generateKeyAlias, genrateKeyOptions); - await publicDeleteKeyFunc(importKeyAlias, genrateKeyOptions); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testSm2SignVerify') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testSm2SignVerify(); - }) - } - .width('100%') - .height('100%') - } -} -``` - -### 密钥协商 - -两个或多个对象生成会话密钥,通过会话密钥进行交流 。 - -开发步骤如下: - -1. 生成两个密钥。 -2. 分别导出密钥。 -3. 交叉进行密钥协商。 - -**支持的密钥类型:** - -仅HksInit和HksFinish接口对paramSet参数有要求,HksUpdate接口对paramSet无要求 - -HksInit对paramSet中参数的要求 - -| HUKS_ALG_ALGORITHM | HUKS_ALG_KEY_SIZE | HUKS_ALG_PURPOSE | -| ------------------ | ------------------------------------------------------------ | ---------------------- | -| HUKS_ALG_ECDH | HUKS_ECC_KEY_SIZE_224 HUKS_ECC_KEY_SIZE_256 HUKS_ECC_KEY_SIZE_384 HUKS_ECC_KEY_SIZE_521 | HUKS_KEY_PURPOSE_AGREE | -| HUKS_ALG_DH | HUKS_DH_KEY_SIZE_2048 HUKS_DH_KEY_SIZE_3072 HUKS_DH_KEY_SIZE_4096 | HUKS_KEY_PURPOSE_AGREE | -| HUKS_ALG_X25519 | HUKS_CURVE25519_KEY_SIZE_256 | HUKS_KEY_PURPOSE_AGREE | - -HksFinish对paramSet中参数的要求: - -派生后的密钥作为对称密钥进行使用 - -| HUKS_TAG_KEY_STORAGE_FLAG | HUKS_TAG_KEY_ALIAS | HUKS_TAG_IS_KEY_ALIAS | HUKS_TAG_ALGORITHM | HUKS_TAG_KEY_SIZE | HUKS_TAG_PURPOSE | HUKS_TAG_PADDING | HUKS_TAG_DIGEST | HUKS_TAG_BLOCK_MODE | -| ------------------------------ | ------------------ | --------------------- | ------------------ | ------------------------------------------------------------ | -------------------------------------------------- | ------------------ | ------------------------------------------------------------ | ------------------------------------------- | -| 未设置 或者 HUKS_STORAGE_TEMP | 不需要 | TRUE | 不需要 | 不需要 | 不需要 | 不需要 | 不需要 | 不需要 | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_PKCS7 | 【非必选】 | HUKS_MODE_CCM HUKS_MODE_GCM HUKS_MODE_CTP | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_DERIVE | 【非必选】 | HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【非必选】 | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_HMAC | 8的倍数(单位:bit) | HUKS_KEY_PURPOSE_MAC | 【非必选】 | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【非必选】 | - -> **说明** -> -> HUKS_ALG_AES的SIZE需要满足:协商后的密钥长度(转换成bit)>=选择的HUKS_TAG_KEY_SIZE -> -> 存储的 keyAlias 密钥别名最大为64字节 - -在使用示例前,需要先了解几个预先定义的变量: - -| 参数名 | 类型 | 必填 | 说明 | -| ------------------- | ----------- | ---- | -------------------------------------- | -| srcKeyAliasFirst | string | 是 | 生成密钥别名。 | -| srcKeyAliasSecond | string | 是 | 生成密钥别名,用于结果对比。 | -| huksOptions | HuksOptions | 是 | 用于存放生成key所需TAG。 | -| finishOptionsFrist | HuksOptions | 是 | 用于存放协商key所需TAG。 | -| finishOptionsSecond | HuksOptions | 是 | 用于存放协商key所需TAG,用于结果对比。 | - -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 - -**示例:** - -```ts -/* agree操作支持ECDH、DH、X25519类型的密钥。 - * - * 以下以X25519 256 TEMP密钥的Promise操作使用为例 - */ -import huks from '@ohos.security.huks'; - -function StringToUint8Array(str) { - let arr = []; - for (let i = 0, j = str.length; i < j; ++i) { - arr.push(str.charCodeAt(i)); - } - return new Uint8Array(arr); -} - -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback doInit`); - try { - await initSession(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback1: doInit success, data = ${JSON.stringify(data)}`); - handle = data.handle; - }) - .catch((error) => { - console.error(`callback1: doInit failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function initSession(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.initSession(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doUpdate`); - try { - await updateSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.updateSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicFinishFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doFinish`); - try { - await finishSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.finishSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicExportKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback export`); - try { - await exportKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: exportKeyItem success, data = ${JSON.stringify(data)}`); - exportKey = data.outData; - }) - .catch(error => { - console.error(`callback: exportKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: exportKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function exportKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.exportKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); - try { - await deleteKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -let srcKeyAliasFirst = "AgreeX25519KeyFirstAlias"; -let srcKeyAliasSecond = "AgreeX25519KeySecondAlias"; -let agreeX25519InData = 'AgreeX25519TestIndata'; -let handle; -let exportKey; -let exportKeyFrist; -let exportKeySecond; - -async function testAgree() { - /* 集成生成密钥参数集 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_X25519, - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_AGREE, - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_CURVE25519_KEY_SIZE_256, - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, - } - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - properties[5] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_CBC, - } - let HuksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } - - /* 1.生成两个密钥并导出 */ - await publicGenKeyFunc(srcKeyAliasFirst, HuksOptions); - await publicGenKeyFunc(srcKeyAliasSecond, HuksOptions); - - await publicExportKeyFunc(srcKeyAliasFirst, HuksOptions); - exportKeyFrist = exportKey; - await publicExportKeyFunc(srcKeyAliasFirst, HuksOptions); - exportKeySecond = exportKey; - - /* 集成第一个协商参数集 */ - let finishProperties = new Array(); - finishProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, - value: huks.HuksKeyStorageType.HUKS_STORAGE_TEMP, - } - finishProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_IS_KEY_ALIAS, - value: true - } - finishProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - } - finishProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, - } - finishProperties[4] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - } - finishProperties[5] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, - } - finishProperties[6] = { - tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, - value: StringToUint8Array(srcKeyAliasFirst+ 'final'), - } - finishProperties[7] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - finishProperties[8] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_ECB, - } - let finishOptionsFrist = { - properties: finishProperties, - inData: StringToUint8Array(agreeX25519InData) - } - - /* 对第一个密钥进行协商 */ - await publicInitFunc(srcKeyAliasFirst, HuksOptions); - HuksOptions.inData = exportKeySecond; - await publicUpdateFunc(handle, HuksOptions); - await publicFinishFunc(handle, finishOptionsFrist); - - /* 集成第二个协商参数集 */ - let finishOptionsSecond = { - properties: finishProperties, - inData: StringToUint8Array(agreeX25519InData) - } - finishOptionsSecond.properties.splice(6, 1, { - tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, - value: StringToUint8Array(srcKeyAliasSecond + 'final'), - }) - - /* 对第二个密钥进行协商 */ - await publicInitFunc(srcKeyAliasSecond, HuksOptions); - HuksOptions.inData = exportKeyFrist; - await publicUpdateFunc(handle, HuksOptions); - await publicFinishFunc(handle, finishOptionsSecond); - - - await publicDeleteKeyFunc(srcKeyAliasFirst, HuksOptions); - await publicDeleteKeyFunc(srcKeyAliasSecond, HuksOptions); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testAgree') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testAgree(); - }) - } - .width('100%') - .height('100%') - } -} -``` - -### 密钥派生 - -从一个密钥产生出一个或者多个密钥。 - -开发步骤如下: - -1. 生成密钥。 -2. 进行密钥派生。 - -**支持的密钥类型:** - -仅HksInit和HksFinish接口对paramSet参数有要求,HksUpdate接口对paramSet无要求 - -HksInit对paramSet中参数的要求 - -| HUKS_TAG_ALGORITHM | HUKS_TAG_PURPOSE | HUKS_TAG_DIGEST | HUKS_TAG_DERIVE_KEY_SIZE | -| ------------------------------------------------------------ | ----------------------- | ---------------------------------------------------------- | ------------------------ | -| HUKS_ALG_HKDF (支持长度: HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256) | HUKS_KEY_PURPOSE_DERIVE | HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【必选】 | -| HUKS_ALG_PBKDF2 (支持长度: HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256) | HUKS_KEY_PURPOSE_DERIVE | HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【必选】 | - -HksFinish对paramSet中参数的要求: - -派生后的密钥作为对称密钥进行使用 - -| HUKS_TAG_KEY_STORAGE_FLAG | HUKS_TAG_KEY_ALIAS | HUKS_TAG_IS_KEY_ALIAS | HUKS_TAG_ALGORITHM | HUKS_TAG_KEY_SIZE | HUKS_TAG_PURPOSE | HUKS_TAG_PADDING | HUKS_TAG_DIGEST | HUKS_TAG_BLOCK_MODE | -| ------------------------------ | ------------------ | --------------------- | ------------------ | ------------------------------------------------------------ | -------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------ | ------------------------------------------- | -| 未设置 或者 HUKS_STORAGE_TEMP | 不需要 | TRUE | 不需要 | 不需要 | 不需要 | 不需要 | 不需要 | 不需要 | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE HUKS_PADDING_PKCS7 | 【非必选】 | HUKS_MODE_CBC HUKS_MODE_ECB | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_ENCRYPT HUKS_KEY_PURPOSE_DECRYPT | HUKS_PADDING_NONE | 【非必选】 | HUKS_MODE_CCM HUKS_MODE_GCM HUKS_MODE_CTR | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_AES | HUKS_AES_KEY_SIZE_128 HUKS_AES_KEY_SIZE_192 HUKS_AES_KEY_SIZE_256 | HUKS_KEY_PURPOSE_DERIVE | 【非必选】 | HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【非必选】 | -| HUKS_STORAGE_PERSISTENT | 【必选】最大64字节 | TRUE | HUKS_ALG_HMAC | 8的倍数(单位:bit) | HUKS_KEY_PURPOSE_MAC | 【非必选】 | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【非必选】 | - -> **说明** -> -> HUKS_ALG_AES的SIZE需要满足:派生后的密钥长度(转换成bit)>=选择的HUKS_TAG_KEY_SIZE -> -> 存储的 keyAlias 密钥别名最大为64字节 - -在使用示例前,需要先了解几个预先定义的变量: - -| 参数名 | 类型 | 必填 | 说明 | -| ------------- | ----------- | ---- | ---------------- | -| srcKeyAlias | string | 是 | 生成密钥别名。 | -| huksOptions | HuksOptions | 是 | 生成密钥参数集。 | -| finishOptions | HuksOptions | 是 | 派生密钥参数集。 | - -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 - -**示例:** - -```ts -/* derive操作支持HKDF、pbdkf类型的密钥。 - * - * 以下以HKDF256密钥的Promise操作使用为例 - */ -import huks from '@ohos.security.huks'; - -function StringToUint8Array(str) { - let arr = []; - for (let i = 0, j = str.length; i < j; ++i) { - arr.push(str.charCodeAt(i)); - } - return new Uint8Array(arr); -} - -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter promise doInit`); - try { - await huks.initSession(keyAlias, huksOptions) - .then ((data) => { - console.info(`promise: doInit success, data = ${JSON.stringify(data)}`); - handle = data.handle; - }) - .catch(error => { - console.error(`promise: doInit key failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`promise: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doUpdate`); - try { - await updateSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.updateSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicFinishFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doFinish`); - try { - await finishSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.finishSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); - try { - await deleteKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); + initOptions.inData = StringToUint8Array(deriveHkdfInData); + await publicUpdateFunc(handle, initOptions); + await publicFinishFunc(handle, finishOptions); + + await publicDeleteKeyFunc(srcKeyAlias, huksOptions); } +``` -let deriveHkdfInData = "deriveHkdfTestIndata"; -let srcKeyAlias = "deriveHkdfKeyAlias"; -let handle; -let HuksKeyDeriveKeySize = 32; +## 密钥访问控制 -async function testDerive() { - /* 集成生成密钥参数集 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DERIVE, - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256, - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_128, - } - let huksOptions = { - properties: properties, - inData: new Uint8Array(new Array()) - } +HUKS提供了全面完善的密钥访问控制能力,确保存储在HUKS中的密钥被合法正确的访问。 - /* 生成密钥 */ - await publicGenKeyFunc(srcKeyAlias, huksOptions); +首先,业务只能访问属于自己的密钥,即只能访问通过HUKS生成或导入的密钥。 +其次,支持密钥的用户身份认证访问控制,对于高安敏感的业务密钥,需要在使用密钥的时候,再次要求用户即时的验证锁屏密码或生物特征,验证通过后,才能使用业务密钥。 +除此之外,HUKS还支持严格限制密钥的使用用途,如支持只允许AES密钥进行加密解密,只允许RSA密钥进行签名验签。 - /* 调整init时的参数集 */ - huksOptions.properties.splice(0, 1, { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_HKDF, - }); - huksOptions.properties.splice(3, 1, { - tag: huks.HuksTag.HUKS_TAG_DERIVE_KEY_SIZE, - value: HuksKeyDeriveKeySize, - }); +**用户身份认证访问控制** - let finishProperties = new Array(); - finishProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, - value: huks.HuksKeyStorageType.HUKS_STORAGE_PERSISTENT, - } - finishProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_IS_KEY_ALIAS, - value: true, - } - finishProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - } - finishProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, - } - finishProperties[4] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | - huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, - } - finishProperties[5] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_NONE, - } - finishProperties[6] = { - tag: huks.HuksTag.HUKS_TAG_KEY_ALIAS, - value: StringToUint8Array(srcKeyAlias), - } - finishProperties[7] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_NONE, - } - finishProperties[8] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_ECB, - } - let finishOptions = { - properties: finishProperties, - inData: new Uint8Array(new Array()) - } +生成或导入密钥时,可以指定密钥必须经过用户身份认证后才能使用。您可以指定用于解锁设备锁屏的凭据(锁屏密码、指纹、人脸)的子集进行身份认证。在生成/导入密钥后,即使应用进程被攻击也不会导致未经用户授权的密钥访问,一般用于高敏感且高级别安全保护的场景,比如免密登录、免密支付、自动填充密码保护场景。 - /* 进行派生操作 */ - await publicInitFunc(srcKeyAlias, huksOptions); +除用户身份认证外,应用还须将密钥的授权访问类型(即失效条件)设置为以下两种类型之一: +- **清除锁屏密码后密钥永久无效。** 设置此模式的前提是当前用户已经设置了锁屏密码,在生成/导入密钥后,一旦清除了锁屏密码,此类密钥将永久失效。注意,修改密码不会导致失效情况发生。此模式适合那些需要锁屏密码授权访问或用户强相关的数据保护的场景。 +- **用户新录入生物特征后永久无效。** 此模式需要当前用户至少录入了一个生物特征(如指纹)才能生效,在生成/导入密钥后,一旦录入新的生物特征,这些密钥将永久失效。注意,仅删除生物特征不会导致失效情况发生。如果您不希望新录入的生物特征后,用户还可以授权访问原有数据(密钥保护的数据),那么可以使用此模式,如免密登录,免密支付等场景。 - huksOptions.inData = StringToUint8Array(deriveHkdfInData); - await publicUpdateFunc(handle, huksOptions); - await publicFinishFunc(handle, finishOptions); +此外,为了保证密钥使用时用户认证结果的有效性(不可重放),HUKS支持挑战值校验:在身份认证前,需要从HUKS获取挑战值(调用[huks.initSession()](../reference/apis/js-apis-huks.md#huksinitsession9)返回的[HuksSessionHandle](../reference/apis/js-apis-huks.md#hukssessionhandle9)中)传给用户身份认证方法([userIAM_userAuth.getAuthInstance](../reference/apis/js-apis-useriam-userauth.md#authinstance9)),然后在密钥操作时校验认证令牌的挑战值。 - huksOptions.properties.splice(0, 1, { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_AES, - }); - huksOptions.properties.splice(3, 1, { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_128, - }); +**开发流程** - await publicDeleteKeyFunc(srcKeyAlias, huksOptions); -} +设置了二次用户身份认证的密钥,需要先初始化密钥会话并获取挑战值,然后将HUKS生成的挑战值传至用户身份认证方法进行用户身份认证,认证通过后获取一个认证令牌,将认证令牌传至HUKS进行密钥操作。 -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testDerive') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testDerive(); - }) - } - .width('100%') - .height('100%') - } -} -``` +![huks_key_user_auth_work_flow](./figures/huks_key_user_auth_work_flow.png) -### 密钥mac +**接口说明** -基于密钥数据进行mac摘要所获得的一个哈希值。 +1. 生成或导入密钥时,在密钥属性集中需指定三个参数:用户认证类型[HuksUserAuthType](../reference/apis/js-apis-huks.md#huksuserauthtype9)、授权访问类型[HuksAuthAccessType](../reference/apis/js-apis-huks.md#huksauthaccesstype9)、挑战值类型[HuksChallengeType](../reference/apis/js-apis-huks.md#hukschallengetype9)。 -开发步骤如下: + **表3** 用户认证类型:三种类型的子集 + | 名称 | 值 | 说明 | + | ------------------------------- |---|------------------------ | + | HUKS_USER_AUTH_TYPE_FINGERPRINT |0x0001 | 用户认证类型为指纹,允许和人脸、锁屏密码同时设置 | + | HUKS_USER_AUTH_TYPE_FACE |0x0002 | 用户认证类型为人脸 ,允许和指纹、锁屏密码同时设置 | + | HUKS_USER_AUTH_TYPE_PIN |0x0004 | 用户认证类型为锁屏密码,允许和人脸、指纹同时设置 | -1. 生成密钥。 -2. 密钥mac。 + **表4** 安全访问类型:二选一 + | 名称 | 值 | 说明 | + | --------------------------------------- | ---- | ------------------------------------------------ | + | HUKS_AUTH_ACCESS_INVALID_CLEAR_PASSWORD | 1 | 清除锁屏密码后密钥无法访问。 | + | HUKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL | 2 | 新录入生物特征后密钥无法访问,用户认证类型须包含生物认证类型。 | -**支持的密钥类型:** + **表5** 挑战值类型:三选一 + | 名称 | 值 | 说明 | + | ------------------------------- | ---- | ------------------------------ | + | HUKS_CHALLENGE_TYPE_NORMAL | 0 | 普通类型,每次密钥的使用需要独立的一次用户认证 | + | HUKS_CHALLENGE_TYPE_CUSTOM | 1 | 自定义类型,支持和多个密钥共享一次用户认证| + | HUKS_CHALLENGE_TYPE_NONE | 2 | 无挑战值类型,用户认证时不需要挑战值 | -HksInit对paramSet中参数的要求,其他三段式接口对paramSet无要求 + > **注意** + > + > 当指定挑战值类型为**HUKS_CHALLENGE_TYPE_NONE** 时,不需要传递挑战值,但是存在新的限制:在用户身份认证后,一段时间内允许访问该密钥,超时后不能访问,需要重新认证才能访问。因此应用需要额外指定超时时间**HUKS_TAG_AUTH_TIMEOUT**属性(最大60秒)。 -| HUKS_TAG_ALGORITHM | HUKS_TAG_KEY_SIZE | HUKS_TAG_PURPOSE | HUKS_TAG_DIGEST | HUKS_TAG_PADDING | HUKS_TAG_BLOCK_MODE | -| ------------ | ---------- | ------------------- | ------------------------------------------------------------ | ---------- | ---------- | -| HUKS_ALG_HMAC | 【非必选】 | HUKS_KEY_PURPOSE_MAC | HUKS_DIGEST_SHA1 HUKS_DIGEST_SHA224 HUKS_DIGEST_SHA256 HUKS_DIGEST_SHA384 HUKS_DIGEST_SHA512 | 【非必选】 | 【非必选】 | -| HUKS_ALG_SM3 | 【非必选】 | HUKS_KEY_PURPOSE_MAC | HUKS_DIGEST_SM3 | 【非必选】 | 【非必选】 | -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 +2. 使用密钥时,先初始化密钥会话,然后根据密钥生成/导入阶段指定的挑战值类型属性是否需要获取挑战值,或组装新的挑战值。 + + **表6** 使用密钥的接口介绍 -在使用示例前,需要先了解几个预先定义的变量: + | 接口名 | 描述 | + | -------------------------------------- | ----------------------------| + |initSession(keyAlias: string, options: HuksOptions, callback: AsyncCallback\) : void| 初始化密钥会话,获取挑战值| + |updateSession(handle: number, options: HuksOptions, token: Uint8Array, callback: AsyncCallback\) : void| 分段操作数据,传递认证令牌| + |finishSession(handle: number, options: HuksOptions, token: Uint8Array, callback: AsyncCallback\) : void| 结束密钥会话,传递认证令牌| -| 参数名 | 类型 | 必填 | 说明 | -| ----------- | ----------- | ---- | -------------- | -| srcKeyAlias | string | 是 | 生成密钥别名。 | -| huksOptions | HuksOptions | 是 | 密钥参数集。 | -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 -**示例:** +**开发步骤** +1. 生成密钥并指定指纹访问控制和相关属性 ```ts -/* mac操作支持HMAC、SM3类型的密钥。 - * - * 以下以SM3 256密钥的Promise操作使用为例 - */ import huks from '@ohos.security.huks'; -function StringToUint8Array(str) { - let arr = []; - for (let i = 0, j = str.length; i < j; ++i) { - arr.push(str.charCodeAt(i)); - } - return new Uint8Array(arr); +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let keyAlias = 'dh_key_fingerprint_access'; +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM4, } - -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, } - -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, } - -async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback doInit`); - try { - await initSession(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback1: doInit success, data = ${JSON.stringify(data)}`); - handle = data.handle; - }) - .catch((error) => { - console.error(`callback1: doInit failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); - } +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, } - -function initSession(keyAlias:string, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.initSession(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +properties[4] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, } - -async function publicUpdateFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doUpdate`); - try { - await updateSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); - } +// 指定密钥身份认证的类型:指纹 +properties[5] = { + tag: huks.HuksTag.HUKS_TAG_USER_AUTH_TYPE, + value: huks.HuksUserAuthType.HUKS_USER_AUTH_TYPE_FINGERPRINT } - -function updateSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.updateSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +// 指定密钥安全授权的类型(失效类型):新录入生物特征(指纹)后无效 +properties[6] = { + tag: huks.HuksTag.HUKS_TAG_KEY_AUTH_ACCESS_TYPE, + value: huks.HuksAuthAccessType.HUKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL } - -async function publicFinishFunc(handle:number, huksOptions:huks.HuksOptions) { - console.info(`enter callback doFinish`); - try { - await finishSession(handle, huksOptions) - .then ((data) => { - console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); - } +// 指定挑战值的类型:默认类型 +properties[7] = { + tag: huks.HuksTag.HUKS_TAG_CHALLENGE_TYPE, + value: huks.HuksChallengeType.HUKS_CHALLENGE_TYPE_NORMAL } - -function finishSession(handle:number, huksOptions:huks.HuksOptions) : Promise { - return new Promise((resolve, reject) => { - try { - huks.finishSession(handle, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +let huksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) } -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); +/* + * 生成密钥 + */ +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { + return new Promise((resolve, reject) => { try { - await deleteKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); -} - -let srcKeyAlias = "sm3KeyAlias"; -let hmacInData = 'sm3TestIndata'; -let handle; - -async function testMac() { - /* 集成生成密钥参数集 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_SM3, - } - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_MAC, - } - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SM3, - } - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, - } - let huksOptions = { - properties:properties, - inData:new Uint8Array(new Array()) - } - - /* 生成密钥 */ - await publicGenKeyFunc(srcKeyAlias, huksOptions); - - /* 修改init时的参数集并进行mac操作 */ - huksOptions.properties.splice(3, 3); - await publicInitFunc(srcKeyAlias, huksOptions); - huksOptions.inData = StringToUint8Array(hmacInData); - await publicUpdateFunc(handle, huksOptions); - huksOptions.inData = new Uint8Array(new Array()); - await publicFinishFunc(handle, huksOptions); - - huksOptions.properties.splice(1, 0, { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256, - }); - await publicDeleteKeyFunc(srcKeyAlias, huksOptions); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testMac') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testMac(); - }) + huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); } - .width('100%') - .height('100%') + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); } + }); } -``` - -### AttestID - -应用生成非对称密钥后,可以通过id attestation获取证书链,ID Attestation包含支持如下设备信息: BRAND, DEVICE, PRODUCT, SERIAL, IMEI, MEID, MANUFACTURER, MODEL, SOCID, UDID。 - -应用还可以通过key attestation获取证书链。 - -ID Attestation和Key Attestation只有拥有TEE环境的设备才具备该功能。 - -开发步骤如下: - -1. 生成证书。 -2. 获取证书信息。 -**支持的密钥类型:** - -RSA512, RSA768, RSA1024, RSA2048, RSA3072, RSA4096, ECC224, ECC256, ECC384, ECC521, X25519 - -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 - -在使用示例前,需要先了解几个预先定义的变量: - -| 参数名 | 类型 | 必填 | 说明 | -| -------- | ----------- | ---- | ------------------------------------ | -| keyAlias | string | 是 | 密钥别名,存放待获取证书密钥的别名。 | -| options | HuksOptions | 是 | 用于获取证书时指定所需参数与数据。 | +async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; + try { + await generateKeyItem(keyAlias, huksOptions, throwObject) + .then((data) => { + console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + } +} -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +async function TestGenKeyForFingerprintAccessControl() { + await publicGenKeyFunc(keyAlias, huksOptions); +} +``` -**示例:** +2. 初始化密钥会话获取挑战值并发起指纹认证获取认证令牌 ```ts -/* 证书AttestID操作示例如下*/ import huks from '@ohos.security.huks'; +import userIAM_userAuth from '@ohos.userIAM.userAuth'; -function StringToUint8Array(str) { - let arr = []; - for (let i = 0, j = str.length; i < j; ++i) { - arr.push(str.charCodeAt(i)); - } - return new Uint8Array(arr); +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let srcKeyAlias = 'sm4_key_fingerprint_access'; +let handle; +let challenge; +let fingerAuthToken; +let authType = userIAM_userAuth.UserAuthType.FINGERPRINT; +let authTrustLevel = userIAM_userAuth.AuthTrustLevel.ATL1; + +/* 集成生成密钥参数集 & 加密参数集 */ +let properties = new Array(); +properties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM4, +} +properties[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_DECRYPT, +} +properties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, +} +properties[3] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, +} +properties[4] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +let huksOptions = { + properties: properties, + inData: new Uint8Array(new Array()) } -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); +function initSession(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise { + return new Promise((resolve, reject) => { try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); + huks.initSession(keyAlias, huksOptions, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + throwObject.isThrow = true; + throw(error); } + }); } -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); +async function publicInitFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback doInit`); + let throwObject = {isThrow: false}; + try { + await initSession(keyAlias, huksOptions, throwObject) + .then ((data) => { + console.info(`callback: doInit success, data = ${JSON.stringify(data)}`); + handle = data.handle; + challenge = data.challenge; + }) + .catch((error) => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doInit failed, code: ${error.code}, msg: ${error.message}`); } + }); + } catch (error) { + console.error(`callback: doInit input arg invalid, code: ${error.code}, msg: ${error.message}`); + } +} + +function userIAMAuthFinger(huksChallenge:Uint8Array) { + // 获取认证对象 + let auth; + try { + auth = userIAM_userAuth.getAuthInstance(huksChallenge, authType, authTrustLevel); + console.log("get auth instance success"); + } catch (error) { + console.log("get auth instance failed" + error); + } + + // 订阅认证结果 + try { + auth.on("result", { + callback: (result: userIAM_userAuth.AuthResultInfo) => { + /* 认证成功获取认证令牌 */ + fingerAuthToken = result.token; + } }); + console.log("subscribe authentication event success"); + } catch (error) { + console.log("subscribe authentication event failed " + error); + } + + // 开始认证 + try { + auth.start(); + console.info("authV9 start auth success"); + } catch (error) { + console.info("authV9 start auth failed, error = " + error); + } +} + +async function testInitAndAuthFinger() { + /* 初始化密钥会话获取挑战值 */ + await publicInitFunc(srcKeyAlias, huksOptions); + /* 调用userIAM进行身份认证 */ + userIAMAuthFinger(challenge); } +``` -async function publicAttestKey(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback attestKeyItem`); - try { - await attestKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: attestKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: attestKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: attestKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } +3. 传入认证令牌进行数据操作 +```ts +/* + * 以下以SM4 128密钥的Callback操作使用为例 + */ +import huks from '@ohos.security.huks'; + +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let srcKeyAlias = 'sm4_key_fingerprint_access'; +let IV = '1234567890123456'; +let cipherInData = 'Hks_SM4_Cipher_Test_101010101010101010110_string'; +let handle; +let fingerAuthToken; +let updateResult = new Array(); +let finishOutData; + +/* 集成生成密钥参数集 & 加密参数集 */ +let propertiesEncrypt = new Array(); +propertiesEncrypt[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_SM4, +} +propertiesEncrypt[1] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_ENCRYPT, +} +propertiesEncrypt[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_SM4_KEY_SIZE_128, +} +propertiesEncrypt[3] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_NONE, +} +propertiesEncrypt[4] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_CBC, +} +propertiesEncrypt[5] = { + tag: huks.HuksTag.HUKS_TAG_IV, + value: StringToUint8Array(IV), +} +let encryptOptions = { + properties: propertiesEncrypt, + inData: new Uint8Array(new Array()) } -function attestKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise{ - return new Promise((resolve, reject) => { - try { - huks.attestKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +function StringToUint8Array(str) { + let arr = []; + for (let i = 0, j = str.length; i < j; ++i) { + arr.push(str.charCodeAt(i)); + } + return new Uint8Array(arr); } -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); +function updateSession(handle:number, huksOptions:huks.HuksOptions, token:Uint8Array, throwObject) : Promise { + return new Promise((resolve, reject) => { try { - await deleteKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); + huks.updateSession(handle, huksOptions, token, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } + throwObject.isThrow = true; + throw(error); + } + }); +} + +async function publicUpdateFunc(handle:number, token:Uint8Array, huksOptions:huks.HuksOptions) { + console.info(`enter callback doUpdate`); + let throwObject = {isThrow: false}; + try { + await updateSession(handle, huksOptions, token, throwObject) + .then ((data) => { + console.info(`callback: doUpdate success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doUpdate failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: doUpdate input arg invalid, code: ${error.code}, msg: ${error.message}`); + } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); +function finishSession(handle:number, huksOptions:huks.HuksOptions, token:Uint8Array, throwObject) : Promise { + return new Promise((resolve, reject) => { + try { + huks.finishSession(handle, huksOptions, token, function (error, data) { + if (error) { + reject(error); + } else { + resolve(data); } - }); + }); + } catch (error) { + throwObject.isThrow = true; + throw(error); + } + }); +} + +async function publicFinishFunc(handle:number, token:Uint8Array, huksOptions:huks.HuksOptions) { + console.info(`enter callback doFinish`); + let throwObject = {isThrow: false}; + try { + await finishSession(handle, huksOptions, token, throwObject) + .then ((data) => { + finishOutData = data.outData; + console.info(`callback: doFinish success, data = ${JSON.stringify(data)}`); + }) + .catch(error => { + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: doFinish failed, code: ${error.code}, msg: ${error.message}`); + } + }); + } catch (error) { + console.error(`callback: doFinish input arg invalid, code: ${error.code}, msg: ${error.message}`); + } } -let securityLevel = StringToUint8Array('sec_level'); -let challenge = StringToUint8Array('challenge_data'); -let versionInfo = StringToUint8Array('version_info'); -let udid = StringToUint8Array('udid'); -let serial = StringToUint8Array('serial'); -let deviceId = StringToUint8Array('device_id'); -let idAliasString = "id attest"; - -async function testAttestId() { - let aliasString = idAliasString; - let aliasUint8 = StringToUint8Array(aliasString); - - /* 集成生成密钥参数集 & 生成密钥 */ - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_RSA - }; - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, - value: huks.HuksKeyStorageType.HUKS_STORAGE_PERSISTENT - }; - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_2048 - }; - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY - }; - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - }; - properties[5] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_PSS - }; - properties[6] = { - tag: huks.HuksTag.HUKS_TAG_KEY_GENERATE_TYPE, - value: huks.HuksKeyGenerateType.HUKS_KEY_GENERATE_TYPE_DEFAULT - }; - properties[7] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_ECB - }; - let options = { - properties: properties - }; - await publicGenKeyFunc(aliasString, options); - - /* 集成证书参数集 */ - let attestProperties = new Array(); - attestProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_SEC_LEVEL_INFO, - value: securityLevel - }; - attestProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_CHALLENGE, - value: challenge - }; - attestProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_VERSION_INFO, - value: versionInfo - }; - attestProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_ALIAS, - value: aliasUint8 - }; - attestProperties[4] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_UDID, - value: udid - }; - attestProperties[5] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_SERIAL, - value: serial - }; - attestProperties[6] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_DEVICE, - value: deviceId - }; - let huksOptions = { - properties: attestProperties - }; - - await publicAttestKey(aliasString, huksOptions); - - await publicDeleteKeyFunc(aliasString, options); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testAttestId') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testAttestId(); - }) - } - .width('100%') - .height('100%') - } +async function testSm4Cipher() { + encryptOptions.inData = StringToUint8Array(cipherInData); + /* 传入认证令牌 */ + await publicUpdateFunc(handle, fingerAuthToken, encryptOptions); + encryptUpdateResult = updateResult; + + encryptOptions.inData = new Uint8Array(new Array()); + /* 传入认证令牌 */ + await publicFinishFunc(handle, fingerAuthToken, encryptOptions); + if (finishOutData === cipherInData) { + console.info('test finish encrypt err '); + } else { + console.info('test finish encrypt success'); + } } ``` -### AttestKey - -应用生成非对称密钥后,可以通过Key attestation获取证书链。应用还可以通过id attestation获取证书链,其中公证书带有设备id等信息。 - -ID Attestation和Key Attestation只有拥有TEE环境的设备才具备该功能。 - -开发步骤如下: +## 密钥证明 -1. 生成证书。 -2. 获取证书信息。 +HUKS为密钥提供合法性证明能力,主要应用于非对称密钥的公钥的证明。基于PKI证书链技术,HUKS可以为存储在HUKS中的非对称密钥对的公钥签发证书,证明其公钥的合法性。业务可以通过OpenHarmony提供的根CA证书,逐级验证HUKS签发的密钥证明证书,来确保证书中的公钥以及对应的私钥,确实来自合法的硬件设备,且存储管理在HUKS中。 -**支持的密钥类型:** +**开发流程** +1. 指定密钥别名和需要证明的密钥属性的标签传入HUKS。 +2. 调用HUKS为应用生成一个依次由根CA证书、设备CA证书、设备证书、密钥证书组成的X.509证书链。 +3. 将证书链传输至受信任的服务器,并在服务器上解析和验证证书链的有效性和单个证书是否吊销。 -RSA512, RSA768, RSA1024, RSA2048, RSA3072, RSA4096, ECC224, ECC256, ECC384, ECC521, X25519 +**接口说明** -> **说明** -> -> 存储的 keyAlias 密钥别名最大为64字节 +**表7** 密钥认证接口介绍 +| 接口名 | 描述 | +| -------------------------------------- | ----------------------------| +|attestKeyItem(keyAlias: string, options: HuksOptions, callback: AsyncCallback\) : void| 密钥认证| -在使用示例前,需要先了解几个预先定义的变量: +**开发步骤** -| 参数名 | 类型 | 必填 | 说明 | -| -------- | ----------- | ---- | ------------------------------------ | -| keyAlias | string | 是 | 密钥别名,存放待获取证书密钥的别名。 | -| options | HuksOptions | 是 | 用于获取证书时指定所需参数与数据。 | +```ts +/* + * 以下以attestKey Callback接口操作验证为例 + */ +import huks from '@ohos.security.huks'; -关于接口的具体信息,可在[API参考文档](../reference/apis/js-apis-huks.md)中查看。 +/* + * 确定密钥别名和封装密钥属性参数集 + */ +let keyAliasString = "key attest"; +let aliasString = keyAliasString; +let aliasUint8 = StringToUint8Array(keyAliasString); +let securityLevel = StringToUint8Array('sec_level'); +let challenge = StringToUint8Array('challenge_data'); +let versionInfo = StringToUint8Array('version_info'); +let attestCertChain; -**示例:** +let genKeyProperties = new Array(); +genKeyProperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ALGORITHM, + value: huks.HuksKeyAlg.HUKS_ALG_RSA +}; +genKeyProperties[1] = { + tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, + value: huks.HuksKeyStorageType.HUKS_STORAGE_PERSISTENT +}; +genKeyProperties[2] = { + tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, + value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_2048 +}; +genKeyProperties[3] = { + tag: huks.HuksTag.HUKS_TAG_PURPOSE, + value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY +}; +genKeyProperties[4] = { + tag: huks.HuksTag.HUKS_TAG_DIGEST, + value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 +}; +genKeyProperties[5] = { + tag: huks.HuksTag.HUKS_TAG_PADDING, + value: huks.HuksKeyPadding.HUKS_PADDING_PSS +}; +genKeyProperties[6] = { + tag: huks.HuksTag.HUKS_TAG_KEY_GENERATE_TYPE, + value: huks.HuksKeyGenerateType.HUKS_KEY_GENERATE_TYPE_DEFAULT +}; +genKeyProperties[7] = { + tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, + value: huks.HuksCipherMode.HUKS_MODE_ECB +}; +let genOptions = { + properties: genKeyProperties +}; -```ts -/* 证书AttestKey操作示例如下*/ -import huks from '@ohos.security.huks'; +let attestKeyproperties = new Array(); +attestKeyproperties[0] = { + tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_SEC_LEVEL_INFO, + value: securityLevel +}; +attestKeyproperties[1] = { + tag: huks.HuksTag.HUKS_TAG_ATTESTATION_CHALLENGE, + value: challenge +}; +attestKeyproperties[2] = { + tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_VERSION_INFO, + value: versionInfo +}; +attestKeyproperties[3] = { + tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_ALIAS, + value: aliasUint8 +}; +let huksOptions = { + properties: attestKeyproperties +}; function StringToUint8Array(str) { let arr = []; @@ -3477,22 +2583,7 @@ function StringToUint8Array(str) { return new Uint8Array(arr); } -async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback generateKeyItem`); - try { - await generateKeyItem(keyAlias, huksOptions) - .then((data) => { - console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); - }) - .catch(error => { - console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); - }); - } catch (error) { - console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); - } -} - -function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { +function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) { return new Promise((resolve, reject) => { try { huks.generateKeyItem(keyAlias, huksOptions, function (error, data) { @@ -3503,27 +2594,33 @@ function generateKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicAttestKey(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback attestKeyItem`); +async function publicGenKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback generateKeyItem`); + let throwObject = {isThrow: false}; try { - await attestKeyItem(keyAlias, huksOptions) - .then ((data) => { - console.info(`callback: attestKeyItem success, data = ${JSON.stringify(data)}`); + await generateKeyItem(keyAlias, huksOptions, throwObject) + .then((data) => { + console.info(`callback: generateKeyItem success, data = ${JSON.stringify(data)}`); }) .catch(error => { - console.error(`callback: attestKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: generateKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: attestKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: generateKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function attestKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise{ +function attestKeyItem(keyAlias:string, huksOptions:huks.HuksOptions, throwObject) : Promise{ return new Promise((resolve, reject) => { try { huks.attestKeyItem(keyAlias, huksOptions, function (error, data) { @@ -3534,137 +2631,49 @@ function attestKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) : Promise< } }); } catch (error) { + throwObject.isThrow = true; throw(error); } }); } -async function publicDeleteKeyFunc(keyAlias:string, huksOptions:huks.HuksOptions) { - console.info(`enter callback deleteKeyItem`); +async function publicAttestKey(keyAlias:string, huksOptions:huks.HuksOptions) { + console.info(`enter callback attestKeyItem`); + let throwObject = {isThrow: false}; try { - await deleteKeyItem(keyAlias, huksOptions) + await attestKeyItem(keyAlias, huksOptions, throwObject) .then ((data) => { - console.info(`callback: deleteKeyItem key success, data = ${JSON.stringify(data)}`); + console.info(`callback: attestKeyItem success, data = ${JSON.stringify(data)}`); + if (data !== null && data.certChains !== null) { + attestCertChain = data.certChains; + } }) .catch(error => { - console.error(`callback: deleteKeyItem failed, code: ${error.code}, msg: ${error.message}`); + if (throwObject.isThrow) { + throw(error); + } else { + console.error(`callback: attestKeyItem failed, code: ${error.code}, msg: ${error.message}`); + } }); } catch (error) { - console.error(`callback: deleteKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); + console.error(`callback: attestKeyItem input arg invalid, code: ${error.code}, msg: ${error.message}`); } } -function deleteKeyItem(keyAlias:string, huksOptions:huks.HuksOptions) { - return new Promise((resolve, reject) => { - try { - huks.deleteKeyItem(keyAlias, huksOptions, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } catch (error) { - throw(error); - } - }); +async function AttestKeyTest() { + await publicGenKeyFunc(aliasString, genOptions); + + await publicAttestKey(aliasString, huksOptions); + console.info('attest certChain data: ' + attestCertChain) } +``` -let securityLevel = StringToUint8Array('sec_level'); -let challenge = StringToUint8Array('challenge_data'); -let versionInfo = StringToUint8Array('version_info'); -let keyAliasString = "key attest"; +> 常见问题 -async function testAttestKey() { - let aliasString = keyAliasString; - let aliasUint8 = StringToUint8Array(aliasString); - - let properties = new Array(); - properties[0] = { - tag: huks.HuksTag.HUKS_TAG_ALGORITHM, - value: huks.HuksKeyAlg.HUKS_ALG_RSA - }; - properties[1] = { - tag: huks.HuksTag.HUKS_TAG_KEY_STORAGE_FLAG, - value: huks.HuksKeyStorageType.HUKS_STORAGE_PERSISTENT - }; - properties[2] = { - tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, - value: huks.HuksKeySize.HUKS_RSA_KEY_SIZE_2048 - }; - properties[3] = { - tag: huks.HuksTag.HUKS_TAG_PURPOSE, - value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY - }; - properties[4] = { - tag: huks.HuksTag.HUKS_TAG_DIGEST, - value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 - }; - properties[5] = { - tag: huks.HuksTag.HUKS_TAG_PADDING, - value: huks.HuksKeyPadding.HUKS_PADDING_PSS - }; - properties[6] = { - tag: huks.HuksTag.HUKS_TAG_KEY_GENERATE_TYPE, - value: huks.HuksKeyGenerateType.HUKS_KEY_GENERATE_TYPE_DEFAULT - }; - properties[7] = { - tag: huks.HuksTag.HUKS_TAG_BLOCK_MODE, - value: huks.HuksCipherMode.HUKS_MODE_ECB - }; - let options = { - properties: properties - }; - await publicGenKeyFunc(aliasString, options); - - /* 集成证书参数集 */ - let attestProperties = new Array(); - attestProperties[0] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_SEC_LEVEL_INFO, - value: securityLevel - }; - attestProperties[1] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_CHALLENGE, - value: challenge - }; - attestProperties[2] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_VERSION_INFO, - value: versionInfo - }; - attestProperties[3] = { - tag: huks.HuksTag.HUKS_TAG_ATTESTATION_ID_ALIAS, - value: aliasUint8 - }; - let huksOptions = { - properties: attestProperties - }; +1. Cannot find name 'huks'. - await publicAttestKey(aliasString, huksOptions); + 不能找到huks,使用了接口函数但没导入security.huks.d.ts,添加import huks from '@ohos.security.huks';即可。 - await publicDeleteKeyFunc(aliasString, options); -} - -@Entry -@Component -struct Index { - build() { - Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { - Button() { - Text('testAttestKey') - .fontSize(30) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .onClick(()=>{ - testAttestKey(); - }) - } - .width('100%') - .height('100%') - } -} -``` +2. Property 'finishSession' does not exist on type 'typeof huks'. Did you mean 'finish'? + 不能在huks库中找到finishSession,finishSession是API9版本的,请更新SDK版本或替换新版本的security.huks.d.ts文件。 \ No newline at end of file diff --git a/zh-cn/application-dev/security/huks-overview.md b/zh-cn/application-dev/security/huks-overview.md index 0dbdb097ed..a9e2b8411c 100755 --- a/zh-cn/application-dev/security/huks-overview.md +++ b/zh-cn/application-dev/security/huks-overview.md @@ -1,28 +1,75 @@ -# HUKS开发概述 +# 通用密钥库开发概述 -## 功能简介 +## 简介 + +OpenHarmony通用密钥库系统(英文全称:Open**H**armony **U**niversal **K**ey**S**tore,以下简称HUKS)是OpenHarmony提供的系统级的密钥管理系统服务,提供密钥的全生命周期管理能力,包括密钥生成、密钥存储、密钥使用、密钥销毁等功能,以及对存储在HUKS中的密钥提供合法性证明。 +HUKS基于系统安全能力,为业务提供密钥全生命周期的安全管理,业务无需自己实现,利用HUKS的系统能力,就能确保业务密钥的安全。 + + +以下目录文档主要介绍了HUKS在典型场景下的用法、原理以及支持的密码算法规格。 + +- [生成新密钥](huks-guidelines.md#生成新密钥) +- [导入外部密钥](huks-guidelines.md#导入外部密钥) +- [常见密钥操作](huks-guidelines.md#常见密钥操作) +- [密钥访问控制](huks-guidelines.md#密钥访问控制) +- [密钥证明](huks-guidelines.md#密钥证明) +- [支持的算法类型及参数组合](huks-appendix.md#支持的算法类型及参数组合) +- [密钥材料格式](huks-appendix.md#密钥材料格式) -HUKS(OpenHarmony Universal KeyStore)向应用提供密钥库能力,包括密钥管理及密钥的密码学操作等功能。HUKS所管理的密钥可以由应用导入或者由应用调用HUKS接口生成。 ## 基本概念 -- HUKS提供密钥管理功能,支持的主要操作包括:加密解密、签名验签、派生协商密钥、计算HMAC(Hash-based Message Authentication Code)。 -- HUKS涉及的算法主要有:AES加密解密、RSA加密解密、RSA签名验签、ECC签名验签、DSA签名验签、ED25519签名验签、PBKDF2派生、DH协商、ECDH协商、X25519协商等。 -- HUKS当前使用的算法库为OpenSSL和mbed TLS。 +在使用HUKS开发之前,建议了解以下基本概念: + +- **HUKS Core** + + HUKS核心组件,承载HUKS的核心功能,包括密钥的密码学运算、明文密钥的加解密、密钥访问控制等。一般运行在设备的安全环境中(如TEE、安全芯片等,不同的厂商有所不同),保证密钥明文不出HUKS Core。 + + +- **密钥会话** + + 应用通过指定密钥别名,给当前操作的密钥建立一个会话,HUKS为每个会话生成一个全局唯一的句柄值来索引该会话。它的作用是缓存密钥使用期间的信息,包括操作数据、密钥信息、访问控制属性等。密钥操作一般需要经过**建立会话、传入数据和参数、结束会话(中止会话)** 三个阶段。 + -## 运作机制 +## 实现原理 -HUKS对密钥的使用主要通过InitSession、UpdateSession、FinishSession操作来实现。 -- **InitSession操作**:读取密钥,并为其创建Session Id返回给调用者。 +HUKS的核心安全设计包括以下几点: +- **密钥不出安全环境** -- **UpdateSession操作**:根据InitSession操作获取的Session Id对数据进行分段updateSession处理。 + HUKS的核心特点是密钥全生命周期明文不出HUKS Core,在有硬件条件的设备上,如有TEE(Trusted Execution Environment)或安全芯片的设备,HUKS Core运行在硬件安全环境中。能确保即使REE(Rich Execution Environment)环境被攻破,密钥明文也不会泄露。 +- **系统级安全加密存储** -- **FinishSession操作**:当所有待处理的数据均传入HUKS后,调用FinishSession操作完成最终数据处理,释放资源。 + 基于设备根密钥加密业务密钥,在有条件的设备上,叠加用户口令加密保护密钥。 +- **严格的访问控制** -> **须知:**
-> 当InitSession、UpdateSession、FinishSession操作中的任一阶段发生错误时,都需要调用AbortSession操作来终止密钥的使用。 + 只有合法的业务才有权访问密钥,同时支持用户身份认证访问控制以支持业务的高安敏感场景下安全访问密钥的诉求。 +- **密钥的合法性证明** + 可为业务提供硬件厂商级别的密钥的合法性证明,证明密钥没有被篡改,并确实存在于有硬件保护的HUKS Core中,以及拥有正确的密钥属性。 + + +此外,密钥会话是HUKS中承载密钥使用的基础,它的主要作用是初始化密钥信息、缓存业务数据等。对数据的密码学运算和对密钥密文的加解密都是在HUKS Core中进行,以此保证密钥明文和运算过程的安全。 + +**图1** HUKS运行机制 + +![huks_architect](figures/huks_architect.png) ## 约束与限制 -不涉及。 + + - **基于别名的访问** + + 由于密钥材料不出HUKS的限制,应用只能以别名的方式访问密钥材料,而且密钥的别名必须保证应用内唯一(否则已存在的同别名密钥会被覆盖),长度不能超过64个字节。 + + - **数据分片大小限制** + + 所有数据需要经过IPC通道传输到HUKS,受IPC缓冲区大小限制,建议对总大小超过100K的数据进行分片传输,且分片大小不超过100K。 + + - **指定必选参数** + + 在**生成密钥或导入密钥时**,必须指定密码算法、密钥大小和使用目的参数,其他参数可选(如工作模式、填充模式、散列算法等),但是在**使用密钥**时必须传入密码算法相关的完整的参数。 + + - **密钥材料格式** + + 导入/导出密钥时(包括密钥对、公钥、私钥),密钥材料的数据格式必须满足HUKS要求的格式,具体各个密码算法密钥材料见[密钥材料格式](huks-appendix.md#密钥材料格式)。 + -- GitLab