From 362d25b44e8e504fb3281e962a2761d0d899add0 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 19 Apr 2021 18:10:57 +0800 Subject: [PATCH 01/59] chore(ci): Modify CI image URL --- .gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99d6d5c9d..83b515304 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,9 @@ variables: IDF_PATH: "$CI_PROJECT_DIR" + # Versioned esp-idf-doc env image to use for all document building jobs + ESP_IDF_DOC_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env:v8" + # before each job, we need to check if this job is filtered by bot stage/job filter .apply_bot_filter: &apply_bot_filter python $APPLY_BOT_FILTER_SCRIPT || exit 0 @@ -79,7 +82,7 @@ push_master_to_github: build_docs: stage: build - image: $CI_DOCKER_REGISTRY/esp32-ci-env$BOT_DOCKER_IMAGE_TAG + image: $ESP_IDF_DOC_ENV_IMAGE tags: - build_docs artifacts: From c2be3ba0b50675e5161f5b8cc0a15728ae73d8a3 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Tue, 30 Mar 2021 03:35:29 +0000 Subject: [PATCH 02/59] Merge branch 'bugfix/check_and_reset_mac_reg_every_2s_when_rx_hang' into 'master' fix(lib): check and reset mac reg every 2s when rx hang occurs See merge request sdk/ESP8266_RTOS_SDK!1595 (cherry picked from commit 30a4ba7e2d05eafe2a04841714d2d702f98ae60a) fb5894a9 fix(lib): check and reset mac reg every 2s when rx hang occurs --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libpp.a | Bin 245990 -> 247158 bytes components/esp8266/lib/libpp_dbg.a | Bin 263422 -> 264598 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 49d4d2e10..b2c4332be 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: cebfb0e net80211: 8114f9c - pp: 52bb147 + pp: 5fda74f espnow: 7a93d71 smartconfig: 3.0.0/5f550c40 diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index 761b8be80a2336113730e58f643b2eb9fb9f3e83..aa59f60fd5f95b5d2039d38664bd642a2c66c5c7 100755 GIT binary patch delta 22466 zcmcJX4RjR6n*O_bk`S257sLb%keNwHCJ-QBVt@c4$*I|1|plr;h(ee#8F9J^imc(9-63EzX|P zEsdRKCE5w2u~SlHLI1p*LZh!QQ!jhErOHz*TK`#Z-&(taA${;@YQx|Kx*HBFl~OCLU5n|8Opd%c!1T94YIMdfSLZc6b4 zOtIC3huFgH{@!l!I$^kuGTkG^W+q(v?j{ZCoAlkiwD||z^Wvu^$9mKCqy?JS+kIN4 zKKvQY)ls}i-&00Y|8D-LtzsRc{-%iy#HdHK);^1qP{(zy12a=ZBgNeY(qkfaM_6^I zcpf&z8tS2~OBat?wNx%%sAUY+hcD5F|7xNHFamu^^eiyN3d`TxqT0oNEu)Vfb&r;b z-aO2#2?x3Zms<7Y?YyIS+|8_eA4#uEcN`)jnITPusI zsWFR7f)%B~oYLTkSM}l7Xf;=vH(zU7nSbP5s_z~jDp2eVR(OLsc|p$@(-k46SlzO= zuE~r4!u{)0fCeX7eL9P{yf)0uj@W025XCVVsEt! z4EjLrg8}A02AV_rvj>GSb#dP8&ZWPAcn{T2IbMk}L`N2y! z>x+AbWNK-_+}>ujHwf?DnVP4EDF(4KsK_dbnudB!e~vd;7>7+>x>nMtaeq=wQ9#Gz zGgG22Mt_da>^g0XzBpdXd;eEgl1tCYScqxeI@viGl$0ktai++!cHhVC@FTNxC^Lxx@F?eEKHpXHzx?MgNwoiyn#g|8n#9 z5&zQLzm$_6e0$xe%S#%and$oCU7EL3#Ey@#PYK?c7X`E3c{Q5&lGS>%Qwl}K~hxGmXBbI(HKBXAiF5aklc9{pt7nFHDZ zd)*%y6S`d}{%GNZH_m(0G2vgCoPE)|X-8Hqeo`yZMarTpefsW3ty0%|l~%rWzT&{b zQC|0T!LNH9OZq&+^RhK!Q=*Aj2n(#(}ZD!Uf1h>56hA1sLx z?QV!%(5NjaU-Z0p{z-S*(E9uR@%QC*E7^5nR9SKH+{}WG_1<95-d;Vjq9Xmu$_SV4 zEyrY9)Vkj5dT2tWhPmV|U*y}kVBKwH9X+ee%U7E{OL3IlrNQD6L!IfFg^8XO=BN^H zgqGsy|269d|ZEd-MnWOV-WnxqAIb&AU^K zSn(q??2pe;*lBc>EAobfJ49$b=1w$g6UsIY=vOB)TcQdR2lT^k6ehaUg5H^(k7sxO zHp4qJXrg!%slW<4w)!3_fa$(>=nh+X|*8C}8l&g+DP2jQ+`?WJj;BLioib5Cd#!}f`1U5!? z!Lp)WU=*?RY;dB|i^2AxxeXkn^aWu)tHAcCzW^?@C*xO0Ft9PQ-HG48%%aYNZSQdo zn1MRk4eLd$0Nb6r9NbRnRbYFhuLavXd~0r4fw^FxD)1PXfeny*!Lorp4&&Z`Tr9W! zCR{DoUArVPatXxsE#KW7C&ZAJ^S8|I?q3Eakvo9Ugh=LWcttU*TIl?VixG4n2lYkm}TcG=JcAUxGVTh#hl}7 z6uZHkPOLu>?Q6{{kq-VyF&jInm<^=jsIowBXuTA(u|A5~7-t84*jS}vH+Z&U)_<2` z*1rtww+lQOma$PW_wsecoD@9u(4U3B4AUc#N1gti74sDI6#S@j{=5*zFaN=Q?Jbqy z#M%?a?YUxZ6tfdnz0i4)^`WC;6E+B!`jr#KOTkW+ zk&U!iLKjUNqYGkL<*&sW_)l`5yyUuY+_furTu9SPt0@Gk; zDCQOY<}jWY#&;^_UKX+eo;VQf9iv8eD*dm@pLYWf37k>c7!#|)GS-H15h@T5u}Yk} zlunK8RC+PeVm&qptc!IPD|RZK$DBHED;UYG;z~^`g`?oJ2f|L3L5=KGI&V2xfVUWo zWZqVk!OuRHpDCT%XU5u9$^JH_ltUrgV_d;f>HwWoKGewefn)7#g=eLo6myJux}?sV z6h<=dOUAQKHF-LObiXAsVb7{6I;q(cuL@Hm+nr-&tket3D=C=`Ph?qj5Yv@Tjcgy> zo?-sgyjt3O*;i#$qX607%OOht3v7vE-Zf1PYixwlsgdo*Mk}2Me1hVMuvghSr`=?g zsD-`W&VYWCVxG8f4$~VH^V)AH=H5SJ`@lXMrteU^2X2ngNK0kjC1I(4#8QaQzP4>a!%>!G+uP7lXN@S2*uo+7{#4nYn)P!Z+rkIzZx-fk%_e{;+dz4O%Y){@g z`lyk6TKP~T+anrKI&YZY@v8(cTOTOqUGn^}#y(LxHL~4UJ$=+l`CR!>Bik$G2c`2; z_mg7YRWD*$EbmdU+OH~uBH5n#bhMY2OEIspabbMX@7!|m(MLP^n@~rhkF{!{%O)5u zp-UooGwN?cnm*C6KPjgEi((h7;5h_7@!$-_^ckf%5&D&iyMiZ$*l*ogyrnYS$k?kG zb!mqcr-QAN6hyLhJ^_0$F@2nz;DNBi6lcTdTE#iwYQ+WM)rt$jPw1FWEL?=dew9%S zKCbv1*z=0VVQ+dP(!UIr6O>#9J4Z41_>nMPi@NB;yO#Be=~D`}<|f+LIREHGk!;VG zol4&iTZU*)=DkX%Mz;MwRC*h1Iig?%2p=J`erjZ=(ocdt%;0gXK(q^d3#Lx(Tf^U+ z>_4NF@vvm)BiJCss{_Pplfu}#$3YF$)0NI=G3yQ;I&aRc`xme^9?FN$ZX*;^zcIvq z>k52_%2)u)$SyX)E>+BvwhZH^!nj%SHrQ9f_>kf=u+}9JbzT5lH}2qVI&S3bS;9+K zG9nx1gJM6$#ju=y)cHU-M=>7)t-E#T3&6{j&I^Hc=MH@X_!XtU0Lx3KScwh7J4&zO zEMdZ_G7hT@J}CZEF&_>cYU)+NwpYxDK$l`ZD8?%0L*PWzE8b*NxHc=D8riAzeqcZM zj0LKZX%`rRG<9lZy8zdT?O&^y50+C|b~82zH!7VP*{Sq-O6LRTe8q-C8?4pBQEXsQ z^hRY+BRiEo6KT;x4^6vI`B1yh7~7KUe@H2FppczS^uuEoqF8UChNi5pQzP5=d3E&J z8S-J*xO>#d_G((MbWYp(^jE#w5Yl^EA`^CZ>*=Jr`@AYl?Rg_^Q*sR7;jo!SP+o4y z-&C6{e8yw%E)-dYyLFQ2Y1Fq46upeCt$jtdaSGesM%VX7`1`;e;RN31h};mLA?Jno z0y#g#(Vf5rAxbu$b&-MaUv6gLy21Q5aaT~K7j|E zB3f_hzP~wAG#dLp8Y`NN)Q`uCR%14{CyedbUNGW587u0IN!T7VHhwZzFBre#&=@ht z7<*`}SYoWjw$V6^?Pg=hr(?yB#d_O7woh-@Bd#>6J{tq=na{?Gna0o9E;2?O z9xFB(65A8TH`rb84OsL>ug}x;u8Bs)=NWqE zTaCKUGsG!l?dO?#*V~M}(BhMfGtkZ%$zNom@*!Vj>aIJC+AlK1Nn;rjg?Ad8k?IHWhRYx<>6F(B2=NnB(lpFhyzrpw!+m%LYTPAjV1hx&v?6yq3 zXo0c1Ekln;F@i927aAwqSU&C;n*6I#cq~JlF(w_$#30^xEHkpAD;{!=T$1}uIBlCEsIAV-Fo~h?NY}7$oZLEc~)!2jWCgU`=ZAS8!nc|Q!3zSaW51$* z>R0sN{#CMZot2*bd-_GB%aM~QDvWQeZQR$HdWBcUZ+0XZW4?}uJ}IQQC+bPY6Ht~K z8^6vJyNpk;-E5d&XGUhHV;LL0zDbKbxPmn9n>0+7dEYR9Ew)X@o^N`Ff6Q%(J-M$> zxDZj0*RRm%>Q8#N170mP5QroV@cl ze7nkh*(~vD{D?Kt9ag>e!A3Lp1v6Og{{542=)*C_v@eYAkm>Kem zXh*udE<$$&`VA0A^xw1Z*7=6h_-(W+P%=nt?ku_l&R-!+A-)Qnzfz>d`1#@^kuO#e zF!eMs-9l|P40UQ`r_ym6*F-Hg2zMgVhZ@DD6w^ouZDrO31Wt+!;*sgZ5}GWyKI27zT*CpEHD>E%eH zOS~b{v;oSABH5|>TCDU5uocL&$F)r9)X4U@jtTQ0Pk;QFiNSi&g-%c-JC$Cg$)f3^ zL+*8|Tov-kP9Agw-a?5v7HU(K54EX*dDF!+QGV;uBvI4c(1M7G;7zdBAl(N*@Ua|p zYGkL@^3gq>fpv3c*xa)X4U%kHpE6W6#e3^O0x!cTzevvh80_AGI2+PX{PV zjcl)yMf91X#lqmItBMxMqjN;E|7s*Tf9zo_SJ~9a_AufH4pzVDzm)!J@{d- zHuhhIiL%@AfAGa#Ihy;?-3aLaKtbLCw$OXZU^=j6LIN94cL%#kUZ zU`pjMnld>9CUQwTO}kslO(HF-=?XkjN6O$P;nkx%th!21lD}>e@nW-dzJdZ9WIoL% zc@51exe!Lro+zJxMPy*GKBQVL&(my>X|EzvtC8ieB6F*}mC5uP`52P=xfr>Biz7*X z_9`l`mfs-{E}a4}2jxJRs6+9Z_HcVSf0ynRd*y?WB5U!Bpu6R}yYw_YzD^zqpxTqt zu^DCd%U(3&SX@waJ)0r{Ek26 zhGhBZ>nJ}z)u{eDIpI$ogc|%RNq!3tOr7(rOGyK^ppd&>mT$pc*2`PBAgkW;PAH7} zk9%1!-wfr->T!Bf$Lo;IJH8PT7hEvg_s2 zt!VFrY(Y}2l!sxWTzIZq7G=!;#w#9^z1~2^V{+UZ;y>K%1|EGw+?pPKw;P$CmzPr@ z*S;Zq9eF0fkQG?X`Lr9g=`uG(bd`TSF7A!0taA6L>kx2!DRzp$?h~Rcp&}^G4xw*^ zc6LZ#SkpB9b6g>fdLd#k=IoGNi1F>T>aFk(B7Y<^@fL4x$Tx^`+Y$MAm{Z+3l&R%@ zVS2tW@tew^TdQ?c>FsZRyXMo&|2{NlnET3AlXo04p;u;WA738{D0Fx}GKF;mj#OT@ z<~d-x)8fJ2nOf+#V4L%*>MZ6O!NdY&~iJ0;;qa;O87!@5=WHfjR*;ArC! z_S{{z%5*v4oi`c3fr=K^?=6q5GCh(t7sk5N+^|`(+1}7kEj8-fAT-0<0Y0I*x*laV2AFsI&`)5 zDFs(1tSkQX>a2M&79|_l3xVmh!09A?nVv9j>h-rzs+~7!R?U=2w_Jbg^>d9$x4Grx zUG#h5DAMq3swa0TLqUWz#6;B3I?2v5S%K_9YAxd2VP&%!vQz0Uuo#66LJ=aXphk9% zk{d48)4E&+Mz|W0+1w+t^D^0Xu|A_qIT#@uF%dh$Y_hXlPEFDKcEKf_AqP=-Ewr+Q zXVn0`d*GcEeYvo!Sb=O_=f}g6QN`SD`nWD*LdBBNIJ2veBs<5*wr+Y~F;>QR*OOs< z-Su}xno(gUV{lxldYY(|IjP91lx0Zu7#FH%GEAa6d6S(=XD$jhu*7KDl&TM$#hxJC zf|!V*2m}#S4@VHL~r)^PTO(XF1kGjcogLwR~iSTTk|@>fDxu4Z!C(=DxHUmU$PnG*KR!_MkOd%d<_Z(P0*=|hd|RC)nes5<|qe5jF~N2M%WFw~8)PEHCSkAQ8XCWFPn_;G80>eVG3fL)qWJFq?;q?3^fDefmJV zi~ZOLve^eRy6Dc*(?(u}G=lXJ4%zGj*{Sq7su+J*QHVUD2E=+SgFiLvv8-G!&C>gh zoQgDp^mKvcSRCXD%$xfwT zP&%KS*fF7+>3}q=qeixyub@v2HV8?|hZ@;gBO81EF<#cg4XUU@w)JpRZqJg=z4UpQ z9u2*$S<=)Cvwt+tb=~E~+4}wVc=H_3cj@u4WQ_OLY`B)o_}+SQ4>gQj>h=@kd#VIA zGLCO~Z%l)Uat=zsEa`16v?IOs5q2wiERz^IDU;DkQ6Fobt0~>kM_*$*b3G?sga}K9 zb7^0Fgxwcw0wJ62TN6n2(K`K1kS27I%4RcU`&rx##%Kj|tzpg?pbtd7iFv5E7C8vK zbR;H+>LojC<*B?s>YcB$Sugod^%m#r$u(*Zk^B3DG%w?c90CNg{U99ywl9UurVll; zQ(Yo0A6{NnK!-Qy>kE6RwZyf>kMhy5WP6Rdz~U-dU4T8kO4b$VX;-MHXRdsf;3Oj3 zPtP|fy_6PFs1|NlIyJK00y~MS&!B}a%9STiR^158AriBt`x0cUmYY=eVps<3O2s95 zPf;k(_t(4n-Ii$WyihxA?FH5mgEbJTAzlKLm=fX#6*K>7#T<=*V%D)+G57fk#oV7r zu65KC59{ZK3GP@Y#oUWh#cuFu#p&P*#o6F0#XfMgVxTrlaSr%4#XL-Y#f4xa#D43< z)~qtvyAKqzfv*%7gHI~vk|JS*g4i;jduO^Wyt!405T|&?Y*Ch zwC!WP9fS`x>+PT#p<4P>VuP?mH9(DQkI*Wm^H9%3p176X82>dYgCf}($_V|4YOdv^ zty4a8kR&^mPNOOc{ni+rqsHGI+x67Z=QeB*j;ea7k)5}3{1JFs6W3U%{i>X(k?or& z>u2d`fR9qc)#M`Av#cXjc9hbok)6;xgBKy%$DU<)2&h$bqm#O`i=dMS8x@3Ct}e7L zRHq8{5wqB7ggnH=ZitGVN@p`d@g(I#jcogHVcR|nln*ts?c)ZEI@#DC=g>O2xxd~b z?g-2rptlo)RPAfwk=Q+S$3nJC7Fi{+L9iARe5jF~b+UP&o;*@ju@@4nD2655Rg{A5 z4t@xx4>hvwQ)c`wBv-UutF?GgD>brRtM$tn=8P$Szu{)u5uycc9e)X28e45eQITOH=l*8%jWMz;Oyl+Irm zPYv_GN9oka@W&GX0#_miq;4nnpl02Fsv+kx zvwec9I%;Gb>@#82?N`|>Nw)W7epnS>C?9HM+o#_0krjh+r>9z8M2Yt|2;Zw>)X2_x zvLaF>%kNf+^uV6MdSc8-tTcunF&IKIpS*2$k-LU`I)`z380T0l>o$lHaz?q{FbLzp zkQjrqirrzHO->E@6q4N`E+wb&#~a44USHt4(RzGj`-uMd?uz_cIj<7;s6WazG%Mxy zN^lMK9H^oGPNI59 z`lbpQ$2xO<_lI8 zYpL#%+o`tUj&rZo;nP%4$haz~P388u52=EhBPUU<7_DjNEZ=2RSIUi4%f{jwV5vu` zW=oUmUYuY;YTv7%?v-P&qA#wJq22IPJtNmrZGgJLs$m~h3^3IKoN{tP1AMif8r9Yb z*EU_2UafoEPeqp7+QHe#(#I#shO03ei*N!8mDo;sW|C~9ya_U2DAmfiYasiQWzIE_ z_u~GQZ^CNjB+A)ctatOPuF>Pcvi=&pTYv6P@9*Km?+)AlQ+dFl|4&9Q(XJiPmKDB( zA7yrx0j=?kea9c6w+l(uwzdmj-gk_`E2OYmw8k!S4&40$I;$*JMH;SClNaeJh|I&n z)jIvD{E~}t_Qy)JgI#y*pXxg`DdflV=ksvw0kG=7V z7TxC)MVay3Hz|<^FTsy9<&ASR-P`w0I2-GYO(^T0^^$i-d{%;Ii#c*fmT)Ea zjvF2GZ^wl7L!ITXn!cfhUEXWo-`jMox1;|D$gqA#Q}}_eYhhZ`u}tG+;f|UY&C1w> zoSqBHmKVNsLqg4SX3zATVGZbwyVT!!gK)KT4R5<50lf`MbC@XeSO?%3wnO;S;DS-s(bFE9f=&_>yDhl zs!)MGtPQGsW+h|1a3W|_t3@1@{up}hs7@e-%))^d>6l1 zcs__<@0zBzI$_Tp9dqN(d*aWRJiREe;k2F?<->=NHjY*90n@Xe`_Nzad|>A8Q)8g4 z=Y4tMpL#~uw|@VUGTJrBg|hFk>}ZgyQ=s@KJ*r)d4BgmZFYpH+^0I&FmtyH^^_mNp z)_(Hpf9kRR<^wee9~?0+|I8d6;~M6DYQxT&j(Sa}<2CVTYvRO7R{5Tubf63V`;g=7 zj^K0Chvv7ZLjU`LIBs=zzv^t6jd*u6c- za-Hex79W{@qz|mm$=zk{KT==PxC%e&vVOdlyTkNuSmMrxHX`fZ(2v(VFPSx4&G^gR z@wxowRHl1(o-8hXWu)V>{OK#c_q=Ml(kwZrfZ3t%PWt>0?e9&w8%=NH8G2w2(F48o zT>d1x87y*NwOqWy&UqTq(?PMJZ(&#i?CB7@}|Ji zNQcx#LttLCBU!KH%O>oJb{Y{QV7(bT13MX!dL_h6L@)Rb3JB4N$%!*<-LQcmm9Oe{vNvai?POj8G*{bayODj%4A(fy{o6 zPEP+Ca3W497?RN6l7iWrBTDCG`>5i0*i(x61@TwKiLln2ER^jG&Os+wXIJoF6uZIJ z^A+@TFh2~!$8WtjdO|s6Bcn-i4mhZ|0DM4kA^5oBBJhuji@}NL6dNuD`xKXfa}<|@ zOBGju%dJ0sK!Nc{Oi~$cu=NWc=(t_eW-EOn_;$tfVR^jRST%GWFY;6{zqOKC=id~w zqfa1GKLcBfD3)R4N94L<21T-y83>;t+688UMFUm*7Y2ybsga#Z_o$KJD9nK_mScl3 zMCsJXPNmO9TJ{{~aQB#|JUBJ$pxPx@DxKO&IeVBRxlgp(qxq1?c4fCHoujf)@e+Mi zu_Gh!{xC;T&yj~vfGg!zqS9?VnG4!CqL`5F!!HtLCDtx)X28aRIu$6 zt450&+4iaZ!^a=`J9tz?(HrJ8^A9KMTP7Q$)-TM5S6b>kgX$yWW}SXHCe8v=`7nGb<7Z5WceD$Kyk5LvIZ_Ga`PI;Gx!}^ z^m}8Pim4%NZ<(Bo2BhMq7X}q$HCQZga0h2C^X?n;xG#Rq-2^7eb zCz*$dgPq%eWRKwY9Ra1<58k z2}$hAeN-bdWiwR0qEQ}V?hcPsz>9OqWNgHpq6b!fcX#X71PPs?+! zqF|{^4Ir8Qv@8oCSu1BVS^BhG%_I&alM|noCz?+)ou89cNJbvPz5fika0}*E&U3h9l+OY6vI73B zIYW1El;>z&mq_5vDz?v__@j1*QB#X=kV% z+$KA3M^7u{knQMcMX0KKwmahS>*{5c8V2%3qkNyzUU`&K+cp^;#Gq8;95Geq2Qet6 XJBZf+F>?N(HJ;DV)X9B8NAmvz0y(%j delta 22034 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 delta 23783 zcmcJ13v?7!x^$7A&CkY92CYuJD>ui zI0OS692t#@f+GqF8dPQoh`@{}pp4*LW&}k=1|{MP6jOKas;``0^UuBO{{O%3S*z;X zXP-BL7|D|99Hi znTGl|o7tt!kS*eZ|6RN4F2nq{Tjak=_ zN$!~5)Rgv92REYY{hkbOVfd25#%*~yX3PFfC-QDjZM<mPm?|`I~Ts) zG(x-E0)g4zh0=rjtlXj{J3^tdMU&%#d#!gTckw3#-?DPekZCL_?|!#Am4X+6zdxyE+` z<5vHn`-N8wIc!XuVa5(IkbB|lM)#}CUGEvYiVVY;Hn~&qc}u-&wHSu8@0p;sFcxbX z(^6y9E5v?t;r7OoffvTM82(hXzH$7(P-KmrkQ)1fp`Nt$FByA73p>K)82`J|JFCY? z?plv{xp$Y>U{Rfe_gm@_^3L9Gs#Ug^F$-54-HXg2PZ&dfu%HC=LZ2S|rKK8dy}gIq zg{zG2Av0DRIjG1ICS8--4e$ZmPtmRiP*Bm1pI*r_cU{7|-rtyYS$<@qb*$PtaHG`{ zFe+lJ5<~eJB?;FL4!wJK|G_!MX6#*toiM4&DlKoR9NbbkxMk=o=8)OOSC!VJadxhx zf2Lew?wVm|NGc7rREAm#i&}zLSbndXWY^T*2Z!PSzEadPJjC*sm-O-UHM3AeRZ>xY zM%m?7(R+7S_HL<4)TGqtM$e#bk)H7LU6HwPm=VsW zD^K4A=8z%AnEcL&S6y#B#-vWxu?wsNng2z* zhZH;Qo|PKa?q+jgFQF5x(qTXMM_-O7yPN*mYJ;r~ve2N{sb}nND{Q*;-1UYPKG()} zdxpH)=q=4^|3#ZhvO6iUFmkwa^tw+r7E>?#qbK1 z-uU<7U#Q@wy(3PTk&PcsO3s_qudFb;#hjG3V^Zq7lTyxo<=^y$Hz&&+Qm%@E-UBaN zp)~LQp90mPl+cV07{~|hy1i(r_Xt8gFWkF89UzRoL>a;L)*iE^#k=H)dfTp)y#l6dS{#d%>-u|J$$d*sDkDZzjT;e#{m8`-Ky4|%fL6tpV zg@0|Sb*#>=b}G2osx3qRm6*A)g`t)n;e}R(2i;$Gua)O5?^+(}Iyh(fpz^Mzll|V( zz`-KFiklWQrpXvHs`2y69+6S^hm!6)l^RNVX!VX@4N82as6%*~Q=I85yTuCMWU2e@ zo`G%wbf=0!NsB|H?hB5$)g8mvLtT_*^o6|I3SVcbyBzubN8SyN5zXj-YO1VorKN7C zX6~db%AdF+cWp^(aIDoMtEeCYEePb4`HK1$XFvtYG&=V#DmTJcSs{w4S zHBAY5jhx=*kn1q*lwHyI!RXGB(L)U5=Fy#}l|OOkgim5)*u}H@S-12p@)+pU!0enp zv7M*YY(D9atr$IbbVGJZS+O-H>4CaSS^|Sx8nRM~n(F%(ZS0nUv-LnP>sWzR8EV=e zN*R{WFAA{WJDd_1^TNmjIMPhDZIArDYjtK^> z&`cwkgy!c=P6&3jCQpi2VYaz;etDA$$6NlPxt^T3KH(J0muJEDv+#_&w;F&Vxfw%*|OIc`o>MTZK8$%$b@JjIsL7 zVu_uc3?;LI|7hc(?OfvVm-jW(OA@O__fxm`G|GN#3!i9H$<&2UxA{$UaBbN~1s!r@ zOFCYa=#NqDne;a^c&2Ucq#nIW)*gB(HaD$DFLZWp+Wt^W=(f!Ag3O|V3nN$d8des% zt>d(`*pT1cm5x&>Y3{VF(PR8($8zubt8-o+zQgZ9=eCrU4fL0W>c>oqQ{S|~c}r+P z)vW77vG!R34FoxxFB<5-GNitcbR52g%IF!hcXG(@@%Ij7-Fq~&pth_!R3Ex6Bb0Ri zH4hsR^)bb-H=e) zyXBTCWskIl?`cynw>7T5v}?E6Nw(9VsG#ds0Jq5RfZcYS1@0>P43Y@c6T8I(fL%QnVzwW~bGbvHo;VXW zWEc^nG%A7E?by-43699P5tuS&t_CPBX3mW}t>RA2x(-7Vss4*8;nKo(0Ak z;t-c&o#nQE05Dra;T2qeA=plV-434soC=-=%mj9w)&aW%svem6s979^8-N*@*3b=+ zGysa8ib^2np`qiKyn22>U9jnysXJq0t|Zy`t6N4z;((a#kEl4>V*A?;e5vr~f&GHx z=?8&j68DECg^R4w9X5om7I?GZE^ydMaGxkVk_92aPsCuvE#P`!rm=t<0Njq64creL z3wR#bJ)*AxV>-0?cYqTG9|3j?{ua1Y_&(?lcZA;rJdQ_>PG61cG~$aS!y#bzh#m&! znw|RgILYAGu4n>q7vb~li=)F6yS-Zo%p~UR18{-hQDH>f0xE$gNWyxm5!e&N8kU(a zkF}3tWB*D1O^;1)O^;azvi+vU`AJF*+Vs?de*_{Mz@)SF2z#&*vw43JOqZOp$g|@) zWx1L*!PIzBFY+8j@q)>BjN&hg!ebpAF?hm`mjn*u+XZuSsu%nKoGudFANqHq_(OtO z@R2CZsVM$y!R?XvN5L#B-NX@Pty#c@NF=7Hn_w1pv0!ExD42zf7tFk?1ao*jDws$4 zF~OX9wh0aZA8_?3-#93dY~XW(Sy+3F8CED4I8`ubm~6p?zepE1>t`W=@ZxGB@z9g8fd|5DC`M%%)@E3yFumt31S!`IQ zU^FaZWJ!dzD-_HEN(2Ytq+BqkG){xAqg$i+hXk{tO@cWcz6oE{a}d8Bh2OO?`kx7( zNCHRqXHod1U^@LyFh}=U!8`+gjM5~_;Nq1#OE9n8Jq6Pbmvc-6{pM67);{Laqk}qXlXUb_h30 z0=XL-H>}NwOckaA46*Ma>I`E9HuZuHjp?ExN9+@x-dPM!=1S@%?_A-@5xYLwMC!TF zSnA-2!Ij1eNg#eS3O^>8Z5qb{Ud0aKDdEWx`-Fd1c&<`jjKZ%);Wwgi73X}_X!|sF zNkSE(#6ICU4XbAy4IQ%uCY6f(nESZ>5z z08K@jd*IrHCr9j)16oUsbh}q(7jndIw@;@=4w+vx)0W*2~V!gY;1b2W5g?S z^!R@FD12)ao++5?_B#dhYWGXQ?5BGL{~C5-6u%thxea*CgYsEZl8-^;wtzJ!&nw^h zsLXE(J^;(5BsDDTBf%$NKZ)Yc3ASL*NAasz2_6)-KNSw>h$OL3j@E_3XTb&p^KkSK z%&TV4D1MM&-bjoQ%&X>gf_cNRngwsc4q+nV_BwzhBktMRgt&WlaskZBkt24ejyr|t zrS=}dyxUmAyfURM5S|>dd$Lg{Jr^>TON7^W9fg1A;!Sg2sL4`!jUOK9stOye0~y$7 zygM{fh}*{3Ir-%p#s{#+1gE1kdl3je6Zo9)*}%L{p*{c{!;=A+e&PiqYEQ5>58PGw zeBe2P=|5uP$Wg=s?9&;z7?`KGTfoy%8J-tR{T9I`z^_N?{}iRS7mDKli0}iT|L|u$ zM$T6fDMf;P9wWnG;1rHD;0oX_QMjjIYV30nno4lj3V$0cZ>X4;nqLVX0n9}s`AT4W z%@{#s93uNAp$gbu1cILcd_;J*__*L|VBTrb5qX{#Zei`b4xS1=S8y#b@5WrsFu}|l z85_Nd7<7O%j8}nwtb{EV$v=I)D z*aN3l5?FAZVAk#~!E=G{6+9bwq2PJIzZM(-epWCC<4c0+^G|~5^F6`zc_fB27iw3J z$cK_ZrymIp04q47hM9eW5j7G87XqgUz7IHC@M7Q`!2#faoHgBH?bR)u6n2Av6Tdr~ z^cN91VrMdG%zyt&`%JYLUC75XkZUN`YcK4d1he7xZ5;UR;643qlcYv zUJ^K;YzE_INJgBMAeY>@?SqU+7h$%5A@;4Z9T?q(r-!Xk9xfK195ITt1745Xo;l-H z3g$%Bf>gKk@xqfMMqVxn&`*-US!-4l-ib7K^xP{vIb!z!?V;wk*daV78gj%w;Wr4+ z=UDFu=CiB+@XEx(sqUC0@FCT4!F-VQsbD^&+6%{OH;XciAA~1I>=Ql>I`%R3`;q4A zFA$y_v8`wOIsdvDE*H!PUk90){fsa~cyh!(;l~Qk2V&O=ehT(5bZQ-Q;sPZ+Ibxsi zHv!+kf>!{{5E1d5D0~E&+(WQbcyh$;Y-+EQFwn8 z{)UctS|OYeo*c2;?6Ziw&2AM9Ibxr5$9ZEcMAG8#M06e^Vz(#Sgtr%Gn9|uwVgu8> zTZtaRlk2fb|E;()R*8Gh&QU!Y2cH|OE^b_P?oyTCc<@|->e=Z1p+NO&EdF6=Bn&SI z^=a_LC$$Jmh+hH*AVl8KEg)gz7F0>T;kw8#Qhz7koZyuA0-~>;8VnW<&Thh zmXSeD#G4LW))*}BrQ0&h+)XooY=~2}+A`HBRi{UJV3zC0Y1Zr0G&}VmFHD7AO0!yj z2xFd~qzhxzNL8XEF<>k8KAKwHH5R5`-xjOpsIB@l#u{|rIGAL;h{n)|Xy)picsp$l zO^e|otYQ=#M9tBci3x_5h(74ywpJd4od0acTZv{yN*O0T6! z*85=06N~iO_Nu$uth@RUJ^rAs@F6-^-^S?9hx95&3-vZew=UHu7~P_i{BU9DzBI*p zB29^2L~~ScfH6-r>cf6W2WX3Iwa!VfHKS;}dJfG5{Wy%du1W7nfHa^_GrHwTotcPe zu^yBNQK_z>snbhg%p*_hEl7;npN$6&`r|~E6w+YREA_QB6Lf^8 zM6ab;rT4*@=eOvyNpOz_CxZ>>p)~pWHW>5lt9n&3636LnWQ+A@G%Iydifc0T$P|?f zXA?=;CPP2wP#dDCFCEH?qAp9dGmc8NGoDUW8TvO4^>`HZcT%f0l5=pxGSX}_$T2mJ zS?ZW(7_+sPKANVw(>DFq?5HnFNA`SOkdAy)^|dhO^4D~II{IvHAugZ#xpa(*jhKj& z^?QKkk-_>bQdO?*+5vg0^w18fhx;K`V^dnk=HGNsj~7QXF~!Awi=0ohHvYDG?v?5i zkC~i0I0LpVmCyh0>nBF43ws2&T02G= z>P>RHpKOn}^N78{SkXmB@Hu{2(_-sebXi~Y&))GkT@pvB$5pubg;6SCs&Dna(dx9@ zfWq<%b?R8PZ2TVP^4}Y38m@K`hBsSz9<`Hj@{YFPOIG+Lt2|@?v`h%7ZFU*<{=fz+ z{JhmNBCx+_K)r7B_8vX)njL#pw~bX9shjPHy?0vvlFk{YdaGyj_;IT1z&T_>5&ol5ue;5DIqQ8@k+RX_?J7-)ncs!xa78A@*lqOuU^P@{sN3 zp`SY>wXtDvd~^EMYD=2>q517v^?YnQPW~Zmvvhqxb#8v_HkIjV?~-?zdVWA%(0I=T zfAgoetHMqyr8#4XvXuH;bH;Df_=HF?*ZOg|UxAC!f&uLpCaQ)AYfEg@;JRUBpYZvJ zt6J<3?!rb5Ibxsi7|Mpa89Rh}Y}Ak=_T6mgPaah%H}DdnZov+L@7*XON9+@xmk4z$ zb_l=2Mh!V)pYQ{K)nx1t)?uS&fF8SA<#Z@TR87GS;Xkl3sZ=jz(qKf@bSB7)2qX>G zZ>?6HBNd?Bq$KGba>PE#GJ={J*dcTf4LM?;@Rf+WrQ0`mD1w}Qb0@VbmAoTi$5B7i zxE=C|&vvt_@P1d_z=fag(j2Yi!g$Pv2( zel|5S;GYxCY(4%NjOw{elA&ck06-_r)f!2Wc}!w)2)L)W4=6cecU0R?^pK?!21Py6 zTz#4h5^}_@{yu7Cv@;I}2{~f-EZ{gmmC9iZ<1!JEB=$*N2Ma$2b_vpO8rZ>pegLQB zM(7P|RYv%Fkv4!Lb_e}(#N9#97kjJ=x$5Tc*Q!C6V^!4mA6S1K(Tivf>q9gLbBxAHzd+MhCuw_rzG0 zY#tw{>%YWGfNTA+dUz~k`FbB@SUY}CvqpD4jZB+kbyEWhyYVy%E7rGx!$L{ZY|-1O zsEgArblXNnp-yT=(qY|~CSFg3iRluLhb{WSR;dmbxWG-*7H~vF))L*tD z<2Y?GN1@L73Z_zzqM57bz?ge()J4po~b6=yxI$iKJ@?mA4=Qf?MMGK>4VYjW_@0!zUG2w`%&w_F{{X*(($R(3V&`w zPP!ReVdP$zlg2OlE9T|)1hepRqiRX0rzfX_xvSI&zHg~dm^XOPI`+QR!cT~U@6lF> ztx80s)(q|^IoAyC>qCH#ohKQ=cPw>?nL(5!;-?5XX&vw-J+fmOvG@3H-(KGa-_%=s zn}NyAmfr($KhwX!S#utH)e2(G$ozq<0Bl}je#lwRVrh#NDX0pwusGSy#Ykd!qb2dg zk|mvc_gjJnPfKIo3N!qBU0Q5*h{Ng>Rd`-cC^j!k-~pPA?L}-qHqH;H>DP+Q)kXYj z;c9G?vE7V~p90Lsb}zOK*j~f78{2+t{LbeSWv~CSd~W`0A9JgzrZ?*n^J8BJUJz8S zb8+JL2begG!_K1eoxx_F(p@7etGW9S^QIX6^l)=UV|5pA^MDa%z-%t;uIiLNnvK!8 zeG zbgh{Iv*lVdtJ9UB5V#6t4)ztX?@FC~vw4wDtun7t!*z8PatzmVtIUEjd-CVPhTQc9?YZ6&p|N6FwbS30@!?a>TBNGmILmOK-rp!6NNqQ`jM_!A95Qh}~=f zVAu6#U~0$_yP9lZSF;CVcNm*bc&4In zGjBATsuR$cotI9eVn6l(}&F`yzNywc#&wx5&ML<2ZnQ3VJsC5Ibxsi#lUK` zuAhj3GFq>iXx`dMa_4cNpyUBi5c|gHF4Z`=BXnuCSpZY#n5Js;Uc|B|nL}afZ-peJ z8zz|_cN&hI2wc*oQ`wonqdq zChEE=Xv9RlcnU`7B%~lb$VQ-SHj~&lN$*6m8qs`WiunuGX|k;{=E0<=I0u#3H(AfV z3FpjsC;2l;W(O1d#_OXunU}f^yo7}yH~W?tH7cHlI`9-jm?+7t1F^44*G)4s+}!qU z26C4m*}lyfuA8QzgQw{IR864^y!sX=e8X3YxSG1M2Cbc{Giz}2PNKvxz7$b~V6?Ux zs)6Ez-)XX*Balq&F2XAjSJl`dbQBFaVqdl1Tw`8}xulil4R;T4eyusjZNcTxrl(?q zB}V(V)?y@&;LNqwRq-iqdYW@?6GOHCW^?R7IjlP+nNEn^VUP*z-t@dJ8gj%qOx3rb zqvbH2mSmpd#5hc=Z?Vs))?3V>Zas>qLMeV&V$`GZR(n9p*j;n0`6wD%I^Aw>)pU$& z91_I3Nmc3Jt; zo2Kh$q5?PTjWbb*+(8DAs-(b$l7k#Es$804X6Vy*n%QukdmEhBfJfjgpPu13C-&8> zth0R=)?r@!H@+W~3;|eTx8DJOz70$SPG_eh5WBa5USP%MATXI4a>PF2ZA}OS0xlKE z5Yl_=FlAKf%2{Y`6*=c75*o7$?k)6Mh`x z5p_EySObcPB(YC;4o25evS`Q=yN)JM^9$?{E*A|sVxREUh%33Q-zXYz5&H{e_j=zT z3GCkAN8zc=?7wmN`@m-c*J4v+utQ*ewwN5TyXNpBt^(LeP(j^tr#%O!&oPH~ zyVIdL@1|df4VKs^JWEk`>iRim#xS8cs+Hgcz|2OD*v%FIb~VocQ$vo}cc(r&$K0gm zG%ufP#woW3_7J+rx#|+Tt_A?31}(qDL?flPfJ%pyqpK7fl3S7NR*(bRE&M%TO34ws zUhVNG?!FccIbu|>@-EC#`AjtAh+WNn)ZBp`!WW_;N9?;pm!_K|o6WmT zmC&t=tuemwA%kEpUtH|BvEKio8rlgR%ur2A@N^sN#SfT|CUkX@(-V*!`uXj7^D7UU z$uaGY_jN8THF{qIF1P>CU&F-k!^|oAEsx4-x3R=|(^8{{E;GBTR$a5q%xPEE59MKs zsL>j%c_c<3{hm+zmYK=zwhVBljvDFaQ)_hJhvBGBU;8i|jT`8S z7m;13*OHwMmUpE!`Vd&NAxP82w%h4{&tfcpg$wqTI#IdYjnnPu+w$l>GaVyx=b~<+jn%bot!yR2%qpt;xhmLbrKwLi*LdqmfOO<0Ue(mHCf4bcyNtvbi%CgVscz5Z(zYF9YI z`QZaPo|%^DI1N|1BMti}V(52`I%cOjPkm=<9IADr+_XhV!-MLzb`D&}omL$pdqRIt zwhrtXyGl8$z^>6lSAk6*>-N?hu;$@3eY710!V9b5eK6Ae_8{5Kw2d8fJ)fJmF|G0% zH($mhNZZpvmpp==s|THK7g9s|zK(h^>E+it&u(gTGwH>dEBS)`%SX&)VEY+v>eG0T z9*=%Od3F%K{~!Mk9@hz!;07EJI~;(HbfLtNpKtL~{|X+%>*ugDQx~SIj;W_a?w%p+8I0mL-bN!_N zqwV^Obk#1_n}aop^RPG}z9*i0q6M`O4AmER!1=PL7}rz%@uOyU_fc_k@@li+%%5YG z?LB#>8{=QVWSYf&`4rha{I}+ve$S7d=Do?D@)(`a(erd96tDK=R%EukxuCRsK~a3l zm3u2ndUQLm&$@Ci{>vcL(DFu8%Z{epgrDNSNI12i?9bMiH4*>iJ^kvq)q>{?2}jDy zc3EM(+Mf{MM+F~}9WoD(df%{0%)gnhTpkQ=$6piZ$(?e$-kzk!B;i?QZ9&-|tnjOr ze~gMd$PBp&x+KHXC6Z*;7Mw_|?9*_xcS`U@tMZx!JCfAlL7&er+hFzo@^kb0UU}ZX zzT1-Dus_t4w=B17`S#?%-UYeK{271V5lTuKmcj}n>BZn0>#D>&l{HT62JfFcqmC# zbsd&cHs1;qH4IZTVk(veu3mrw@KuBTIRWapq+))*P!RuY5io|CW-a-2TQdDkNq+DIGtVed)ERuQNvN|iYfltb-yn$|Z6}%N)g411JM(`GENpY+) z@Eyj@B>S1cYw@%kACd&|PaMuu^YBC~Sk?Sfrl(!Z4V~IUijWPPkIk}Lt-iu7Z|9_O z|1|K6*tTMO1KU1q>BbrRb;A30KKu2;hgOndL0*3joUeJxMHs1=jvoqmextr_?h^E* zrKn%(!Xi(Gx~I9K$kRWzyE?|655H?U?*>re16J66O_1ktZE&CdeTk?0MZ4T*_U2Xo zfqsn9x0v@Te@lL#s7W=SD)E?c9hTvu934JKBuo2BJ$>W9aFXhAoh^*45>e{*jrebu z14pdUf3>bk@MnddxEX)$i9RH0oq{hIz4UG7Y%!N~p zGSv0}{*lCK#*k__4ZmaIUzX7M6`qftI1!{AjO$ z`io7xX`MM@`gitBa^7{k`($BwhxOMZ9^7D^?`0j(r{Y*PD?>)Twh}dy_ffH(K zCF==fQxckD1fO6{TLuKKTc+^U4hnh9@-H8ls5aV>N0I7p9TZUZ>k*t!sN%o`^_-o! z(kcqZqkhhR%HaQ(X05a)J#1AigXx4EWAwzkV;r5(^6!t#Y8L*3CAAwy0 z%g25Vu>6pJIcz3Y6susbfL#syOV}pZXJFUB9)?{98-vThde~yvjj&v&ZHDEqnzyJk zkwIGk_#%(Lz2Zwf{){R=@ysCp*FqsIf8A6J%dezMVAsO#h3+lb{jjHD55n?8<-@Rj zVUxXQ291YJhmF8y!tz7h0m#1x_Mf4#XL(*@VR`p|vn4e@aD}JI6Z-`+d>7MPILx!s zR1M9$hkFXlUK`;8EwrDCrlLziz<&loju-yD0A;Y`6Fho8zlwV2W`FZFBR#cg!y3S< zT<1{(7Cm;Z55+R~F>HKPL2p&?z8aREz5$*gcoGuR^mBvFtmYn*Jts0^>GZg2&b-TW zy|4c+MA9)Qp>vGC3#R8Yf;mXO6`Ty~g=gx|nR@;Go@*jEN<15B_I(!8P+{kXyukUu z&x@uI_$|S>mK*O0##-I3G5rnzZWF!~*nY|jelTz@T0%_)u>FBhdpt52fykATPzgL; zFz+Dm5nKgq|2zYkv9K|ID||KZlY*xL+rPntrWSaI@Y8|qUsZtL2aX4fm8b)M&@6Bw zY?=KNE+p_S?b9fyp9^M1kLZF$o{UZ>0O?~cEV1ul>=0V@#6_O9GZq7?=S+oHA@}MR?(0n_6g6WIE&#E`3CAG?@8gwoz#)Vo{Z3qQb{U^-D2Jpey^$H@AGtT zKDpR4_~K#jL*+iX_&{(M*kgioVUG*uz&Is1ANCu;?6S^!&SuZ1@P47j!4msWXI_MO z*xX+N2u_CO69?+kVGjt- zggqjdr;FVVXxJvZ9l!zL??uBl#jrxXF#y?E`};bG*g*S{2QV90;)v{*I`(r)@C^7Y z+pa_j@GXJ|06!?W6!>w$ENr7-mh}h0ENicdIF4A@UnQX!_;bO4&JQI~@L=H6!n5EM z9$WAgz?}t;0PZfh6u4AyC9wVbZ0O^G?cZJlcR~5aXc6)FRbg{K-hTm@1&||le?jny z@W){PAee`!MKBM+9>F|ZZ$O~mu8r~$Pv4m`X~(_H?Swj5xbiEqBI|ihTO;3MTrt+ z9PsM1m>F;$GxQgp9I<ccdhbl_vpt?B~38>}@_JU2y`jUb=y?0=@SHl!f{{O<3oD4zfts()8AA zK1Z#Kb@EBiSk*^QJqgJrdihDurIEgN?;2-2qN__B?9Bk~=ip@G{toU!e5r$Ti3d2i zm>7??7)pupu!>;>aj6ZBD&jH+PbDsQ@FjJOT<%2X5f66oV&W?tyqtK5gPVwl{`|bJ z@qz5z=EPH;b@BLotoa)ceh4#L$A9Y?iC=15`>kgbI%_QrK0kypx89*EzC)r{>+it& z^=IFCE=IqmeecPN8HZc51br!C!yHWLdI})kOE3D~gU{oBr8j=>>8|$ZLx`d;Et(RY za~6sF=jqC`NSvc2FIZv{=C8A5uAxFKgIS0*& zh;BgCj8Aqn?@-fFua8nwsN;TsrlnpN{s7Gu(aik8lZ=n$B1pq;V%Ac!X1?A}$?Ww z?;5?4i4FP?*-CAZ{icV`{RvsuKcg#Q)oeZIC#25SkJIecyJ5_u>vSs<^L0iW*b#aV z&0JjrW3GQzH?$!!UNWTA7iDmCi9?X6sQf=9Z$Bry%%^2^U*m&`l;> z9M-#;bm|4&il}*_NN0LLd-Wg>=<_&58SSE%B8oxVOjE2sgo(+=L_1siyxy#s5gRb? z>fV4DR@ZuwvwD-R_aZ00EoF4x%X&Yfh5CC&TX*O{42s)3!1=24#u!uykvfw5g?xeJ z!2!C3B&_}YXB)}G19WmMNLZU38S9YMB#$7G->J3 fjz=%$<8EcW_O(MVt=0wY(31`NTE|4%c{BbK@7I0U From 990293129b4c35def78d9dedcba4e06e4c7c5c8c Mon Sep 17 00:00:00 2001 From: yuanjm Date: Fri, 12 Mar 2021 14:10:06 +0800 Subject: [PATCH 03/59] fix(mbedtls): Fix mbedtls_ssl_send_alert_message crash due to ssl->out_iv is NULL --- .../port/dynamic/esp_mbedtls_dynamic_impl.c | 173 ++++++++++-------- .../port/dynamic/esp_mbedtls_dynamic_impl.h | 15 ++ components/mbedtls/port/dynamic/esp_ssl_tls.c | 4 +- 3 files changed, 114 insertions(+), 78 deletions(-) diff --git a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c index e78ea5afe..f64b09e35 100644 --- a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c +++ b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c @@ -23,6 +23,33 @@ static const char *TAG = "Dynamic Impl"; +static void esp_mbedtls_set_buf_state(unsigned char *buf, esp_mbedtls_ssl_buf_states state) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + temp->state = state; +} + +static esp_mbedtls_ssl_buf_states esp_mbedtls_get_buf_state(unsigned char *buf) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + return temp->state; +} + +void esp_mbedtls_free_buf(unsigned char *buf) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + ESP_LOGV(TAG, "free buffer @ %p", temp); + mbedtls_free(temp); +} + +static void esp_mbedtls_init_ssl_buf(struct esp_mbedtls_ssl_buf *buf, unsigned int len) +{ + if (buf) { + buf->state = ESP_MBEDTLS_SSL_BUF_CACHED; + buf->len = len; + } +} + static void esp_mbedtls_parse_record_header(mbedtls_ssl_context *ssl) { ssl->in_msgtype = ssl->in_hdr[0]; @@ -118,29 +145,30 @@ static void init_rx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) static int esp_mbedtls_alloc_tx_buf(mbedtls_ssl_context *ssl, int len) { - unsigned char *buf; + struct esp_mbedtls_ssl_buf *esp_buf; if (ssl->out_buf) { - mbedtls_free(ssl->out_buf); + esp_mbedtls_free_buf(ssl->out_buf); ssl->out_buf = NULL; } - buf = mbedtls_calloc(1, len); - if (!buf) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", len); + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + len); return MBEDTLS_ERR_SSL_ALLOC_FAILED; } - ESP_LOGV(TAG, "add out buffer %d bytes @ %p", len, buf); + ESP_LOGV(TAG, "add out buffer %d bytes @ %p", len, esp_buf->buf); + esp_mbedtls_init_ssl_buf(esp_buf, len); /** * Mark the out_msg offset from ssl->out_buf. - * + * * In mbedtls, ssl->out_msg = ssl->out_buf + offset; */ ssl->out_msg = (unsigned char *)MBEDTLS_SSL_HEADER_LEN; - init_tx_buffer(ssl, buf); + init_tx_buffer(ssl, esp_buf->buf); return 0; } @@ -150,7 +178,7 @@ int esp_mbedtls_setup_tx_buffer(mbedtls_ssl_context *ssl) CHECK_OK(esp_mbedtls_alloc_tx_buf(ssl, TX_IDLE_BUFFER_SIZE)); /* mark the out buffer has no data cached */ - ssl->out_iv = NULL; + esp_mbedtls_set_buf_state(ssl->out_buf, ESP_MBEDTLS_SSL_BUF_NO_CACHED); return 0; } @@ -168,10 +196,7 @@ int esp_mbedtls_reset_add_tx_buffer(mbedtls_ssl_context *ssl) int esp_mbedtls_reset_free_tx_buffer(mbedtls_ssl_context *ssl) { - ESP_LOGV(TAG, "free out buffer @ %p", ssl->out_buf); - - mbedtls_free(ssl->out_buf); - + esp_mbedtls_free_buf(ssl->out_buf); init_tx_buffer(ssl, NULL); CHECK_OK(esp_mbedtls_setup_tx_buffer(ssl)); @@ -181,60 +206,57 @@ int esp_mbedtls_reset_free_tx_buffer(mbedtls_ssl_context *ssl) int esp_mbedtls_reset_add_rx_buffer(mbedtls_ssl_context *ssl) { - unsigned char *buf; + struct esp_mbedtls_ssl_buf *esp_buf; if (ssl->in_buf) { - mbedtls_free(ssl->in_buf); + esp_mbedtls_free_buf(ssl->in_buf); ssl->in_buf = NULL; } - buf = mbedtls_calloc(1, MBEDTLS_SSL_IN_BUFFER_LEN); - if (!buf) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", MBEDTLS_SSL_IN_BUFFER_LEN); + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + MBEDTLS_SSL_IN_BUFFER_LEN); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + MBEDTLS_SSL_IN_BUFFER_LEN); return MBEDTLS_ERR_SSL_ALLOC_FAILED; } - ESP_LOGV(TAG, "add in buffer %d bytes @ %p", MBEDTLS_SSL_IN_BUFFER_LEN, buf); + ESP_LOGV(TAG, "add in buffer %d bytes @ %p", MBEDTLS_SSL_IN_BUFFER_LEN, esp_buf->buf); + esp_mbedtls_init_ssl_buf(esp_buf, MBEDTLS_SSL_IN_BUFFER_LEN); /** * Mark the in_msg offset from ssl->in_buf. - * + * * In mbedtls, ssl->in_msg = ssl->in_buf + offset; */ ssl->in_msg = (unsigned char *)MBEDTLS_SSL_HEADER_LEN; - init_rx_buffer(ssl, buf); + init_rx_buffer(ssl, esp_buf->buf); - return 0; + return 0; } void esp_mbedtls_reset_free_rx_buffer(mbedtls_ssl_context *ssl) { - ESP_LOGV(TAG, "free in buffer @ %p", ssl->in_buf); - - mbedtls_free(ssl->in_buf); - - init_rx_buffer(ssl, NULL); + esp_mbedtls_free_buf(ssl->in_buf); + init_rx_buffer(ssl, NULL); } int esp_mbedtls_add_tx_buffer(mbedtls_ssl_context *ssl, size_t buffer_len) { int ret = 0; int cached = 0; - unsigned char *buf; + struct esp_mbedtls_ssl_buf *esp_buf; unsigned char cache_buf[CACHE_BUFFER_SIZE]; ESP_LOGV(TAG, "--> add out"); if (ssl->out_buf) { - if (ssl->out_iv) { + if (esp_mbedtls_get_buf_state(ssl->out_buf) == ESP_MBEDTLS_SSL_BUF_CACHED) { ESP_LOGV(TAG, "out buffer is not empty"); ret = 0; goto exit; } else { memcpy(cache_buf, ssl->out_buf, CACHE_BUFFER_SIZE); - - mbedtls_free(ssl->out_buf); + esp_mbedtls_free_buf(ssl->out_buf); init_tx_buffer(ssl, NULL); cached = 1; } @@ -242,15 +264,17 @@ int esp_mbedtls_add_tx_buffer(mbedtls_ssl_context *ssl, size_t buffer_len) buffer_len = tx_buffer_len(ssl, buffer_len); - buf = mbedtls_calloc(1, buffer_len); - if (!buf) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", buffer_len); + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; goto exit; } - ESP_LOGV(TAG, "add out buffer %d bytes @ %p", buffer_len, buf); - init_tx_buffer(ssl, buf); + ESP_LOGV(TAG, "add out buffer %d bytes @ %p", buffer_len, esp_buf->buf); + + esp_mbedtls_init_ssl_buf(esp_buf, buffer_len); + init_tx_buffer(ssl, esp_buf->buf); if (cached) { memcpy(ssl->out_ctr, cache_buf, COUNTER_SIZE); @@ -270,11 +294,11 @@ int esp_mbedtls_free_tx_buffer(mbedtls_ssl_context *ssl) { int ret = 0; unsigned char buf[CACHE_BUFFER_SIZE]; - unsigned char *pdata; + struct esp_mbedtls_ssl_buf *esp_buf; ESP_LOGV(TAG, "--> free out"); - if (!ssl->out_buf || (ssl->out_buf && !ssl->out_iv)) { + if (!ssl->out_buf || (ssl->out_buf && (esp_mbedtls_get_buf_state(ssl->out_buf) == ESP_MBEDTLS_SSL_BUF_NO_CACHED))) { ret = 0; goto exit; } @@ -282,22 +306,19 @@ int esp_mbedtls_free_tx_buffer(mbedtls_ssl_context *ssl) memcpy(buf, ssl->out_ctr, COUNTER_SIZE); memcpy(buf + COUNTER_SIZE, ssl->out_iv, CACHE_IV_SIZE); - ESP_LOGV(TAG, "free out buffer @ %p", ssl->out_buf); - - mbedtls_free(ssl->out_buf); - + esp_mbedtls_free_buf(ssl->out_buf); init_tx_buffer(ssl, NULL); - pdata = mbedtls_calloc(1, TX_IDLE_BUFFER_SIZE); - if (!pdata) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", TX_IDLE_BUFFER_SIZE); + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + TX_IDLE_BUFFER_SIZE); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + TX_IDLE_BUFFER_SIZE); return MBEDTLS_ERR_SSL_ALLOC_FAILED; } - memcpy(pdata, buf, CACHE_BUFFER_SIZE); - init_tx_buffer(ssl, pdata); - ssl->out_iv = NULL; - + esp_mbedtls_init_ssl_buf(esp_buf, TX_IDLE_BUFFER_SIZE); + memcpy(esp_buf->buf, buf, CACHE_BUFFER_SIZE); + init_tx_buffer(ssl, esp_buf->buf); + esp_mbedtls_set_buf_state(ssl->out_buf, ESP_MBEDTLS_SSL_BUF_NO_CACHED); exit: ESP_LOGV(TAG, "<-- free out"); @@ -309,7 +330,7 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) int cached = 0; int ret = 0; int buffer_len; - unsigned char *buf; + struct esp_mbedtls_ssl_buf *esp_buf; unsigned char cache_buf[16]; unsigned char msg_head[5]; size_t in_msglen, in_left; @@ -317,15 +338,11 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) ESP_LOGV(TAG, "--> add rx"); if (ssl->in_buf) { - if (ssl->in_iv) { + if (esp_mbedtls_get_buf_state(ssl->in_buf) == ESP_MBEDTLS_SSL_BUF_CACHED) { ESP_LOGV(TAG, "in buffer is not empty"); ret = 0; goto exit; } else { - memcpy(cache_buf, ssl->in_buf, 16); - - mbedtls_free(ssl->in_buf); - init_rx_buffer(ssl, NULL); cached = 1; } } @@ -341,7 +358,7 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) } else { ESP_LOGE(TAG, "mbedtls_ssl_fetch_input error=-0x%x", -ret); } - + goto exit; } @@ -354,16 +371,23 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) ESP_LOGV(TAG, "message length is %d RX buffer length should be %d left is %d", (int)in_msglen, (int)buffer_len, (int)ssl->in_left); - buf = mbedtls_calloc(1, buffer_len); - if (!buf) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", buffer_len); + if (cached) { + memcpy(cache_buf, ssl->in_buf, 16); + esp_mbedtls_free_buf(ssl->in_buf); + init_rx_buffer(ssl, NULL); + } + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; goto exit; } - ESP_LOGV(TAG, "add in buffer %d bytes @ %p", buffer_len, buf); + ESP_LOGV(TAG, "add in buffer %d bytes @ %p", buffer_len, esp_buf->buf); - init_rx_buffer(ssl, buf); + esp_mbedtls_init_ssl_buf(esp_buf, buffer_len); + init_rx_buffer(ssl, esp_buf->buf); if (cached) { memcpy(ssl->in_ctr, cache_buf, 8); @@ -377,14 +401,14 @@ int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) exit: ESP_LOGV(TAG, "<-- add rx"); - return ret; + return ret; } int esp_mbedtls_free_rx_buffer(mbedtls_ssl_context *ssl) { int ret = 0; unsigned char buf[16]; - unsigned char *pdata; + struct esp_mbedtls_ssl_buf *esp_buf; ESP_LOGV(TAG, "--> free rx"); @@ -392,7 +416,7 @@ int esp_mbedtls_free_rx_buffer(mbedtls_ssl_context *ssl) * When have read multi messages once, can't free the input buffer directly. */ if (!ssl->in_buf || (ssl->in_hslen && (ssl->in_hslen < ssl->in_msglen)) || - (ssl->in_buf && !ssl->in_iv)) { + (ssl->in_buf && (esp_mbedtls_get_buf_state(ssl->in_buf) == ESP_MBEDTLS_SSL_BUF_NO_CACHED))) { ret = 0; goto exit; } @@ -407,23 +431,20 @@ int esp_mbedtls_free_rx_buffer(mbedtls_ssl_context *ssl) memcpy(buf, ssl->in_ctr, 8); memcpy(buf + 8, ssl->in_iv, 8); - ESP_LOGV(TAG, "free in buffer @ %p", ssl->out_buf); - - mbedtls_free(ssl->in_buf); - + esp_mbedtls_free_buf(ssl->in_buf); init_rx_buffer(ssl, NULL); - pdata = mbedtls_calloc(1, 16); - if (!pdata) { - ESP_LOGE(TAG, "alloc(%d bytes) failed", 16); + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + 16); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + 16); ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; goto exit; } - memcpy(pdata, buf, 16); - init_rx_buffer(ssl, pdata); - ssl->in_iv = NULL; - + esp_mbedtls_init_ssl_buf(esp_buf, 16); + memcpy(esp_buf->buf, buf, 16); + init_rx_buffer(ssl, esp_buf->buf); + esp_mbedtls_set_buf_state(ssl->in_buf, ESP_MBEDTLS_SSL_BUF_NO_CACHED); exit: ESP_LOGV(TAG, "<-- free rx"); @@ -438,7 +459,7 @@ size_t esp_mbedtls_get_crt_size(mbedtls_x509_crt *cert, size_t *num) while (cert) { bytes += cert->raw.len; n++; - + cert = cert->next; } diff --git a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h index 8f4bb144c..2c6b15c00 100644 --- a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h +++ b/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h @@ -41,6 +41,21 @@ \ }) +typedef enum { + ESP_MBEDTLS_SSL_BUF_CACHED, + ESP_MBEDTLS_SSL_BUF_NO_CACHED, +} esp_mbedtls_ssl_buf_states; + +struct esp_mbedtls_ssl_buf { + esp_mbedtls_ssl_buf_states state; + unsigned int len; + unsigned char buf[]; +}; + +#define SSL_BUF_HEAD_OFFSET_SIZE offsetof(struct esp_mbedtls_ssl_buf, buf) + +void esp_mbedtls_free_buf(unsigned char *buf); + int esp_mbedtls_setup_tx_buffer(mbedtls_ssl_context *ssl); void esp_mbedtls_setup_rx_buffer(mbedtls_ssl_context *ssl); diff --git a/components/mbedtls/port/dynamic/esp_ssl_tls.c b/components/mbedtls/port/dynamic/esp_ssl_tls.c index 081e50af8..d8b4506b5 100644 --- a/components/mbedtls/port/dynamic/esp_ssl_tls.c +++ b/components/mbedtls/port/dynamic/esp_ssl_tls.c @@ -108,12 +108,12 @@ int __wrap_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t void __wrap_mbedtls_ssl_free(mbedtls_ssl_context *ssl) { if (ssl->out_buf) { - mbedtls_free(ssl->out_buf); + esp_mbedtls_free_buf(ssl->out_buf); ssl->out_buf = NULL; } if (ssl->in_buf) { - mbedtls_free(ssl->in_buf); + esp_mbedtls_free_buf(ssl->in_buf); ssl->in_buf = NULL; } From a8be948f21d001bdcd70125cef73c8cc85160c50 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Tue, 27 Apr 2021 14:39:50 +0800 Subject: [PATCH 04/59] tcpip_adapter: fix set static ip fail and remove the dhcp_check timer --- components/tcpip_adapter/tcpip_adapter_lwip.c | 84 +++++-------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index d3a31d635..bf3f8bad9 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -74,7 +74,6 @@ static esp_err_t tcpip_adapter_reset_ip_info(tcpip_adapter_if_t tcpip_if); static esp_err_t tcpip_adapter_start_ip_lost_timer(tcpip_adapter_if_t tcpip_if); static void tcpip_adapter_dhcpc_cb(struct netif *netif); static void tcpip_adapter_ip_lost_timer(void *arg); -static void tcpip_adapter_dhcpc_done(TimerHandle_t arg); static bool tcpip_inited = false; static const char* TAG = "tcpip_adapter"; @@ -85,9 +84,25 @@ ESP_EVENT_DEFINE_BASE(IP_EVENT); err_t ethernetif_init(struct netif* netif); void system_station_got_ip_set(); -static int dhcp_fail_time = 0; static tcpip_adapter_ip_info_t esp_ip[TCPIP_ADAPTER_IF_MAX]; -static TimerHandle_t *dhcp_check_timer; + +static void tcpip_adapter_set_wifi_ps(bool flag) +{ + static bool is_ps_set = false; + if (flag) { + if (!is_ps_set) { + ESP_LOGD(TAG, "enable wifi ps"); + esp_wifi_ps_lock(); + is_ps_set = true; + } + } else { + if (is_ps_set) { + ESP_LOGD(TAG, "disable wifi ps"); + esp_wifi_ps_unlock(); + is_ps_set = false; + } + } +} static void tcpip_adapter_dhcps_cb(u8_t client_ip[4]) { @@ -192,11 +207,6 @@ void tcpip_adapter_init(void) IP4_ADDR(&esp_ip[TCPIP_ADAPTER_IF_AP].ip, 192, 168 , 4, 1); IP4_ADDR(&esp_ip[TCPIP_ADAPTER_IF_AP].gw, 192, 168 , 4, 1); IP4_ADDR(&esp_ip[TCPIP_ADAPTER_IF_AP].netmask, 255, 255 , 255, 0); - - dhcp_check_timer = xTimerCreate("check_dhcp", 500 / portTICK_RATE_MS, true, NULL, tcpip_adapter_dhcpc_done); - if (!dhcp_check_timer) { - ESP_LOGI(TAG, "TCPIP adapter timer create error"); - } } } @@ -287,56 +297,6 @@ static int tcpip_adapter_sta_recv_cb(void *buffer, uint16_t len, void *eb) return tcpip_adapter_recv_cb(esp_netif[ESP_IF_WIFI_STA], buffer, len, eb); } -static void tcpip_adapter_dhcpc_done(TimerHandle_t xTimer) -{ - bool unlock_ps = true; - struct dhcp *clientdhcp = netif_dhcp_data(esp_netif[TCPIP_ADAPTER_IF_STA]) ; - struct netif *netif = esp_netif[TCPIP_ADAPTER_IF_STA]; - - if (!netif) { - ESP_LOGD(TAG, "null netif=%p", netif); - return; - } - -#if LWIP_IPV4 && LWIP_AUTOIP - struct autoip *autoip = netif_autoip_data(netif); -#endif - - xTimerStop(dhcp_check_timer, 0); - if (netif_is_up(esp_netif[TCPIP_ADAPTER_IF_STA])) { - if ((clientdhcp && clientdhcp->state == DHCP_STATE_BOUND) -#if LWIP_IPV4 && LWIP_AUTOIP - || (autoip && autoip->state == AUTOIP_STATE_ANNOUNCING) -#endif - ) { - /*send event here*/ - tcpip_adapter_dhcpc_cb(esp_netif[TCPIP_ADAPTER_IF_STA]); - ESP_LOGD(TAG,"ip:" IPSTR ",mask:" IPSTR ",gw:" IPSTR "\n", IP2STR(ip_2_ip4(&(esp_netif[0]->ip_addr))), - IP2STR(ip_2_ip4(&(esp_netif[0]->netmask))), IP2STR(ip_2_ip4(&(esp_netif[0]->gw)))); - dhcp_fail_time = 0; - } else if (dhcp_fail_time < (CONFIG_IP_LOST_TIMER_INTERVAL * 1000 / 500)) { - ESP_LOGD(TAG,"dhcpc time(ms): %d\n", dhcp_fail_time * 500); - dhcp_fail_time ++; - xTimerReset(dhcp_check_timer, 0); - unlock_ps = false; - } else { - dhcp_fail_time = 0; - ESP_LOGD(TAG,"ERROR dhcp get ip error\n"); - } - } else { - dhcp_fail_time = 0; - tcpip_adapter_release_dhcp(esp_netif[TCPIP_ADAPTER_IF_STA]); - - dhcpc_status[TCPIP_ADAPTER_IF_STA] = TCPIP_ADAPTER_DHCP_INIT; - - tcpip_adapter_reset_ip_info(TCPIP_ADAPTER_IF_STA); - } - - if (unlock_ps) { - esp_wifi_ps_unlock(); - } -} - static esp_err_t tcpip_adapter_update_default_netif(void) { if ((esp_netif[TCPIP_ADAPTER_IF_STA] != NULL) && netif_is_up(esp_netif[TCPIP_ADAPTER_IF_STA])) { @@ -1073,6 +1033,7 @@ static void tcpip_adapter_dhcpc_cb(struct netif *netif) } } + tcpip_adapter_set_wifi_ps(false); return; } @@ -1171,16 +1132,13 @@ esp_err_t tcpip_adapter_dhcpc_start(tcpip_adapter_if_t tcpip_if) return ESP_OK; } - esp_wifi_ps_lock(); + tcpip_adapter_set_wifi_ps(true); if (tcpip_adapter_start_dhcp(p_netif) != ERR_OK) { - esp_wifi_ps_unlock(); + tcpip_adapter_set_wifi_ps(false); ESP_LOGD(TAG, "dhcp client start failed"); return ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED; } - - dhcp_fail_time = 0; - xTimerReset(dhcp_check_timer, portMAX_DELAY); ESP_LOGD(TAG, "dhcp client start successfully"); dhcpc_status[tcpip_if] = TCPIP_ADAPTER_DHCP_STARTED; return ESP_OK; From f9aca5a8e002f78ad463d5aa9357abf37307ed43 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 28 Apr 2021 11:56:21 +0800 Subject: [PATCH 05/59] fix: potential blocking code when call uart_wait_tx_done() 1. there is a risk of ticks_end overflow, if xTaskGetTickCount() plus ticks_to_wait is bigger than portMAX_DELAY 2. potential blocking code on waiting for tx fifo done. It usually occurs at esp uart uses flow control, and the other side of uart not. --- components/esp8266/driver/uart.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/components/esp8266/driver/uart.c b/components/esp8266/driver/uart.c index 63d2a92bb..ae067dd07 100644 --- a/components/esp8266/driver/uart.c +++ b/components/esp8266/driver/uart.c @@ -257,7 +257,18 @@ esp_err_t uart_wait_tx_done(uart_port_t uart_num, TickType_t ticks_to_wait) uint32_t baudrate; uint32_t byte_delay_us = 0; BaseType_t res; + portTickType ticks_cur; + portTickType ticks_start = xTaskGetTickCount(); portTickType ticks_end = xTaskGetTickCount() + ticks_to_wait; + /** + * Considering the overflow of the ticks_end and the ticks_cur (xTaskGetTickCount()), + * the possible tick timestamp is as follows: + * (one start tick timestamp, two end tick timestamps, four current tick timestamps) + * + * ticks: 0 0xFFFFFFFF + * |_______._______._______._______._______._______._______._______| + * cur1 end1 cur2 start cur3 end2 cur4 + */ // Take tx_mux res = xSemaphoreTake(p_uart_obj[uart_num]->tx_mux, (portTickType)ticks_to_wait); @@ -273,10 +284,22 @@ esp_err_t uart_wait_tx_done(uart_port_t uart_num, TickType_t ticks_to_wait) uart_get_baudrate(uart_num, &baudrate); byte_delay_us = (uint32_t)(10000000 / baudrate); // (1/baudrate)*10*1000_000 us - ticks_to_wait = ticks_end - xTaskGetTickCount(); + ticks_cur = xTaskGetTickCount(); + if (ticks_start <= ticks_cur) { + ticks_to_wait = ticks_to_wait - (ticks_cur - ticks_start); + } else { + ticks_to_wait = ticks_to_wait - (portMAX_DELAY - ticks_start + ticks_cur); + } // wait for tx done sem. if (pdTRUE == xSemaphoreTake(p_uart_obj[uart_num]->tx_done_sem, ticks_to_wait)) { while (1) { + ticks_cur = xTaskGetTickCount(); + bool end1_timeout = (ticks_end < ticks_start && ticks_cur < ticks_start && ticks_cur > ticks_end); + bool end2_timeout = (ticks_start < ticks_end && (ticks_cur < ticks_start || ticks_end < ticks_cur)); + if (end1_timeout || end2_timeout) { + xSemaphoreGive(p_uart_obj[uart_num]->tx_mux); + return ESP_ERR_TIMEOUT; + } if (UART[uart_num]->status.txfifo_cnt == 0) { ets_delay_us(byte_delay_us); // Delay one byte time to guarantee transmission completion break; From 0c9be8d891d9cd8f3287ece990450fc890ed3446 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 12 May 2021 19:43:57 +0800 Subject: [PATCH 06/59] fix(lib): fix beacon ie error when set to bgn mode --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 484932 -> 484912 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 536908 -> 536888 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index b2c4332be..b58432f71 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: cebfb0e - net80211: 8114f9c + net80211: 9aeb2d7 pp: 5fda74f espnow: 7a93d71 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index ed88b9dcbe91c5dc69945b76bfdb33ab7ce45b3d..03b36c2c4b406019f59d6daf39e439b82322d8e7 100755 GIT binary patch delta 3069 zcmc(hZA@EL7{{OI+!ku;3olD+33P2?!nQ^M8I(CEjA6tuUdBGKF`^{KFkM_(H(ruu z7Z!~em!+n`hb8F5?ZdK+1T4$NWFHhY(S#2&U&x|iYET1-8D^MhLd55Em)oKQx@qH0 z{`~*v_dMsEdv3Xuk0-K*C$fSWmco2rL9uUJ>4>Kf)l9&SJJ8S10WRxi3gBv5Yz2&0 zs@^D^7FeOpS3&$6?e&6abVb!X0AdxXtHHZwQ*|I>b%SqWZK>-u{G+Z^4a2%>sskl1 zscSS2dv|gq$kQ&3SM2 zKYOf6=SJah8^e{BD-RprJ2X9q_OPSmM_UxtGzxn- ztLE`61BGg4Y~juqFS!gtk*4y~?o0zknz}Dui+-s^i2tLnqtlo;M3FD#uF1vG@EbEc zI`v?v>D?fDLH?`8-KxtB(U; zGdj2Mf^#vsh->&vqy=v2T(VnKUyF8iNk?)OnXN6iTdll|+(w>Z2P5Q~yi#;7naoz^ zCeE2UFJgDaRd9Jm(Sg#^AfF~ z4r`rwn0j8qS`5;pw0-10f*n{*XOHk`2hB>$qf$Rx8NDX0k*-S{qs?t>KwZ*u=rd`Z z3wPWbWeb7fc6J)EA~+N7b2bd1DIm=wE}d~P=G(D3p5$PTpXr9r1{8w zjO}Hrl6H*Rq>a*9X$$nBv|gHJGcURTMrg27NvFf_no_WpN{@4Oh+5dpV>$e^L#a=t z(>up8h3|QUsTj)%P5)q{d5IPX+qu!Z31B#cRq0gL&J?JgDSk^`(kwJ6dwTiT-~{26 z$VLi4Bf|!eUp0v}ZeTi{E%NHGWwiRp^sBOpP*r@$*D< zc>gaehC6R!-F-$ivgYMQJmp@As-5CZtAXg`l(1{KMbzj0jkWC+UzrUiZ)dj>9m+pJ z!ckc4i#d;L#ewYF_sycOD>{XD`ZC!VMsCl4Lp=Y2fwbu=r9bAhCz}+1xlxTwxc4ok zy}>}l6YSdHyz<*sgK2LMnSy=BP|kX_y27kgS0tS|xB_dxpG&&tiA8p8c08#iXrSBc KA8JVcChad+I@4+Z delta 3096 zcmc(gZA@Eb6vxlGx1|^)LOUs&z-WQdDZ0YgR^ku}%zzQlG84l~l(-pRGJ)})ptnR8 zLu3o)1`kWROfyY1kpykX^@Cd~!-NmhiE+*k)A@p%E?Y*HnV5_W&(mCP=_J6WjW_xC z|9^hZdCv3Po8ET(RQlAZ^x+Nh`S}I;?!xV!kDVi^xBHQ0j^bw zUMOXxL6kOs4bs1nuN0)G3nOY82Wc58%fUNlXZL`#ROk5+D@$3e;2-mH)fB9%hCLv! zNy-X^fpZ|oTFP82$p531l{Pnx4M2>g#Cn5O`YUU@Lvwi=#6(K08n3ah5|g`H+nQ|p6yhfDOqMzitRmI_aE5xxyD_*ce&QkHe@CsEO$ny%xR7EHdhH_G zgXuNJ=@InR(Kvgs_vH^WBPd+u9de`@DBQ~b!8egVH43k5J9#Fuj*Hey+dZnICKsoJ zZ_V;ZTCaBByr8%cM;Af>1rBhcDKpFOYzF6+vK{+fF7GC5mUD}v;GIyeyus;!xspsg zBx*Tu6%L8ap&)PL&@rCJK?Aq~<(0O8$tTA%FF1+#x;{12x!}xm2R`vw1MOv2beeeC ztHJLVcBd2#0C!_KB!CP(mv9zLsF|}}&pn)z^*l(QC*ysZKLzvdL9aHJgGw&$qz9|< z81`s0)tH5erx9SC6~N7FP7~ImZCetz_(R7cUt5w3xeTA-558{bT(BEdT#I(a!sViy zhmpt$&7u&%rgL!zJ9&(}R9vY#7fj>?<|fYBi=5rHB$x6W*7FBnTXkNl=Y)EAOmHaE zC&XiUbV;t@ITR=3t48O7iJYMG=X748r9XkS8Q4MlUg3u_O2>sYQeq2s<8C_8g0**5 z_y8{RfuB@oUbNHo7IdSVa$32rk~)PwOJ4{J(Yck{I<`Je+C|mvUmlQfD&^%ocg@ri) z<0@9!D6fm@Ix6nsF0-^(SQ;HB=q0u6eIwQKC^5e#N!J^Vsh&Sh$%dLwY+Q z8eQHj`CQSc(I2Q1|5@pTB^ouJm!5yg zK=eqR#jgEaCHLR5iN8Uv_Zns4eg6u`U3CT$D^s;YbMp1i48~=4?cFg`?}&lIkK~P_ fa+LQ z5VxT~Y0{8Gwy~H@Ge67>t>p4S7M8egn)nZ+W?PnJm}Qv@M3=Zvc;3t9(kenX(|D7o z&-4DydEfiq+uqx+Zn!3IxFY$s$_oGERsJg9r=Ag1UIrXaUP9afL+eLDtR%-Ah-9TK zH3lLe;#D66@n6*UA&7q7;F7}GAXbpF5`0)@%Ik&%xnBg+>QdHf_-Fl$7ffsE!Vh3d zO3J@CnqeAJE~R%I%>Sd5)lP0{c0!7!r1}JFtgj?_h4Y_-B^6RqvFHP=sifqplHUM- z|H(J6+G}lUfCpCl{-40w{&2Dtl*1Z|hN=sqx1ZS>tG7*TH~x3%TCdp=%k$0V7$};4 zDktXL{X%gfi5zWTCb9HWp)HB^^ON5#|D91mu5iT0zMpX!O(S>B4s)QYvLfo zDA2Qyvt&~HI6L&*#yL~Zz0?#FC2B({UU>uqYH0v3l%kh@eg)5Bo7(d#7NYVE0%T_a zRPY>`?bRIkxT$j+uXj5xd%1?KS_}NHbID}3>)bDUUR&&0(0e44*{*Xtugm9Ew2P{d z6SydkjKu;-CbONnnX^;pUUpYp-oZ6Iz#n{+>3oNt?K*!_=ki896_~P_A+I1W|&`g()XjWQ;o_L?lOUI;rOf%B5sb~PpkiW$NEVPUt2I!5%oO`+MA{*m zVoQs>1Mp=!z#hKKP0_bbZpLy-8|K2DT7X6?z+Ns?M=x=qhV~C*sqtK@;bDCDh%w}+ z-=SXZ7fuuHS|>6MuDvMK9!BDC>=Ro&laIN2b-f6r8Eq=R3`X6{!;1lN-fj%#>qhOb z7j?CX=vJTj(GrieZEZ6dNZZjl-jSfvaNJI{9qbE>odZUzc8=*h(?zR+w5~C?X{y^m zT36>8Q(xRw^UZOndE=(98;!P!yXIRVW2Ev6wB;9Ls4aW2+0r#)Anjdv;{Lovy+6OM c>wv+v(?y!BZx$sYXNR@wTq0^KuwBXe3!6;@EzsRXU>hkcNTF*nTm*xL(qh!`&<4qnj$GFiP6+hBz1V9f#`!KrizyrFNrj*LDLx5^g+|mdcI|tT`B^tAx`q= z{O5PhcfQNQw@h5Oe}3KGyCpm;D?7`blecrs*^lZmz@D{Bk^u}wTmor1#VH`IRqA4) zX9T2Bl~#fDUvz2$qz+!-qPkrmEhBX~_^?cl?}a6D90qx1sjC(IGe1-Y@@g8L19?qS z|GiLwZ(!Y}wtWK1|0s2($xZv+u+CD~dxBN^t83h$|5Grnht&0G`T#xEb=lS07r>wY z+KX4LHCNWa1FP}CEigAeoWhd|VHNpWcPIINE!s9-8h$0i_}-zrUdc8dpLa9XK)y}q zW5*LJUQAs|dXF^*$@r%8t%*zXmnOanex1=ZzsWZK&ez}CjUvkEmTZbAH^=RBRHWgd z@yXlGTO}!;@}lr()rB~4ZwesW0Zvrx$*s;>aAp?gz3FllrCO4mnU3rWQ)%)RrvrX3 zuw%EVWy4k2B|Mw#^le=iIggzNa1B0HW*tB#}8sMzVHPC#8?2_JWlw?-E8=|q4Oy2`AdlZBgE%$9o7ZP z;f~G)yF;_DpiMRNP|>pj^99<#4kaJkg zKlpiE=Y_hDtcGU=hhjY;>n8U&1-m_gbNI!GvEtgM&IR(5CCK$&Pd6IgL%Yk;YEr zUTh~k!=Z&LML0~uB2ww)yZGE4j}yjiI8mOn0OKYzWX+L3623&~m1Vudm-qUkX}IvrMJ(G~dv< zSh9CB>Y%J{E}Y`R%Yotbf(}v}Q$E7?L>QqN4q-WY09V6&cN?5|n;bnns-zTPg#2xi zm1=s>68r=IZB({teLZ-j!C-us*tA4h+94ZA6E}`TuZ2s|OIWPbKcz%O)gL;M@kVA= z?&3ejfontiEJE@;Vw4AN7tIkTwK;+;F#EJe>W?&-_EM=-^M7}#$#`8{TY^kC zz4tA~B^hNJ-&-|a Date: Tue, 4 May 2021 19:00:39 +1000 Subject: [PATCH 07/59] freertos: Add queue init overflow check Based on FreeRTOS kernel patch 47338393 but modified to work without assertions. --- components/freertos/freertos/queue.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/components/freertos/freertos/queue.c b/components/freertos/freertos/queue.c index c37d285f3..a87d6d62e 100644 --- a/components/freertos/freertos/queue.c +++ b/components/freertos/freertos/queue.c @@ -360,6 +360,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue; Queue_t *pxNewQueue; size_t xQueueSizeInBytes; uint8_t *pucQueueStorage; + BaseType_t overflow; configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); @@ -375,7 +376,29 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue; xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ } - pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); + /* Check for multiplication overflow. */ + overflow = ( uxItemSize != 0 ) && ( uxQueueLength != ( xQueueSizeInBytes / uxItemSize ) ); + + /* Check for addition overflow. */ + overflow = overflow || ( ( sizeof( Queue_t ) + xQueueSizeInBytes ) < xQueueSizeInBytes ); + + if ( overflow == (BaseType_t) 0 ) + { + /* Allocate the queue and storage area. Justification for MISRA + deviation as follows: pvPortMalloc() always ensures returned memory + blocks are aligned per the requirements of the MCU stack. In this case + pvPortMalloc() must return a pointer that is guaranteed to meet the + alignment requirements of the Queue_t structure - which in this case + is an int8_t *. Therefore, whenever the stack alignment requirements + are greater than or equal to the pointer to char requirements the cast + is safe. In other cases alignment requirements are not strict (one or + two bytes). */ + pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*lint !e9087 !e9079 see comment above. */ + } + else + { + pxNewQueue = NULL; + } if( pxNewQueue != NULL ) { From 91163a8e48112863b863ff3c983b1a13e1d584ff Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 4 May 2021 19:03:58 +1000 Subject: [PATCH 08/59] freertos: Add addition overflow check for stream buffer Patch from upstream commit d05b9c123f2bf9090bce386a244fc934ae44db5b --- components/freertos/freertos/stream_buffer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/freertos/freertos/stream_buffer.c b/components/freertos/freertos/stream_buffer.c index c60045f69..317be2355 100644 --- a/components/freertos/freertos/stream_buffer.c +++ b/components/freertos/freertos/stream_buffer.c @@ -242,8 +242,15 @@ static void prvInitialiseNewStreamBuffer( StreamBuffer_t * const pxStreamBuffer, this is a quirk of the implementation that means otherwise the free space would be reported as one byte smaller than would be logically expected. */ - xBufferSizeBytes++; - pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */ + if( xBufferSizeBytes < ( xBufferSizeBytes + 1 + sizeof( StreamBuffer_t ) ) ) + { + xBufferSizeBytes++; + pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */ + } + else + { + pucAllocatedMemory = NULL; + } if( pucAllocatedMemory != NULL ) { From f46ef19f496bc33697cde4dd9237048e9d169a9e Mon Sep 17 00:00:00 2001 From: chenwen Date: Fri, 7 May 2021 11:33:30 +0800 Subject: [PATCH 09/59] fix(sc): fix the issue of sending failure and exit 1. Send unicast first, use broadcast to send if unicast fails. 2. Send 60 times successfully before exiting. --- components/esp8266/source/smartconfig_ack.c | 35 ++++++++++--------- .../wifi/smart_config/main/Kconfig.projbuild | 4 +-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/components/esp8266/source/smartconfig_ack.c b/components/esp8266/source/smartconfig_ack.c index 7c4347470..4610cea0e 100644 --- a/components/esp8266/source/smartconfig_ack.c +++ b/components/esp8266/source/smartconfig_ack.c @@ -79,7 +79,7 @@ static void sc_ack_send_task(void* pvParameters) sc_ack_t* ack = (sc_ack_t*)pvParameters; tcpip_adapter_ip_info_t local_ip; uint8_t remote_ip[4]; - memset(remote_ip, 0xFF, sizeof(remote_ip)); + memcpy(remote_ip, ack->ctx.ip, sizeof(remote_ip)); struct sockaddr_in server_addr; socklen_t sin_size = sizeof(server_addr); int send_sock = -1; @@ -99,6 +99,7 @@ static void sc_ack_send_task(void* pvParameters) port_bit = 0; } remote_port = SC_ACK_TOUCH_V2_SERVER_PORT(port_bit); + memset(remote_ip, 0xFF, sizeof(remote_ip)); } else { remote_port = SC_ACK_AIRKISS_SERVER_PORT; } @@ -156,30 +157,32 @@ static void sc_ack_send_task(void* pvParameters) memcpy(remote_ip, &from.sin_addr, 4); server_addr.sin_addr.s_addr = from.sin_addr.s_addr; } else { - goto _end; + server_addr.sin_addr.s_addr = INADDR_BROADCAST; } } + uint32_t ip_addr = server_addr.sin_addr.s_addr; while (s_sc_ack_send) { /* Send smartconfig ACK every 100ms. */ vTaskDelay(100 / portTICK_RATE_MS); - sendlen = sendto(send_sock, &ack->ctx, ack_len, 0, (struct sockaddr*) &server_addr, sin_size); - - if (sendlen > 0) { - /* Totally send 60 smartconfig ACKs. Then smartconfig is successful. */ - if (packet_count++ >= SC_ACK_MAX_COUNT) { - esp_event_post(SC_EVENT, SC_EVENT_SEND_ACK_DONE, NULL, 0, portMAX_DELAY); - goto _end; - } + if (ip_addr != INADDR_BROADCAST) { + sendto(send_sock, &ack->ctx, ack_len, 0, (struct sockaddr*) &server_addr, sin_size); + server_addr.sin_addr.s_addr = INADDR_BROADCAST; + sendlen = sendto(send_sock, &ack->ctx, ack_len, 0, (struct sockaddr*) &server_addr, sin_size); + server_addr.sin_addr.s_addr = ip_addr; } else { - err = sc_ack_send_get_errno(send_sock); - - if (err == ENOMEM || err == EAGAIN) { - ESP_LOGD(TAG, "send failed, errno %d", err); - continue; - } + sendlen = sendto(send_sock, &ack->ctx, ack_len, 0, (struct sockaddr*) &server_addr, sin_size); + } + if (sendlen <= 0) { + err = sc_ack_send_get_errno(send_sock); ESP_LOGE(TAG, "send failed, errno %d", err); + vTaskDelay(200 / portTICK_RATE_MS); + } + + /* Send 60 smartconfig ACKs, exit regardless of failure or success. */ + if (packet_count++ >= SC_ACK_MAX_COUNT) { + esp_event_post(SC_EVENT, SC_EVENT_SEND_ACK_DONE, NULL, 0, portMAX_DELAY); goto _end; } } diff --git a/examples/wifi/smart_config/main/Kconfig.projbuild b/examples/wifi/smart_config/main/Kconfig.projbuild index 533a7140c..8d27ee8e0 100644 --- a/examples/wifi/smart_config/main/Kconfig.projbuild +++ b/examples/wifi/smart_config/main/Kconfig.projbuild @@ -12,7 +12,7 @@ config ESP_TOUCH bool "ESPTouch" config AIRKISS bool "AirKiss" -config ESP_TOUCH-AIRKISS +config ESP_TOUCH_AIRKISS bool "ESPTouch and AirKiss" config ESP_TOUCH_V2 bool "ESPTouch-V2" @@ -22,7 +22,7 @@ config ESP_SMARTCONFIG_TYPE int default 0 if ESP_TOUCH default 1 if AIRKISS - default 2 if ESP_TOUCH-AIRKISS + default 2 if ESP_TOUCH_AIRKISS default 3 if ESP_TOUCH_V2 endmenu From 611a94211ad8e959e03d825463a81df8b874fc68 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Tue, 18 May 2021 16:12:05 +0800 Subject: [PATCH 10/59] fix(lib): restart softap when phy mode change (backport v3.4) --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 484912 -> 484912 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 536888 -> 536888 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index b58432f71..578580090 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: cebfb0e - net80211: 9aeb2d7 + net80211: 8272d65 pp: 5fda74f espnow: 7a93d71 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index 03b36c2c4b406019f59d6daf39e439b82322d8e7..55f8b8e7579d272482ac6d231c012fcc052c0fad 100755 GIT binary patch delta 566 zcmdmRMs~v)*$L9@hQ>yQ7M8{vm2R5B*pvO9h;5cIe=Y_Sm|pjsQGBzkpPejRfl-x#)2PB2y5f${TF3C`Phh^=x0oCmb#lMnzS C?6pS# delta 566 zcmdmRMs~v)*$L9@1{Q{97N(XPm2R5B*pvO9h;5cIe=Y_Sm|pjsQGBzkpPejRfl-x#)2PB2y5f${TF3C`Phh^=x0oCmb#lMn!n Cd$yeb diff --git a/components/esp8266/lib/libnet80211_dbg.a b/components/esp8266/lib/libnet80211_dbg.a index c50d3d2976fc64321fc15b80fe76f76dbf7d7837..292cd8c46433373bb910f504d1ae7e07caaecd7f 100755 GIT binary patch delta 568 zcmdn-NMXk#g$dH^hQ>yQmIj6!m2R5B*wg=YGl_4OG=C-r6Pe66TWqt8U$`2Iz=V== zxWHz<*^RO&vTJr+mVyaPKlhPUeDmDP*A(C)dVN4UCVqJ|3oi0*J+t`siT#Xu#V{eD zwc^`lnVIyMVItc>PGg#$$-yLqEF{J>^#BW(KtV>OUcSDenGw`e(?OPOKgq>p#S2pn zcFA@{38p4RxDeRlgO*IwxM4!u<6M~{ZQ;CqO-yfDVLX_lWSFNv?M8KmCKF82cA(9P z-7wyCpmE~c*Ge+)V}}cYoz|(yysri(wB2tW^GrcF59q3=0WcoeCtE+VTxx)efI?#X z!$uarTW}$;nZ7ZsRa0R?+kpYEC&j2V{Vz9LJfl*3EDziESRQsJSC|4&%x2J1VA8 delta 568 zcmdn-NMXk#g$dH^1{Q{97G~xfm2R5B*wg=YGl_4OG=C-r6Pe66TWqt8U$`2Iz=V== zxWHz<*^RO&vTJr+mVyaPKlhPUeDmDP*A(C)dVN4UCVqJ|3oi0*J+t`siT#Xu#V{eD zwc^`lnVIyMVItc>PGg#$$-yLqEF{J>^#BW(KtV>OUcSDenGw`e(?OPOKgq>p#S2pn zcFA@{38p4RxDeRlgO*IwxM4!u<6M~{ZQ;CqO-yfDVLX_lWSFNv?M8KmCKF82cA(9P z-7wyCpmE~c*Ge+)V}}cYoz|(yysri(wB2tW^GrcF59q3=0WcoeCtE+VTxx)efI?#X z!$uarTW}$;nZ7ZsRa0R?+kpYEC&g$q{Vz9LJfl&2EDziESRQsJSC|4&%x2=0d2x From 5047c3af6d0054bbeafec5e1e3cde1d2c9c39982 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Fri, 21 May 2021 15:18:46 +0800 Subject: [PATCH 11/59] fix(wifi): fragment and forge vulnerability detection --- components/wpa_supplicant/src/common/wpa_common.h | 2 ++ components/wpa_supplicant/src/rsn_supp/wpa_ie.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/components/wpa_supplicant/src/common/wpa_common.h b/components/wpa_supplicant/src/common/wpa_common.h index f88e8a6fa..fd1d9995a 100644 --- a/components/wpa_supplicant/src/common/wpa_common.h +++ b/components/wpa_supplicant/src/common/wpa_common.h @@ -110,6 +110,8 @@ #define WPA_CAPABILITY_MFPR BIT(6) #define WPA_CAPABILITY_MFPC BIT(7) #define WPA_CAPABILITY_PEERKEY_ENABLED BIT(9) +#define WPA_CAPABILITY_SPP_CAPABLE BIT(10) +#define WPA_CAPABILITY_SPP_REQUIRED BIT(11) /* IEEE 802.11r */ diff --git a/components/wpa_supplicant/src/rsn_supp/wpa_ie.c b/components/wpa_supplicant/src/rsn_supp/wpa_ie.c index d648a4542..5ad24bb99 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa_ie.c +++ b/components/wpa_supplicant/src/rsn_supp/wpa_ie.c @@ -225,6 +225,9 @@ static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, } } #endif /* CONFIG_IEEE80211W */ + + capab |= WPA_CAPABILITY_SPP_CAPABLE; + WPA_PUT_LE16(pos, capab); pos += 2; From be11a5ab95cc635611cae0d7229cde1314272c58 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Fri, 21 May 2021 15:44:56 +0800 Subject: [PATCH 12/59] fix(lib): update lib --- components/esp8266/lib/VERSION | 8 ++++---- components/esp8266/lib/libcore.a | Bin 39398 -> 39742 bytes components/esp8266/lib/libcore_dbg.a | Bin 40306 -> 40650 bytes components/esp8266/lib/libespnow.a | Bin 38660 -> 38660 bytes components/esp8266/lib/libespnow_dbg.a | Bin 38660 -> 38660 bytes components/esp8266/lib/libnet80211.a | Bin 484912 -> 485864 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 536888 -> 537592 bytes components/esp8266/lib/libpp.a | Bin 247158 -> 249654 bytes components/esp8266/lib/libpp_dbg.a | Bin 264598 -> 267410 bytes 9 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 578580090..d6ed83b98 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,8 +1,8 @@ gwen: - core: cebfb0e - net80211: 8272d65 - pp: 5fda74f - espnow: 7a93d71 + core: db51cd0 + net80211: db51cd0 + pp: db51cd0 + espnow: db51cd0 smartconfig: 3.0.0/5f550c40 phy: 1163.0 diff --git a/components/esp8266/lib/libcore.a b/components/esp8266/lib/libcore.a index 75f2cd848a0f2f82db56e8b0dc0c4a5d37704af7..8dfeb8e2e7a7b52f5032f43349423889e79cb05a 100755 GIT binary patch delta 11013 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 SI1eH6GcefBpHSi3l>Y&)Q`Le1 delta 10561 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 Kr)go51^)}^;-K#U diff --git a/components/esp8266/lib/libcore_dbg.a b/components/esp8266/lib/libcore_dbg.a index c59cd3276841122a73f6d7481fe215a4ecb5b2eb..9aa92b8fdb52464fc5df08fe63afdf8b5c75939c 100755 GIT binary patch delta 11797 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 UJd8wt-lpWGp?(G&I^w|p1(gpbP5=M^ delta 11075 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 KHv5KccK`r#qY?)I delta 68 zcmZo!$JDZpX@V4oftjU+nWcf*Mx_#FIA`-b<`P3VZ}L25vCRgiY>JGAlNG~+nGH=1 KH~WTdcK`r-RT41( diff --git a/components/esp8266/lib/libespnow_dbg.a b/components/esp8266/lib/libespnow_dbg.a index e3c2f9d3ce32e14b62e3deeb8705d37fc9d22478..df26ea84217b8fa93ddd26b991229c4bdefae0ed 100755 GIT binary patch delta 68 zcmZo!$JDZpX@V4ok)f%Dfw{54Mx_#FIA`-b<`P3VZ}L25vCRgiY>JE~lNG~+nGH=% KHv5KccK`r!#1a7j delta 68 zcmZo!$JDZpX@V4oftjU+nWeGeMx_#FIA`-b<`P3VZ}L25vCRgiY>JGAlNG~+nGH=1 KH~WTdcK`r+b`mQ9 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index 55f8b8e7579d272482ac6d231c012fcc052c0fad..e2a7bb764755afe762e0957975fb531863dd9767 100755 GIT binary patch delta 48669 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

ieN-wb4E4Mu(Y37uoYx(K*ml#L))l!o~{UXHD~r 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 O{L9i^q0`fqgZ~e)<8X=q diff --git a/components/esp8266/lib/libnet80211_dbg.a b/components/esp8266/lib/libnet80211_dbg.a index 292cd8c46433373bb910f504d1ae7e07caaecd7f..464a0afb655fa2bed3bee5f52d3eaa3e5d4e5f0e 100755 GIT binary patch delta 53431 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 qXwX=FM}q-{cz$ZAH-T)l{ delta 53388 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 diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index aa59f60fd5f95b5d2039d38664bd642a2c66c5c7..ab5e5587ccb7218b59db78d18f87c81eb4d619d2 100755 GIT binary patch delta 30636 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%0Dw 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 pB`q9*H7MMZ1bZd5zCa9hM2-tHFi2Waql1VP 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 From 1d1b99557dec3f22efc8d42f440893eddcee072f Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Wed, 2 Jun 2021 16:02:57 +0800 Subject: [PATCH 13/59] fix(smartconfig): add timeout for airkiss sync --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libsmartconfig.a | Bin 158922 -> 157626 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index d6ed83b98..e62ac86d2 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -4,5 +4,5 @@ gwen: pp: db51cd0 espnow: db51cd0 - smartconfig: 3.0.0/5f550c40 + smartconfig: 3.0.0/703cbb6c phy: 1163.0 diff --git a/components/esp8266/lib/libsmartconfig.a b/components/esp8266/lib/libsmartconfig.a index ab3ccef63b505f3d2dadc8b764cb6531500762c0..3d434168789d904c1981f8093250319379d3a8fb 100755 GIT binary patch literal 157626 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 L496K2_4oe)+icC> literal 158922 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 CD)~_W From 186b2449b14efcb31a9800fa0e5afd86323bc22a Mon Sep 17 00:00:00 2001 From: Chen Wen Date: Sat, 15 May 2021 17:49:12 +0800 Subject: [PATCH 14/59] feat(sc): Support ESP-Touch-V2 to send unicast --- components/esp8266/source/smartconfig_ack.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/components/esp8266/source/smartconfig_ack.c b/components/esp8266/source/smartconfig_ack.c index 4610cea0e..34bd8eb30 100644 --- a/components/esp8266/source/smartconfig_ack.c +++ b/components/esp8266/source/smartconfig_ack.c @@ -31,6 +31,7 @@ #include "esp_wifi.h" #include "esp_smartconfig.h" #include "smartconfig_ack.h" +#include "lwip/inet.h" #define SC_ACK_TASK_PRIORITY 2 /*!< Priority of sending smartconfig ACK task */ #define SC_ACK_TASK_STACK_SIZE 2048 /*!< Stack size of sending smartconfig ACK task */ @@ -40,7 +41,7 @@ #define SC_ACK_TOUCH_V2_SERVER_PORT(i) (18266+i*10000) /*!< ESP touch_v2 UDP port of server on cellphone */ #define SC_ACK_AIRKISS_SERVER_PORT 10000 /*!< Airkiss UDP port of server on cellphone */ #define SC_ACK_AIRKISS_DEVICE_PORT 10001 /*!< Airkiss UDP port of server on device */ -#define SC_ACK_AIRKISS_TIMEOUT 1500 /*!< Airkiss read data timout millisecond */ +#define SC_ACK_TIMEOUT 1500 /*!< Airkiss and ESP touch_v2 read data timout millisecond */ #define SC_ACK_TOUCH_LEN 11 /*!< Length of ESP touch ACK context */ #define SC_ACK_AIRKISS_LEN 7 /*!< Length of Airkiss ACK context */ @@ -133,21 +134,24 @@ static void sc_ack_send_task(void* pvParameters) setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, &optval, sizeof(int)); - if (ack->type == SC_TYPE_AIRKISS) { + if (ack->type == SC_TYPE_AIRKISS || ack->type == SC_TYPE_ESPTOUCH_V2) { char data = 0; struct sockaddr_in local_addr, from; socklen_t sockadd_len = sizeof(struct sockaddr); struct timeval timeout = { - SC_ACK_AIRKISS_TIMEOUT / 1000, - SC_ACK_AIRKISS_TIMEOUT % 1000 * 1000 + SC_ACK_TIMEOUT / 1000, + SC_ACK_TIMEOUT % 1000 * 1000 }; bzero(&local_addr, sizeof(struct sockaddr_in)); bzero(&from, sizeof(struct sockaddr_in)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = INADDR_ANY; - local_addr.sin_port = htons(SC_ACK_AIRKISS_DEVICE_PORT); - + if (ack->type == SC_TYPE_AIRKISS) { + local_addr.sin_port = htons(SC_ACK_AIRKISS_DEVICE_PORT); + } else { + local_addr.sin_port = htons(SC_ACK_TOUCH_DEVICE_PORT); + } bind(send_sock, (struct sockaddr*)&local_addr, sockadd_len); setsockopt(send_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); @@ -156,6 +160,7 @@ static void sc_ack_send_task(void* pvParameters) if (from.sin_addr.s_addr != INADDR_ANY) { memcpy(remote_ip, &from.sin_addr, 4); server_addr.sin_addr.s_addr = from.sin_addr.s_addr; + ESP_LOGI(TAG, "cellphone_ip: %s", inet_ntoa(server_addr.sin_addr)); } else { server_addr.sin_addr.s_addr = INADDR_BROADCAST; } From ba8b38ba7d4d761ca24859951d2d9352a9cd8812 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 26 Jul 2021 11:59:35 +0800 Subject: [PATCH 15/59] chore(ci): Modify SSC branch for CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83b515304..f884330ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,7 +48,7 @@ build_ssc: - git clone $GITLAB_SSH_SERVER/yinling/SSC.git - cd SSC # try checkout same branch - - git checkout "${CI_BUILD_REF_NAME}_8266" || echo "Using default branch..." + - git checkout "release/v3.4_8266" || echo "Using default branch..." - ./gen_misc_rtos.sh push_master_to_github: From 97d0c2a4fbce99ebe4da0e9f298e30a9c1f19824 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Fri, 23 Jul 2021 17:05:40 +0800 Subject: [PATCH 16/59] feat(nvs_flash): Bring nvs_flash from esp-idf Commit ID: ecd2c51 --- components/nvs_flash/CMakeLists.txt | 53 +- components/nvs_flash/Kconfig | 12 + components/nvs_flash/README.rst | 234 -- components/nvs_flash/component.mk | 4 +- .../host_test/fixtures/test_fixtures.hpp | 429 +++ .../host_test/nvs_page_test/CMakeLists.txt | 27 + .../host_test/nvs_page_test/README.rst | 23 + .../nvs_page_test/main/CMakeLists.txt | 10 + .../nvs_page_test/main/nvs_page_test.cpp | 934 ++++++ .../nvs_page_test/sdkconfig.defaults | 3 + components/nvs_flash/include/nvs.h | 488 ++- components/nvs_flash/include/nvs_flash.h | 158 +- components/nvs_flash/include/nvs_handle.hpp | 280 ++ .../{test_nvs_host => mock/int}/crc.cpp | 5 +- .../{test_nvs_host => mock/int}/crc.h | 6 +- .../nvs_partition_generator/README.rst | 308 +- .../nvs_partition_generator/README_CN.rst | 304 ++ .../nvs_partition_gen.py | 823 +++-- .../sample_multipage_blob.csv | 1 + .../sample_singlepage_blob.csv | 1 + .../nvs_flash/src/compressed_enum_table.hpp | 4 +- components/nvs_flash/src/intrusive_list.h | 14 +- components/nvs_flash/src/nvs_api.cpp | 670 ++-- components/nvs_flash/src/nvs_cxx_api.cpp | 68 + .../nvs_flash/src/nvs_encrypted_partition.cpp | 121 + .../nvs_flash/src/nvs_encrypted_partition.hpp | 43 + .../nvs_flash/src/nvs_handle_locked.cpp | 82 + .../nvs_flash/src/nvs_handle_locked.hpp | 66 + .../nvs_flash/src/nvs_handle_simple.cpp | 137 + .../nvs_flash/src/nvs_handle_simple.hpp | 107 + .../nvs_flash/src/nvs_item_hash_list.cpp | 28 +- .../nvs_flash/src/nvs_item_hash_list.hpp | 8 +- components/nvs_flash/src/nvs_page.cpp | 319 +- components/nvs_flash/src/nvs_page.hpp | 52 +- components/nvs_flash/src/nvs_pagemanager.cpp | 53 +- components/nvs_flash/src/nvs_pagemanager.hpp | 15 +- components/nvs_flash/src/nvs_partition.cpp | 77 + components/nvs_flash/src/nvs_partition.hpp | 115 + .../nvs_flash/src/nvs_partition_lookup.cpp | 69 + .../nvs_flash/src/nvs_partition_lookup.hpp | 22 + .../nvs_flash/src/nvs_partition_manager.cpp | 244 ++ .../nvs_flash/src/nvs_partition_manager.hpp | 62 + components/nvs_flash/src/nvs_platform.hpp | 35 +- components/nvs_flash/src/nvs_storage.cpp | 576 +++- components/nvs_flash/src/nvs_storage.hpp | 75 +- components/nvs_flash/src/nvs_test_api.h | 22 +- components/nvs_flash/src/nvs_types.cpp | 19 +- components/nvs_flash/src/nvs_types.hpp | 64 +- components/nvs_flash/src/partition.hpp | 59 + components/nvs_flash/test/CMakeLists.txt | 4 + components/nvs_flash/test/component.mk | 1 + components/nvs_flash/test/encryption_keys.bin | 1 + .../nvs_flash/test/partition_encrypted.bin | Bin 0 -> 8192 bytes components/nvs_flash/test/sample.bin | 1 + components/nvs_flash/test/test_nvs.c | 495 ++- components/nvs_flash/test_nvs_host/Makefile | 63 +- components/nvs_flash/test_nvs_host/README.md | 22 + .../test_nvs_host/esp_error_check_stub.cpp | 7 +- .../nvs_flash/test_nvs_host/sdkconfig.h | 3 + .../test_nvs_host/spi_flash_emulation.cpp | 61 +- .../test_nvs_host/spi_flash_emulation.h | 36 +- .../test_compressed_enum_table.cpp | 2 +- .../nvs_flash/test_nvs_host/test_fixtures.hpp | 149 + .../test_nvs_host/test_intrusive_list.cpp | 7 +- .../nvs_flash/test_nvs_host/test_nvs.cpp | 2777 +++++++++++++++-- .../test_nvs_host/test_nvs_cxx_api.cpp | 193 ++ .../test_nvs_host/test_nvs_handle.cpp | 281 ++ .../test_nvs_host/test_nvs_initialization.cpp | 46 + .../test_nvs_host/test_nvs_partition.cpp | 55 + .../test_nvs_host/test_nvs_storage.cpp | 60 + .../test_nvs_host/test_partition_manager.cpp | 91 + .../test_spi_flash_emulation.cpp | 189 +- 72 files changed, 10427 insertions(+), 1446 deletions(-) create mode 100644 components/nvs_flash/Kconfig delete mode 100644 components/nvs_flash/README.rst create mode 100644 components/nvs_flash/host_test/fixtures/test_fixtures.hpp create mode 100644 components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt create mode 100644 components/nvs_flash/host_test/nvs_page_test/README.rst create mode 100644 components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt create mode 100644 components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp create mode 100644 components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults create mode 100644 components/nvs_flash/include/nvs_handle.hpp rename components/nvs_flash/{test_nvs_host => mock/int}/crc.cpp (97%) rename components/nvs_flash/{test_nvs_host => mock/int}/crc.h (72%) create mode 100644 components/nvs_flash/nvs_partition_generator/README_CN.rst create mode 100644 components/nvs_flash/src/nvs_cxx_api.cpp create mode 100644 components/nvs_flash/src/nvs_encrypted_partition.cpp create mode 100644 components/nvs_flash/src/nvs_encrypted_partition.hpp create mode 100644 components/nvs_flash/src/nvs_handle_locked.cpp create mode 100644 components/nvs_flash/src/nvs_handle_locked.hpp create mode 100644 components/nvs_flash/src/nvs_handle_simple.cpp create mode 100644 components/nvs_flash/src/nvs_handle_simple.hpp create mode 100644 components/nvs_flash/src/nvs_partition.cpp create mode 100644 components/nvs_flash/src/nvs_partition.hpp create mode 100644 components/nvs_flash/src/nvs_partition_lookup.cpp create mode 100644 components/nvs_flash/src/nvs_partition_lookup.hpp create mode 100644 components/nvs_flash/src/nvs_partition_manager.cpp create mode 100644 components/nvs_flash/src/nvs_partition_manager.hpp create mode 100644 components/nvs_flash/src/partition.hpp create mode 100644 components/nvs_flash/test/CMakeLists.txt create mode 100644 components/nvs_flash/test/encryption_keys.bin create mode 100644 components/nvs_flash/test/partition_encrypted.bin create mode 100644 components/nvs_flash/test/sample.bin create mode 100644 components/nvs_flash/test_nvs_host/README.md create mode 100644 components/nvs_flash/test_nvs_host/test_fixtures.hpp create mode 100644 components/nvs_flash/test_nvs_host/test_nvs_cxx_api.cpp create mode 100644 components/nvs_flash/test_nvs_host/test_nvs_handle.cpp create mode 100644 components/nvs_flash/test_nvs_host/test_nvs_initialization.cpp create mode 100644 components/nvs_flash/test_nvs_host/test_nvs_partition.cpp create mode 100644 components/nvs_flash/test_nvs_host/test_nvs_storage.cpp create mode 100644 components/nvs_flash/test_nvs_host/test_partition_manager.cpp diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index b36e6ba52..7ce2871c1 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -1,6 +1,51 @@ -set(COMPONENT_SRCDIRS "src") -set(COMPONENT_ADD_INCLUDEDIRS "include") +idf_build_get_property(target IDF_TARGET) -set(COMPONENT_PRIV_REQUIRES "spi_flash") +set(srcs "src/nvs_api.cpp" + "src/nvs_cxx_api.cpp" + "src/nvs_item_hash_list.cpp" + "src/nvs_page.cpp" + "src/nvs_pagemanager.cpp" + "src/nvs_storage.cpp" + "src/nvs_handle_simple.cpp" + "src/nvs_handle_locked.cpp" + "src/nvs_partition.cpp" + "src/nvs_partition_lookup.cpp" + "src/nvs_partition_manager.cpp" + "src/nvs_types.cpp") -register_component() +set(public_req spi_flash) + +set(include_dirs "include") + +idf_component_register(SRCS "${srcs}" + REQUIRES "${public_req}" + INCLUDE_DIRS "${include_dirs}") + +# If we use the linux target, we need to redirect the crc functions to the linux +if(${target} STREQUAL "linux") + if(CONFIG_NVS_ENCRYPTION) + # mbedtls isn't configured for building with linux or as mock target. It will draw in all kind of dependencies + message(FATAL_ERROR "NVS currently doesn't support encryption if built for Linux.") + endif() + idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR) + target_include_directories(${COMPONENT_LIB} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/mock/int" + "${spi_flash_dir}/sim/stubs/freertos/include") + target_sources(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/mock/int/crc.cpp") + target_compile_options(${COMPONENT_LIB} PUBLIC "-DLINUX_TARGET") +else() + # TODO: this is a workaround until IDF-2085 is fixed + idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib}) +endif() + +if(CONFIG_NVS_ENCRYPTION) + target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp") + idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib}) +endif() + +if(${target} STREQUAL "linux") + target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) + target_link_libraries(${COMPONENT_LIB} PUBLIC --coverage) +endif() diff --git a/components/nvs_flash/Kconfig b/components/nvs_flash/Kconfig new file mode 100644 index 000000000..98cf97987 --- /dev/null +++ b/components/nvs_flash/Kconfig @@ -0,0 +1,12 @@ +menu "NVS" + + config NVS_ENCRYPTION + bool "Enable NVS encryption" + default y + depends on SECURE_FLASH_ENC_ENABLED + help + This option enables encryption for NVS. When enabled, AES-XTS is used to encrypt + the complete NVS data, except the page headers. It requires XTS encryption keys + to be stored in an encrypted partition. This means enabling flash encryption is + a pre-requisite for this feature. +endmenu diff --git a/components/nvs_flash/README.rst b/components/nvs_flash/README.rst deleted file mode 100644 index f04ac592c..000000000 --- a/components/nvs_flash/README.rst +++ /dev/null @@ -1,234 +0,0 @@ -Non-volatile storage library -============================ - -Introduction ------------- - -Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This sections introduces some concepts used by NVS. - -Underlying storage -^^^^^^^^^^^^^^^^^^ - -Currently NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The library uses the all the partitions with ``data`` type and ``nvs`` subtype. The application can choose to use the partition with label ``nvs`` through ``nvs_open`` API or any of the other partition by specifying its name through ``nvs_open_from_part`` API. - -Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc. - -.. note:: if an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``make erase_flash`` target to erase all contents of the flash chip. - -.. note:: NVS works best for storing many small values, rather than a few large values of type 'string' and 'blob'. If storing large blobs or strings is required, consider using the facilities provided by the FAT filesystem on top of the wear levelling library. - -Keys and values -^^^^^^^^^^^^^^^ - -NVS operates on key-value pairs. Keys are ASCII strings, maximum key length is currently 15 characters. Values can have one of the following types: - -- integer types: ``uint8_t``, ``int8_t``, ``uint16_t``, ``int16_t``, ``uint32_t``, ``int32_t``, ``uint64_t``, ``int64_t`` -- zero-terminated string -- variable length binary data (blob) - -.. note:: - String and blob values are currently limited to 1984 bytes. For strings, this includes the null terminator. - -Additional types, such as ``float`` and ``double`` may be added later. - -Keys are required to be unique. Writing a value for a key which already exists behaves as follows: - -- if the new value is of the same type as old one, value is updated -- if the new value has different data type, an error is returned - -Data type check is also performed when reading a value. An error is returned if data type of read operation doesn’t match the data type of the value. - -Namespaces -^^^^^^^^^^ - -To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e. 15 character maximum length. Namespace name is specified in the ``nvs_open`` or ``nvs_open_from_part`` call. This call returns an opaque handle, which is used in subsequent calls to ``nvs_read_*``, ``nvs_write_*``, and ``nvs_commit`` functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces. -Please note that the namespaces with same name in different NVS partitions are considered as separate namespaces. - -Security, tampering, and robustness -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -NVS library doesn't implement tamper prevention measures. It is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. - -NVS is compatible with the ESP32 flash encryption system, and it can store key-value pairs in an encrypted form. Some metadata, like page state and write/erase flags of individual entries can not be encrypted as they are represented as bits of flash memory for efficient access and manipulation. Flash encryption can prevent some forms of modification: - -- replacing keys or values with arbitrary data -- changing data types of values - -The following forms of modification are still possible when flash encryption is used: - -- erasing a page completely, removing all key-value pairs which were stored in that page -- corrupting data in a page, which will cause the page to be erased automatically when such condition is detected -- rolling back the contents of flash memory to an earlier snapshot -- merging two snapshots of flash memory, rolling back some key-value pairs to an earlier state (although this is possible to mitigate with the current design — TODO) - -The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, expect for the new key-value pair if it was being written at the moment of power off. The library should also be able to initialize properly with any random data present in flash memory. - -Internals ---------- - -Log of key-value pairs -^^^^^^^^^^^^^^^^^^^^^^ - -NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, new key-value pair is added at the end of the log and old key-value pair is marked as erased. - -Pages and entries -^^^^^^^^^^^^^^^^^ - -NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states: - -Empty/uninitialized - Flash storage for the page is empty (all bytes are ``0xff``). Page isn't used to store any data at this point and doesn’t have a sequence number. - -Active - Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. At most one page can be in this state at any given moment. - -Full - Flash storage is in a consistent state and is filled with key-value pairs. - Writing new key-value pairs into this page is not possible. It is still possible to mark some key-value pairs as erased. - -Erasing - Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e. page should never stay in this state when any API call returns. In case of a sudden power off, move-and-erase process will be completed upon next power on. - -Corrupted - Page header contains invalid data, and further parsing of page data was canceled. Any items previously written into this page will not be accessible. Corresponding flash sector will not be erased immediately, and will be kept along with sectors in *uninitialized* state for later use. This may be useful for debugging. - -Mapping from flash sectors to logical pages doesn't have any particular order. Library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers. - -:: - - +--------+ +--------+ +--------+ +--------+ - | Page 1 | | Page 2 | | Page 3 | | Page 4 | - | Full +---> | Full +---> | Active | | Empty | <- states - | #11 | | #12 | | #14 | | | <- sequence numbers - +---+----+ +----+---+ +----+---+ +---+----+ - | | | | - | | | | - | | | | - +---v------+ +-----v----+ +------v---+ +------v---+ - | Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- physical sectors - +----------+ +----------+ +----------+ +----------+ - -Structure of a page -^^^^^^^^^^^^^^^^^^^ - -For now we assume that flash sector size is 4096 bytes and that ESP32 flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g. via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g. SPI flash driver and SPI flash cache can support these other sizes). - -Page consists of three parts: header, entry state bitmap, and entries themselves. To be compatible with ESP32 flash encryption, entry size is 32 bytes. For integer types, entry holds one key-value pair. For strings and blobs, an entry holds part of key-value pair (more on that in the entry structure description). - -The following diagram illustrates page structure. Numbers in parentheses indicate size of each part in bytes. :: - - +-----------+--------------+-------------+-----------+ - | State (4) | Seq. no. (4) | Unused (20) | CRC32 (4) | Header (32) - +-----------+--------------+-------------+-----------+ - | Entry state bitmap (32) | - +----------------------------------------------------+ - | Entry 0 (32) | - +----------------------------------------------------+ - | Entry 1 (32) | - +----------------------------------------------------+ - / / - / / - +----------------------------------------------------+ - | Entry 125 (32) | - +----------------------------------------------------+ - -Page header and entry state bitmap are always written to flash unencrypted. Entries are encrypted if flash encryption feature of the ESP32 is used. - -Page state values are defined in such a way that changing state is possible by writing 0 into some of the bits. Therefore it not necessary to erase the page to change page state, unless that is a change to *erased* state. - -CRC32 value in header is calculated over the part which doesn't include state value (bytes 4 to 28). Unused part is currently filled with ``0xff`` bytes. Future versions of the library may store format version there. - -The following sections describe structure of entry state bitmap and entry itself. - -Entry and entry state bitmap -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Each entry can be in one of the following three states. Each state is represented with two bits in the entry state bitmap. Final four bits in the bitmap (256 - 2 * 126) are unused. - -Empty (2'b11) - Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes ``0xff``). - -Written (2'b10) - A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry. - -Erased (2'b00) - A key-value pair in this entry has been discarded. Contents of this entry will not be parsed anymore. - - -Structure of entry -^^^^^^^^^^^^^^^^^^ - -For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. In case when a key-value pair spans multiple entries, all entries are stored in the same page. - -:: - - +--------+----------+----------+---------+-----------+---------------+----------+ - | NS (1) | Type (1) | Span (1) | Rsv (1) | CRC32 (4) | Key (16) | Data (8) | - +--------+----------+----------+---------+-----------+---------------+----------+ - - +--------------------------------+ - +-> Fixed length: | Data (8) | - | +--------------------------------+ - Data format ---+ - | +----------+---------+-----------+ - +-> Variable length: | Size (2) | Rsv (2) | CRC32 (4) | - +----------+---------+-----------+ - - -Individual fields in entry structure have the following meanings: - -NS - Namespace index for this entry. See section on namespaces implementation for explanation of this value. - -Type - One byte indicating data type of value. See ``ItemType`` enumeration in ``nvs_types.h`` for possible values. - -Span - Number of entries used by this key-value pair. For integer types, this is equal to 1. For strings and blobs this depends on value length. - -Rsv - Unused field, should be ``0xff``. - -CRC32 - Checksum calculated over all the bytes in this entry, except for the CRC32 field itself. - -Key - Zero-terminated ASCII string containing key name. Maximum string length is 15 bytes, excluding zero terminator. - -Data - For integer types, this field contains the value itself. If the value itself is shorter than 8 bytes it is padded to the right, with unused bytes filled with ``0xff``. For string and blob values, these 8 bytes hold additional data about the value, described next: - -Size - (Only for strings and blobs.) Size, in bytes, of actual data. For strings, this includes zero terminator. - -CRC32 - (Only for strings and blobs.) Checksum calculated over all bytes of data. - -Variable length values (strings and blobs) are written into subsequent entries, 32 bytes per entry. `Span` field of the first entry indicates how many entries are used. - - -Namespaces -^^^^^^^^^^ - -As mentioned above, each key-value pair belongs to one of the namespaces. Namespaces identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces. - -:: - - +-------------------------------------------+ - | NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi" - +-------------------------------------------+ - | NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi" - +-------------------------------------------+ - | NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm" - +-------------------------------------------+ - | NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm" - +-------------------------------------------+ - - -Item hash list -^^^^^^^^^^^^^^ - -To reduce the number of reads performed from flash memory, each member of Page class maintains a list of pairs: (item index; item hash). This list makes searches much quicker. Instead of iterating over all entries, reading them from flash one at a time, ``Page::findItem`` first performs search for item hash in the hash list. This gives the item index within the page, if such an item exists. Due to a hash collision it is possible that a different item will be found. This is handled by falling back to iteration over items in flash. - -Each node in hash list contains a 24-bit hash and 8-bit item index. Hash is calculated based on item namespace and key name. CRC32 is used for calculation, result is truncated to 24 bits. To reduce overhead of storing 32-bit entries in a linked list, list is implemented as a doubly-linked list of arrays. Each array holds 29 entries, for the total size of 128 bytes, together with linked list pointers and 32-bit count field. Minimal amount of extra RAM useage per page is therefore 128 bytes, maximum is 640 bytes. - diff --git a/components/nvs_flash/component.mk b/components/nvs_flash/component.mk index 20b0ec407..8b2fda4f5 100755 --- a/components/nvs_flash/component.mk +++ b/components/nvs_flash/component.mk @@ -6,4 +6,6 @@ COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_SRCDIRS := src -CPPFLAGS += -DNVS_CRC_HEADER_FILE=\"rom/crc.h\" +ifndef CONFIG_NVS_ENCRYPTION +COMPONENT_OBJEXCLUDE := src/nvs_encr.o +endif diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp new file mode 100644 index 000000000..06b12ce17 --- /dev/null +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -0,0 +1,429 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nvs_partition.hpp" +#include "nvs.h" +#include "nvs_page.hpp" +#include "nvs_storage.hpp" +#include +#include + +#ifdef CONFIG_NVS_ENCRYPTION +#include "nvs_encrypted_partition.hpp" +#endif + +extern "C" { +#include "Mockesp_partition.h" +} + +struct FixtureException : std::exception { + FixtureException(const std::string& msg) : msg(msg) { } + + const char *what() { + return msg.c_str(); + } + + std::string msg; +}; + +class PartitionMock : public nvs::Partition { +public: + PartitionMock(uint32_t address, uint32_t size) + : partition(), address(address), size(size) + { + assert(size); + } + + const char *get_partition_name() override + { + return ""; + } + + esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override + { + return esp_partition_read_raw(&partition, src_offset, dst, size); + } + + esp_err_t read(size_t src_offset, void* dst, size_t size) override + { + return esp_partition_read(&partition, src_offset, dst, size); + } + + esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override + { + return esp_partition_write_raw(&partition, dst_offset, src, size); + } + + esp_err_t write(size_t dst_offset, const void* src, size_t size) override + { + return esp_partition_write(&partition, dst_offset, src, size); + } + + esp_err_t erase_range(size_t dst_offset, size_t size) override + { + return esp_partition_erase_range(&partition, dst_offset, size); + } + + uint32_t get_address() override + { + return address; + } + + uint32_t get_size() override + { + return size; + } + + const esp_partition_t partition; + +private: + uint32_t address; + + uint32_t size; +}; + +#ifdef CONFIG_NVS_ENCRYPTION +struct EncryptedPartitionFixture { + EncryptedPartitionFixture(nvs_sec_cfg_t *cfg, + uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : esp_partition(), emu(start_sector + sector_size), + part(partition_name, &esp_partition) { + esp_partition.address = start_sector * SPI_FLASH_SEC_SIZE; + esp_partition.size = sector_size * SPI_FLASH_SEC_SIZE; + assert(part.init(cfg) == ESP_OK); + } + + ~EncryptedPartitionFixture() { } + + esp_partition_t esp_partition; + + SpiFlashEmulator emu; + + nvs::NVSEncryptedPartition part; +}; +#endif + +struct PartitionMockFixture { + PartitionMockFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : part_mock(start_sector * SPI_FLASH_SEC_SIZE, sector_size * SPI_FLASH_SEC_SIZE) { + std::fill_n(raw_header, sizeof(raw_header)/sizeof(raw_header[0]), UINT8_MAX); + } + + ~PartitionMockFixture() { } + + uint8_t raw_header[512]; + + PartitionMock part_mock; +}; + +struct NVSPageFixture : public PartitionMockFixture { + NVSPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), page() + { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 32); + + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSValidPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t value_entry [32]; + + NVSValidPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l', + 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0xfa; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // read next free entry's header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + // read normal entry second time during duplicated entry check + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSValidStorageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + uint8_t ns_entry [32]; + + uint8_t empty_entry [32]; + + NVSValidStorageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 3, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + empty_entry(), + storage(&part_mock) + { + std::fill_n(empty_entry, sizeof(empty_entry)/sizeof(empty_entry[0]), 0xFF); + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t header_full_page [] = { + 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}; + + uint8_t header_second_page [] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + uint8_t header_third_page [] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + // entry_table with all elements deleted except the namespace entry written and the last entry free + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0x02; + raw_entry_table[31] = 0xFC; + + // read full page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_full_page, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // reading entry table checks empty entry + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(empty_entry, 32); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read last two pages' headers, which trigger an automatic full read each because each page is empty + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_second_page, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_third_page, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + + // read namespace entry in duplicated header item check of pagemanager::load + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // storage finally actually reads namespace + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // storage looks for blob index entries + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // Storage::eraseOrphanDataBlobs() also wants to take it's turn... + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + if (storage.init(start_sector, sector_size) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Storage storage; +}; + +struct NVSValidBlobPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + const static size_t BLOB_DATA_SIZE = 32; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t blob_entry [32]; + uint8_t blob_data [BLOB_DATA_SIZE]; + uint8_t blob_index [32]; + + NVSValidBlobPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + blob_entry {0x01, 0x42, 0x02, 0x00, 0xaa, 0xf3, 0x23, 0x87, 't', 'e', 's', 't', '_', 'b', 'l', 'o', + 'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0xff, 0xff, 0xc6, 0x96, 0x86, 0xd9}, + blob_data {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, + blob_index {0x01, 0x48, 0x01, 0xff, 0x42, 0x6b, 0xdf, 0x66, 't', 'e', 's', 't', '_', 'b', 'l', 'o', + 'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0xFF); + raw_entry_table[0] = 0xaa; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // read next free entry's header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal blob entry + index, not the data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_index, 32); + + // read normal entry second time during duplicated entry check + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32); + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSFullPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t value_entry [32]; + + NVSFullPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME, + bool load = true) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfc, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa3, 0x48, 0x9f, 0x38}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l', + 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0xfa; + + // entry_table with all elements deleted except the namespace entry written and the last entry free + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0x0a; + raw_entry_table[31] = 0xFC; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // no next free entry check, only one entry written + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + // no duplicated entry check + + if (load) { + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + } + + nvs::Page page; +}; diff --git a/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt b/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt new file mode 100644 index 000000000..cafc7c699 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +idf_build_set_property(CONFIG_SPI_FLASH_MOCK 1) +idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) +project(host_nvs_page_test) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND lcov --capture --directory . --output-file coverage.info + COMMENT "Create coverage report" + ) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND genhtml coverage.info --output-directory coverage_report/ + COMMENT "Turn coverage report into html-based visualization" + ) + +add_custom_target(coverage + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + DEPENDS "coverage_report/" + ) diff --git a/components/nvs_flash/host_test/nvs_page_test/README.rst b/components/nvs_flash/host_test/nvs_page_test/README.rst new file mode 100644 index 000000000..c005b3f48 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/README.rst @@ -0,0 +1,23 @@ +NVS Page Test for Host +====================== + +Build +----- + +First, make sure that the target is set to linux. +Run ``idf.py --preview set-target linux`` to be sure. +Then do a normal IDF build: ``idf.py build``. + +Run +--- + +IDF monitor doesn't work yet for Linux. +You have to run the app manually: ``./build/host_nvs_page_test.elf``. + +Coverage +--- + +To generate the coverage, run: ``idf.py coverage``. +Afterwards, you can view the coverage by opening ``build/coverage_report/index.html`` with your browser. +Note that you need to run the application at least once before generating the coverage information. +If you run it multiple times, the coverage information adds up. diff --git a/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt b/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt new file mode 100644 index 000000000..d2af1b6a6 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register(SRCS "nvs_page_test.cpp" + INCLUDE_DIRS + "." + "${CMAKE_CURRENT_SOURCE_DIR}/../../fixtures" + "${CMAKE_CURRENT_SOURCE_DIR}/../../../test_nvs_host" + "${CMAKE_CURRENT_SOURCE_DIR}/../../../src" + REQUIRES cmock nvs_flash spi_flash) + +target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) +target_link_libraries(${COMPONENT_LIB} --coverage) diff --git a/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp new file mode 100644 index 000000000..d3899be78 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp @@ -0,0 +1,934 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "unity.h" +#include "test_fixtures.hpp" + +extern "C" { +#include "Mockesp_partition.h" +} + +using namespace std; +using namespace nvs; + +void test_Page_load_reading_header_fails() +{ + PartitionMock mock(0, 4096); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_ARG); + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, page.load(&mock, 0)); +} + +void test_Page_load_reading_data_fails() +{ + uint8_t header[64]; + std::fill_n(header, sizeof(header)/sizeof(header[0]), UINT8_MAX); + PartitionMock mock(0, 4096); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header, 32); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_FAIL); + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_FAIL, page.load(&mock, 0)); +} + +void test_Page_load__uninitialized_page_has_0xfe() +{ + PartitionMockFixture fix; + Page page; + + fix.raw_header[511] = 0xfe; + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); +} + +void test_Page_load__initialized_corrupt_header() +{ + PartitionMockFixture fix; + Page page; + + uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc}; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); +} + +void test_Page_load_success() +{ + PartitionMockFixture fix; + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + } + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state()); +} + +void test_Page_load_full_page() +{ + NVSFullPageFixture fix(0, 1, NVS_DEFAULT_PART_NAME, false); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); + TEST_ASSERT_EQUAL(ESP_OK, fix.page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state()); +} +void test_Page_load__seq_number_0() +{ + NVSValidPageFixture fix; + + uint32_t seq_num; + fix.page.getSeqNumber(seq_num); + TEST_ASSERT_EQUAL(0, seq_num); +} + +void test_Page_erase__write_fail() +{ + NVSValidPageFixture fix; + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.erase()); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_erase__success() +{ + NVSValidPageFixture fix; + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.erase()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_write__initialize_write_failure() +{ + PartitionMockFixture fix; + uint8_t write_data = 47; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + } + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_FAIL); + + Page page; + + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state()); + + TEST_ASSERT_EQUAL(ESP_FAIL, page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data))); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); +} + +void test_Page_write__write_data_fails() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_write_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_page_write__write_correct_entry_state() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + uint8_t raw_result [4]; + std::fill_n(raw_result, sizeof(raw_result)/sizeof(raw_result[0]), UINT8_MAX); + // mark first entry as written + raw_result[0] = 0xfe; + + // initialize page + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry + esp_partition_write_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry state + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_write__write_correct_data() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + uint8_t raw_result [32] = {0x01, 0x01, 0x01, 0xff, 0x98, 0x6f, 0x21, 0xfd, 't', 'e', 's', 't', '_', 'k', 'e', 'y', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + // initialize page + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry + esp_partition_write_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 64, raw_result, 32, 32, ESP_OK); + + // write entry state + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__read_entry_fails() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t read_value = 0; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); +} + +void test_Page_readItem__read_corrupted_entry() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); +} + +void test_Page_readItem__read_corrupted_second_read_fail() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_readItem__read_corrupted_erase_fail() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_readItem__read_entry_suceeds() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t read_value = 0; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(47, read_value); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_read_data_fails() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__corrupt_data_erase_failure() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + fix.blob_data[16] = 0xdf; + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_corrupt_data() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + fix.blob_data[16] = 0xdf; + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xa2, 0xff, 0xff, 0xff}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 96, 64, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(3, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(1, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_read_entry_suceeds() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL_MEMORY(fix.blob_data, read_data, fix.BLOB_DATA_SIZE); + + // make sure nothing was erased, i.e. checksums matched + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_cmp__uninitialized() +{ + Page page; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, page.cmpItem(uint8_t(1) , "test", 47)); +} + +void test_Page_cmp__item_not_found() +{ + NVSValidPageFixture fix; + + // no expectations here since comparison uses the item hash list + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.cmpItem(uint8_t(1), "different", 47)); +} + +void test_Page_cmp__item_type_mismatch() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, fix.page.cmpItem(uint8_t(1), "test_value", int(47))); +} + +void test_Page_cmp__item_content_mismatch() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS, fix.page.cmpItem(uint8_t(1), "test_value", uint8_t(46))); +} + +void test_Page_cmp__item_content_match() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.cmpItem(NVSValidPageFixture::NS_INDEX, "test_value", uint8_t(47))); +} + +void test_Page_cmpItem__blob_data_mismatch() +{ + NVSValidBlobPageFixture fix; + + // read blob entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + + // read blob data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + + uint8_t blob_data_different [] = + {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xee}; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS, + fix.page.cmpItem(uint8_t(1), + ItemType::BLOB_DATA, + "test_blob", + blob_data_different, + 32)); +} + +void test_Page_cmpItem__blob_data_match() +{ + NVSValidBlobPageFixture fix; + + // read blob entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + + // read blob data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + + uint8_t blob_data_same [] = + {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}; + + TEST_ASSERT_EQUAL(ESP_OK, + fix.page.cmpItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + blob_data_same, + 32)); +} + +void test_Page_eraseItem__uninitialized() +{ + Page page; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); +} + +void test_Page_eraseItem__key_not_found() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "different")); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_eraseItem__write_fail() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_eraseItem__write_succeed() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_findItem__uninitialized() +{ + Page page; + + size_t index = 0; + Item item; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_find__wrong_ns() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX + 1, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_find__wrong_type() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::I8, "test_value", index, item)); +} + +void test_Page_find__key_empty() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "", index, item)); +} + +void test_Page_find__wrong_key() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different", index, item)); +} + +void test_Page_find__too_large_index() +{ + NVSValidPageFixture fix; + size_t index = 2; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_findItem__without_read() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different")); +} + +void test_Page_markFull__wrong_state() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFull()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_markFull__success() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FULL; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFull()); + TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state()); +} + +void test_Page_markFull__write_fail() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FREEING; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_markFreeing__wrong_state() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_markFreeing__success() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FREEING; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::FREEING, fix.page.state()); +} + +void test_Page_getVarDataTailroom__uninitialized_page() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(Page::CHUNK_MAX_SIZE, fix.page.getVarDataTailroom()); +} + +void test_Page_getVarDataTailroom__success() +{ + NVSValidPageFixture fix; + + // blob data item, written namespace item, written normal item: 3 items + TEST_ASSERT_EQUAL((Page::ENTRY_COUNT - 3) * Page::ENTRY_SIZE, fix.page.getVarDataTailroom()); +} + +void test_Page_calcEntries__uninit() +{ + NVSPageFixture fix; + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__corrupt() +{ + PartitionMockFixture fix; + Page page; + + uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc}; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__active_wo_blob() +{ + NVSValidPageFixture fix; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(2, nvsStats.used_entries); + TEST_ASSERT_EQUAL(124, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__active_with_blob() +{ + NVSValidBlobPageFixture fix; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(4, nvsStats.used_entries); + TEST_ASSERT_EQUAL(122, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__invalid() +{ + Page page; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + + // total entries always get updated + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(0, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +int main(int argc, char **argv) +{ + UNITY_BEGIN(); + RUN_TEST(test_Page_load_reading_header_fails); + RUN_TEST(test_Page_load_reading_data_fails); + RUN_TEST(test_Page_load__uninitialized_page_has_0xfe); + RUN_TEST(test_Page_load__initialized_corrupt_header); + RUN_TEST(test_Page_load_success); + RUN_TEST(test_Page_load_full_page); + RUN_TEST(test_Page_load__seq_number_0); + RUN_TEST(test_Page_erase__write_fail); + RUN_TEST(test_Page_erase__success); + RUN_TEST(test_Page_write__initialize_write_failure); + RUN_TEST(test_Page_write__write_data_fails); + RUN_TEST(test_page_write__write_correct_entry_state); + RUN_TEST(test_Page_write__write_correct_data); + RUN_TEST(test_Page_readItem__read_entry_fails); + RUN_TEST(test_Page_readItem__read_corrupted_entry); + RUN_TEST(test_Page_readItem__read_corrupted_second_read_fail); + RUN_TEST(test_Page_readItem__read_corrupted_erase_fail); + RUN_TEST(test_Page_readItem__read_entry_suceeds); + RUN_TEST(test_Page_readItem__blob_read_data_fails); + RUN_TEST(test_Page_readItem__blob_corrupt_data); + RUN_TEST(test_Page_readItem__blob_read_entry_suceeds); + RUN_TEST(test_Page_cmp__uninitialized); + RUN_TEST(test_Page_cmp__item_not_found); + RUN_TEST(test_Page_cmp__item_type_mismatch); + RUN_TEST(test_Page_cmp__item_content_mismatch); + RUN_TEST(test_Page_cmp__item_content_match); + RUN_TEST(test_Page_cmpItem__blob_data_mismatch); + RUN_TEST(test_Page_cmpItem__blob_data_match); + RUN_TEST(test_Page_eraseItem__uninitialized); + RUN_TEST(test_Page_eraseItem__key_not_found); + RUN_TEST(test_Page_eraseItem__write_fail); + RUN_TEST(test_Page_readItem__corrupt_data_erase_failure); + RUN_TEST(test_Page_eraseItem__write_succeed); + RUN_TEST(test_Page_findItem__uninitialized); + RUN_TEST(test_Page_find__wrong_ns); + RUN_TEST(test_Page_find__wrong_type); + RUN_TEST(test_Page_find__key_empty); + RUN_TEST(test_Page_find__wrong_key); + RUN_TEST(test_Page_find__too_large_index); + RUN_TEST(test_Page_findItem__without_read); + RUN_TEST(test_Page_markFull__wrong_state); + RUN_TEST(test_Page_markFreeing__wrong_state); + RUN_TEST(test_Page_markFull__success); + RUN_TEST(test_Page_markFreeing__success); + RUN_TEST(test_Page_markFull__write_fail); + RUN_TEST(test_Page_getVarDataTailroom__uninitialized_page); + RUN_TEST(test_Page_getVarDataTailroom__success); + RUN_TEST(test_Page_calcEntries__uninit); + RUN_TEST(test_Page_calcEntries__corrupt); + RUN_TEST(test_Page_calcEntries__active_wo_blob); + RUN_TEST(test_Page_calcEntries__active_with_blob); + RUN_TEST(test_Page_calcEntries__invalid); + UNITY_END(); + return 0; +} diff --git a/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults b/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults new file mode 100644 index 000000000..a05773334 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n +CONFIG_IDF_TARGET="linux" +CONFIG_CXX_EXCEPTIONS=y diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index dfdd18c92..26183853b 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -17,6 +17,7 @@ #include #include #include +#include "esp_attr.h" #include "esp_err.h" #ifdef __cplusplus @@ -26,34 +27,92 @@ extern "C" { /** * Opaque pointer type representing non-volatile storage handle */ -typedef uint32_t nvs_handle; - -#define ESP_ERR_NVS_BASE 0x1100 /*!< Starting number of error codes */ -#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) /*!< The storage driver is not initialized */ -#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) /*!< Id namespace doesn’t exist yet and mode is NVS_READONLY */ -#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) /*!< The type of set or get operation doesn't match the type of value stored in NVS */ -#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) /*!< Storage handle was opened as read only */ -#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) /*!< There is not enough space in the underlying storage to save the value */ -#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) /*!< Namespace name doesn’t satisfy constraints */ -#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) /*!< Handle has been closed or is NULL */ -#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) /*!< The value wasn’t updated because flash write operation has failed. The value was written however, and update will be finished after re-initialization of nvs, provided that flash operation doesn’t fail again. */ -#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) /*!< Key name is too long */ -#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) /*!< Internal error; never returned by nvs_ API functions */ -#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) /*!< NVS is in an inconsistent state due to a previous error. Call nvs_flash_init and nvs_open again, then retry. */ -#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) /*!< String or blob length is not sufficient to store data */ -#define ESP_ERR_NVS_NO_FREE_PAGES (ESP_ERR_NVS_BASE + 0x0d) /*!< NVS partition doesn't contain any empty pages. This may happen if NVS partition was truncated. Erase the whole partition and call nvs_flash_init again. */ -#define ESP_ERR_NVS_VALUE_TOO_LONG (ESP_ERR_NVS_BASE + 0x0e) /*!< String or blob length is longer than supported by the implementation */ -#define ESP_ERR_NVS_PART_NOT_FOUND (ESP_ERR_NVS_BASE + 0x0f) /*!< Partition with specified name is not found in the partition table */ - -#define NVS_DEFAULT_PART_NAME "nvs" /*!< Default partition name of the NVS partition in the partition table */ +typedef uint32_t nvs_handle_t; + +/* + * Pre-IDF V4.0 uses nvs_handle, so leaving the original typedef here for compatibility. + */ +typedef nvs_handle_t nvs_handle IDF_DEPRECATED("Replace with nvs_handle_t"); + +#define ESP_ERR_NVS_BASE 0x1100 /*!< Starting number of error codes */ +#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) /*!< The storage driver is not initialized */ +#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) /*!< Id namespace doesn’t exist yet and mode is NVS_READONLY */ +#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) /*!< The type of set or get operation doesn't match the type of value stored in NVS */ +#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) /*!< Storage handle was opened as read only */ +#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) /*!< There is not enough space in the underlying storage to save the value */ +#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) /*!< Namespace name doesn’t satisfy constraints */ +#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) /*!< Handle has been closed or is NULL */ +#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) /*!< The value wasn’t updated because flash write operation has failed. The value was written however, and update will be finished after re-initialization of nvs, provided that flash operation doesn’t fail again. */ +#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) /*!< Key name is too long */ +#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) /*!< Internal error; never returned by nvs API functions */ +#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) /*!< NVS is in an inconsistent state due to a previous error. Call nvs_flash_init and nvs_open again, then retry. */ +#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) /*!< String or blob length is not sufficient to store data */ +#define ESP_ERR_NVS_NO_FREE_PAGES (ESP_ERR_NVS_BASE + 0x0d) /*!< NVS partition doesn't contain any empty pages. This may happen if NVS partition was truncated. Erase the whole partition and call nvs_flash_init again. */ +#define ESP_ERR_NVS_VALUE_TOO_LONG (ESP_ERR_NVS_BASE + 0x0e) /*!< String or blob length is longer than supported by the implementation */ +#define ESP_ERR_NVS_PART_NOT_FOUND (ESP_ERR_NVS_BASE + 0x0f) /*!< Partition with specified name is not found in the partition table */ + +#define ESP_ERR_NVS_NEW_VERSION_FOUND (ESP_ERR_NVS_BASE + 0x10) /*!< NVS partition contains data in new format and cannot be recognized by this version of code */ +#define ESP_ERR_NVS_XTS_ENCR_FAILED (ESP_ERR_NVS_BASE + 0x11) /*!< XTS encryption failed while writing NVS entry */ +#define ESP_ERR_NVS_XTS_DECR_FAILED (ESP_ERR_NVS_BASE + 0x12) /*!< XTS decryption failed while reading NVS entry */ +#define ESP_ERR_NVS_XTS_CFG_FAILED (ESP_ERR_NVS_BASE + 0x13) /*!< XTS configuration setting failed */ +#define ESP_ERR_NVS_XTS_CFG_NOT_FOUND (ESP_ERR_NVS_BASE + 0x14) /*!< XTS configuration not found */ +#define ESP_ERR_NVS_ENCR_NOT_SUPPORTED (ESP_ERR_NVS_BASE + 0x15) /*!< NVS encryption is not supported in this version */ +#define ESP_ERR_NVS_KEYS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x16) /*!< NVS key partition is uninitialized */ +#define ESP_ERR_NVS_CORRUPT_KEY_PART (ESP_ERR_NVS_BASE + 0x17) /*!< NVS key partition is corrupt */ +#define ESP_ERR_NVS_WRONG_ENCRYPTION (ESP_ERR_NVS_BASE + 0x19) /*!< NVS partition is marked as encrypted with generic flash encryption. This is forbidden since the NVS encryption works differently. */ + +#define ESP_ERR_NVS_CONTENT_DIFFERS (ESP_ERR_NVS_BASE + 0x18) /*!< Internal error; never returned by nvs API functions. NVS key is different in comparison */ + +#define NVS_DEFAULT_PART_NAME "nvs" /*!< Default partition name of the NVS partition in the partition table */ + +#define NVS_PART_NAME_MAX_SIZE 16 /*!< maximum length of partition name (excluding null terminator) */ +#define NVS_KEY_NAME_MAX_SIZE 16 /*!< Maximal length of NVS key name (including null terminator) */ + /** * @brief Mode of opening the non-volatile storage - * */ typedef enum { NVS_READONLY, /*!< Read only */ NVS_READWRITE /*!< Read and write */ -} nvs_open_mode; +} nvs_open_mode_t; + +/* + * Pre-IDF V4.0 uses nvs_open_mode, so leaving the original typedef here for compatibility. + */ +typedef nvs_open_mode_t nvs_open_mode IDF_DEPRECATED("Replace with nvs_open_mode_t"); + + +/** + * @brief Types of variables + * + */ +typedef enum { + NVS_TYPE_U8 = 0x01, /*!< Type uint8_t */ + NVS_TYPE_I8 = 0x11, /*!< Type int8_t */ + NVS_TYPE_U16 = 0x02, /*!< Type uint16_t */ + NVS_TYPE_I16 = 0x12, /*!< Type int16_t */ + NVS_TYPE_U32 = 0x04, /*!< Type uint32_t */ + NVS_TYPE_I32 = 0x14, /*!< Type int32_t */ + NVS_TYPE_U64 = 0x08, /*!< Type uint64_t */ + NVS_TYPE_I64 = 0x18, /*!< Type int64_t */ + NVS_TYPE_STR = 0x21, /*!< Type string */ + NVS_TYPE_BLOB = 0x42, /*!< Type blob */ + NVS_TYPE_ANY = 0xff /*!< Must be last */ +} nvs_type_t; + +/** + * @brief information about entry obtained from nvs_entry_info function + */ +typedef struct { + char namespace_name[16]; /*!< Namespace to which key-value belong */ + char key[16]; /*!< Key of stored key-value pair */ + nvs_type_t type; /*!< Type of stored key-value pair */ +} nvs_entry_info_t; + +/** + * Opaque pointer type representing iterator to nvs entries + */ +typedef struct nvs_opaque_iterator_t *nvs_iterator_t; /** * @brief Open non-volatile storage with a given namespace from the default NVS partition @@ -64,12 +123,10 @@ typedef enum { * The default NVS partition is the one that is labelled "nvs" in the partition * table. * - * @param[in] name Namespace name. Maximal length is determined by the - * underlying implementation, but is guaranteed to be - * at least 15 characters. Shouldn't be empty. + * @param[in] name Namespace name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will * open a handle for reading only. All write requests will - * be rejected for this handle. + * be rejected for this handle. * @param[out] out_handle If successful (return code is zero), handle will be * returned in this argument. * @@ -80,9 +137,10 @@ typedef enum { * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - other error codes from the underlying storage driver */ -esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle); +esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); /** * @brief Open non-volatile storage with a given namespace from specified partition @@ -92,12 +150,10 @@ esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_ha * with NVS using nvs_flash_init_partition() API. * * @param[in] part_name Label (name) of the partition of interest for object read/write/erase - * @param[in] name Namespace name. Maximal length is determined by the - * underlying implementation, but is guaranteed to be - * at least 15 characters. Shouldn't be empty. - * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will - * open a handle for reading only. All write requests will - * be rejected for this handle. + * @param[in] name Namespace name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will + * open a handle for reading only. All write requests will + * be rejected for this handle. * @param[out] out_handle If successful (return code is zero), handle will be * returned in this argument. * @@ -108,25 +164,99 @@ esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_ha * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - other error codes from the underlying storage driver */ -esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode open_mode, nvs_handle *out_handle); +esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle); /**@{*/ /** - * @brief set value for given key + * @brief set int8_t value for given key * - * This family of functions set value for the key, given its name. Note that - * actual storage will not be updated until nvs_commit function is called. + * Set value for the key, given its name. Note that the actual storage will not be updated + * until \c nvs_commit is called. + * + * @param[in] handle Handle obtained from nvs_open function. + * Handles that were opened read only cannot be used. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[in] value The value to set. + * + * @return + * - ESP_OK if value was set successfully + * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL + * - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only + * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints + * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the + * underlying storage to save the value + * - ESP_ERR_NVS_REMOVE_FAILED if the value wasn't updated because flash + * write operation has failed. The value was written however, and + * update will be finished after re-initialization of nvs, provided that + * flash operation doesn't fail again. + */ +esp_err_t nvs_set_i8 (nvs_handle_t handle, const char* key, int8_t value); + +/** + * @brief set uint8_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_u8 (nvs_handle_t handle, const char* key, uint8_t value); + +/** + * @brief set int16_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_i16 (nvs_handle_t handle, const char* key, int16_t value); + +/** + * @brief set uint16_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_u16 (nvs_handle_t handle, const char* key, uint16_t value); + +/** + * @brief set int32_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_i32 (nvs_handle_t handle, const char* key, int32_t value); + +/** + * @brief set uint32_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_u32 (nvs_handle_t handle, const char* key, uint32_t value); + +/** + * @brief set int64_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_i64 (nvs_handle_t handle, const char* key, int64_t value); + +/** + * @brief set uint64_t value for given key + * + * This function is the same as \c nvs_set_i8 except for the data type. + */ +esp_err_t nvs_set_u64 (nvs_handle_t handle, const char* key, uint64_t value); + +/** + * @brief set string for given key + * + * Set value for the key, given its name. Note that the actual storage will not be updated + * until \c nvs_commit is called. * * @param[in] handle Handle obtained from nvs_open function. * Handles that were opened read only cannot be used. - * @param[in] key Key name. Maximal length is determined by the underlying - * implementation, but is guaranteed to be at least - * 15 characters. Shouldn't be empty. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. * @param[in] value The value to set. * For strings, the maximum length (including null character) is - * 1984 bytes. + * 4000 bytes, if there is one complete page free for writing. + * This decreases, however, if the free space is fragmented. * * @return * - ESP_OK if value was set successfully @@ -141,16 +271,8 @@ esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_o * flash operation doesn't fail again. * - ESP_ERR_NVS_VALUE_TOO_LONG if the string value is too long */ -esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value); -esp_err_t nvs_set_u8 (nvs_handle handle, const char* key, uint8_t value); -esp_err_t nvs_set_i16 (nvs_handle handle, const char* key, int16_t value); -esp_err_t nvs_set_u16 (nvs_handle handle, const char* key, uint16_t value); -esp_err_t nvs_set_i32 (nvs_handle handle, const char* key, int32_t value); -esp_err_t nvs_set_u32 (nvs_handle handle, const char* key, uint32_t value); -esp_err_t nvs_set_i64 (nvs_handle handle, const char* key, int64_t value); -esp_err_t nvs_set_u64 (nvs_handle handle, const char* key, uint64_t value); -esp_err_t nvs_set_str (nvs_handle handle, const char* key, const char* value); -/**@}*/ +esp_err_t nvs_set_str (nvs_handle_t handle, const char* key, const char* value); +/**@}*/ /** * @brief set variable length binary value for given key @@ -160,10 +282,11 @@ esp_err_t nvs_set_str (nvs_handle handle, const char* key, const char* value); * * @param[in] handle Handle obtained from nvs_open function. * Handles that were opened read only cannot be used. - * @param[in] key Key name. Maximal length is 15 characters. Shouldn't be empty. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. * @param[in] value The value to set. * @param[in] length length of binary value to set, in bytes; Maximum length is - * 1984 bytes. + * 508000 bytes or (97.6% of the partition size - 4000) bytes + * whichever is lower. * * @return * - ESP_OK if value was set successfully @@ -178,20 +301,19 @@ esp_err_t nvs_set_str (nvs_handle handle, const char* key, const char* value); * flash operation doesn't fail again. * - ESP_ERR_NVS_VALUE_TOO_LONG if the value is too long */ -esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length); +esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value, size_t length); /**@{*/ /** - * @brief get value for given key + * @brief get int8_t value for given key * - * These functions retrieve value for the key, given its name. If key does not + * These functions retrieve value for the key, given its name. If \c key does not * exist, or the requested variable type doesn't match the type which was used * when setting a value, an error is returned. * * In case of any error, out_value is not modified. * - * All functions expect out_value to be a pointer to an already allocated variable - * of the given type. + * \c out_value has to be a pointer to an already allocated variable of the given type. * * \code{c} * // Example of using nvs_get_i32: @@ -204,9 +326,7 @@ esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, si * \endcode * * @param[in] handle Handle obtained from nvs_open function. - * @param[in] key Key name. Maximal length is determined by the underlying - * implementation, but is guaranteed to be at least - * 15 characters. Shouldn't be empty. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. * @param out_value Pointer to the output value. * May be NULL for nvs_get_str and nvs_get_blob, in this * case required length will be returned in length argument. @@ -218,20 +338,63 @@ esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, si * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints * - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data */ -esp_err_t nvs_get_i8 (nvs_handle handle, const char* key, int8_t* out_value); -esp_err_t nvs_get_u8 (nvs_handle handle, const char* key, uint8_t* out_value); -esp_err_t nvs_get_i16 (nvs_handle handle, const char* key, int16_t* out_value); -esp_err_t nvs_get_u16 (nvs_handle handle, const char* key, uint16_t* out_value); -esp_err_t nvs_get_i32 (nvs_handle handle, const char* key, int32_t* out_value); -esp_err_t nvs_get_u32 (nvs_handle handle, const char* key, uint32_t* out_value); -esp_err_t nvs_get_i64 (nvs_handle handle, const char* key, int64_t* out_value); -esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value); -/**@}*/ +esp_err_t nvs_get_i8 (nvs_handle_t handle, const char* key, int8_t* out_value); + +/** + * @brief get uint8_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_u8 (nvs_handle_t handle, const char* key, uint8_t* out_value); + +/** + * @brief get int16_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_i16 (nvs_handle_t handle, const char* key, int16_t* out_value); /** - * @brief get value for given key + * @brief get uint16_t value for given key * - * These functions retrieve value for the key, given its name. If key does not + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_u16 (nvs_handle_t handle, const char* key, uint16_t* out_value); + +/** + * @brief get int32_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_i32 (nvs_handle_t handle, const char* key, int32_t* out_value); + +/** + * @brief get uint32_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_u32 (nvs_handle_t handle, const char* key, uint32_t* out_value); + +/** + * @brief get int64_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_i64 (nvs_handle_t handle, const char* key, int64_t* out_value); + +/** + * @brief get uint64_t value for given key + * + * This function is the same as \c nvs_get_i8 except for the data type. + */ +esp_err_t nvs_get_u64 (nvs_handle_t handle, const char* key, uint64_t* out_value); +/**@}*/ + +/**@{*/ +/** + * @brief get string value for given key + * + * These functions retrieve the data of an entry, given its key. If key does not * exist, or the requested variable type doesn't match the type which was used * when setting a value, an error is returned. * @@ -239,7 +402,7 @@ esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value); * * All functions expect out_value to be a pointer to an already allocated variable * of the given type. - * + * * nvs_get_str and nvs_get_blob functions support WinAPI-style length queries. * To get the size necessary to store the value, call nvs_get_str or nvs_get_blob * with zero out_value and non-zero pointer to length. Variable pointed to @@ -265,10 +428,8 @@ esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value); * \endcode * * @param[in] handle Handle obtained from nvs_open function. - * @param[in] key Key name. Maximal length is determined by the underlying - * implementation, but is guaranteed to be at least - * 15 characters. Shouldn't be empty. - * @param out_value Pointer to the output value. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[out] out_value Pointer to the output value. * May be NULL for nvs_get_str and nvs_get_blob, in this * case required length will be returned in length argument. * @param[inout] length A non-zero pointer to the variable holding the length of out_value. @@ -282,11 +443,16 @@ esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value); * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints - * - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data + * - ESP_ERR_NVS_INVALID_LENGTH if \c length is not sufficient to store data */ -/**@{*/ -esp_err_t nvs_get_str (nvs_handle handle, const char* key, char* out_value, size_t* length); -esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size_t* length); +esp_err_t nvs_get_str (nvs_handle_t handle, const char* key, char* out_value, size_t* length); + +/** + * @brief get blob value for given key + * + * This function behaves the same as \c nvs_get_str, except for the data type. + */ +esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length); /**@}*/ /** @@ -297,9 +463,7 @@ esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size * @param[in] handle Storage handle obtained with nvs_open. * Handles that were opened read only cannot be used. * - * @param[in] key Key name. Maximal length is determined by the underlying - * implementation, but is guaranteed to be at least - * 15 characters. Shouldn't be empty. + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. * * @return * - ESP_OK if erase operation was successful @@ -308,7 +472,7 @@ esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist * - other error codes from the underlying storage driver */ -esp_err_t nvs_erase_key(nvs_handle handle, const char* key); +esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key); /** * @brief Erase all key-value pairs in a namespace @@ -324,7 +488,7 @@ esp_err_t nvs_erase_key(nvs_handle handle, const char* key); * - ESP_ERR_NVS_READ_ONLY if handle was opened as read only * - other error codes from the underlying storage driver */ -esp_err_t nvs_erase_all(nvs_handle handle); +esp_err_t nvs_erase_all(nvs_handle_t handle); /** * @brief Write any pending changes to non-volatile storage @@ -341,7 +505,7 @@ esp_err_t nvs_erase_all(nvs_handle handle); * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL * - other error codes from the underlying storage driver */ -esp_err_t nvs_commit(nvs_handle handle); +esp_err_t nvs_commit(nvs_handle_t handle); /** * @brief Close the storage handle and free any allocated resources @@ -354,7 +518,154 @@ esp_err_t nvs_commit(nvs_handle handle); * * @param[in] handle Storage handle to close */ -void nvs_close(nvs_handle handle); +void nvs_close(nvs_handle_t handle); + +/** + * @note Info about storage space NVS. + */ +typedef struct { + size_t used_entries; /**< Amount of used entries. */ + size_t free_entries; /**< Amount of free entries. */ + size_t total_entries; /**< Amount all available entries. */ + size_t namespace_count; /**< Amount name space. */ +} nvs_stats_t; + +/** + * @brief Fill structure nvs_stats_t. It provides info about used memory the partition. + * + * This function calculates to runtime the number of used entries, free entries, total entries, + * and amount namespace in partition. + * + * \code{c} + * // Example of nvs_get_stats() to get the number of used entries and free entries: + * nvs_stats_t nvs_stats; + * nvs_get_stats(NULL, &nvs_stats); + * printf("Count: UsedEntries = (%d), FreeEntries = (%d), AllEntries = (%d)\n", + nvs_stats.used_entries, nvs_stats.free_entries, nvs_stats.total_entries); + * \endcode + * + * @param[in] part_name Partition name NVS in the partition table. + * If pass a NULL than will use NVS_DEFAULT_PART_NAME ("nvs"). + * + * @param[out] nvs_stats Returns filled structure nvs_states_t. + * It provides info about used memory the partition. + * + * + * @return + * - ESP_OK if the changes have been written successfully. + * Return param nvs_stats will be filled. + * - ESP_ERR_NVS_PART_NOT_FOUND if the partition with label "name" is not found. + * Return param nvs_stats will be filled 0. + * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized. + * Return param nvs_stats will be filled 0. + * - ESP_ERR_INVALID_ARG if nvs_stats equal to NULL. + * - ESP_ERR_INVALID_STATE if there is page with the status of INVALID. + * Return param nvs_stats will be filled not with correct values because + * not all pages will be counted. Counting will be interrupted at the first INVALID page. + */ +esp_err_t nvs_get_stats(const char *part_name, nvs_stats_t *nvs_stats); + +/** + * @brief Calculate all entries in a namespace. + * + * An entry represents the smallest storage unit in NVS. + * Strings and blobs may occupy more than one entry. + * Note that to find out the total number of entries occupied by the namespace, + * add one to the returned value used_entries (if err is equal to ESP_OK). + * Because the name space entry takes one entry. + * + * \code{c} + * // Example of nvs_get_used_entry_count() to get amount of all key-value pairs in one namespace: + * nvs_handle_t handle; + * nvs_open("namespace1", NVS_READWRITE, &handle); + * ... + * size_t used_entries; + * size_t total_entries_namespace; + * if(nvs_get_used_entry_count(handle, &used_entries) == ESP_OK){ + * // the total number of entries occupied by the namespace + * total_entries_namespace = used_entries + 1; + * } + * \endcode + * + * @param[in] handle Handle obtained from nvs_open function. + * + * @param[out] used_entries Returns amount of used entries from a namespace. + * + * + * @return + * - ESP_OK if the changes have been written successfully. + * Return param used_entries will be filled valid value. + * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized. + * Return param used_entries will be filled 0. + * - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL. + * Return param used_entries will be filled 0. + * - ESP_ERR_INVALID_ARG if used_entries equal to NULL. + * - Other error codes from the underlying storage driver. + * Return param used_entries will be filled 0. + */ +esp_err_t nvs_get_used_entry_count(nvs_handle_t handle, size_t* used_entries); + +/** + * @brief Create an iterator to enumerate NVS entries based on one or more parameters + * + * \code{c} + * // Example of listing all the key-value pairs of any type under specified partition and namespace + * nvs_iterator_t it = nvs_entry_find(partition, namespace, NVS_TYPE_ANY); + * while (it != NULL) { + * nvs_entry_info_t info; + * nvs_entry_info(it, &info); + * it = nvs_entry_next(it); + * printf("key '%s', type '%d' \n", info.key, info.type); + * }; + * // Note: no need to release iterator obtained from nvs_entry_find function when + * // nvs_entry_find or nvs_entry_next function return NULL, indicating no other + * // element for specified criteria was found. + * } + * \endcode + * + * @param[in] part_name Partition name + * + * @param[in] namespace_name Set this value if looking for entries with + * a specific namespace. Pass NULL otherwise. + * + * @param[in] type One of nvs_type_t values. + * + * @return + * Iterator used to enumerate all the entries found, + * or NULL if no entry satisfying criteria was found. + * Iterator obtained through this function has to be released + * using nvs_release_iterator when not used any more. + */ +nvs_iterator_t nvs_entry_find(const char *part_name, const char *namespace_name, nvs_type_t type); + +/** + * @brief Returns next item matching the iterator criteria, NULL if no such item exists. + * + * Note that any copies of the iterator will be invalid after this call. + * + * @param[in] iterator Iterator obtained from nvs_entry_find function. Must be non-NULL. + * + * @return + * NULL if no entry was found, valid nvs_iterator_t otherwise. + */ +nvs_iterator_t nvs_entry_next(nvs_iterator_t iterator); + +/** + * @brief Fills nvs_entry_info_t structure with information about entry pointed to by the iterator. + * + * @param[in] iterator Iterator obtained from nvs_entry_find or nvs_entry_next function. Must be non-NULL. + * + * @param[out] out_info Structure to which entry information is copied. + */ +void nvs_entry_info(nvs_iterator_t iterator, nvs_entry_info_t *out_info); + +/** + * @brief Release iterator + * + * @param[in] iterator Release iterator obtained from nvs_entry_find function. NULL argument is allowed. + * + */ +void nvs_release_iterator(nvs_iterator_t iterator); #ifdef __cplusplus @@ -362,4 +673,3 @@ void nvs_close(nvs_handle handle); #endif #endif //ESP_NVS_H - diff --git a/components/nvs_flash/include/nvs_flash.h b/components/nvs_flash/include/nvs_flash.h index a7ef7f451..a5ad9ac28 100644 --- a/components/nvs_flash/include/nvs_flash.h +++ b/components/nvs_flash/include/nvs_flash.h @@ -19,6 +19,18 @@ extern "C" { #endif #include "nvs.h" +#include "esp_partition.h" + + +#define NVS_KEY_SIZE 32 // AES-256 + +/** + * @brief Key for encryption and decryption + */ +typedef struct { + uint8_t eky[NVS_KEY_SIZE]; /*!< XTS encryption and decryption key*/ + uint8_t tky[NVS_KEY_SIZE]; /*!< XTS tweak key */ +} nvs_sec_cfg_t; /** * @brief Initialize the default NVS partition. @@ -26,30 +38,62 @@ extern "C" { * This API initialises the default NVS partition. The default NVS partition * is the one that is labeled "nvs" in the partition table. * + * When "NVS_ENCRYPTION" is enabled in the menuconfig, this API enables + * the NVS encryption for the default NVS partition as follows + * 1. Read security configurations from the first NVS key + * partition listed in the partition table. (NVS key partition is + * any "data" type partition which has the subtype value set to "nvs_keys") + * 2. If the NVS key partiton obtained in the previous step is empty, + * generate and store new keys in that NVS key partiton. + * 3. Internally call "nvs_flash_secure_init()" with + * the security configurations obtained/generated in the previous steps. + * + * Post initialization NVS read/write APIs + * remain the same irrespective of NVS encryption. + * * @return * - ESP_OK if storage was successfully initialized. * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages * (which may happen if NVS partition was truncated) * - ESP_ERR_NOT_FOUND if no partition with label "nvs" is found in the partition table + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - one of the error codes from the underlying flash storage driver + * - error codes from nvs_flash_read_security_cfg API (when "NVS_ENCRYPTION" is enabled). + * - error codes from nvs_flash_generate_keys API (when "NVS_ENCRYPTION" is enabled). + * - error codes from nvs_flash_secure_init_partition API (when "NVS_ENCRYPTION" is enabled) . */ esp_err_t nvs_flash_init(void); /** * @brief Initialize NVS flash storage for the specified partition. * - * @param[in] partition_label Label of the partition. Note that internally a reference to - * passed value is kept and it should be accessible for future operations + * @param[in] partition_label Label of the partition. Must be no longer than 16 characters. * * @return * - ESP_OK if storage was successfully initialized. * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages * (which may happen if NVS partition was truncated) * - ESP_ERR_NOT_FOUND if specified partition is not found in the partition table + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures * - one of the error codes from the underlying flash storage driver */ esp_err_t nvs_flash_init_partition(const char *partition_label); +/** + * @brief Initialize NVS flash storage for the partition specified by partition pointer. + * + * @param[in] partition pointer to a partition obtained by the ESP partition API. + * + * @return + * - ESP_OK if storage was successfully initialized + * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages + * (which may happen if NVS partition was truncated) + * - ESP_ERR_INVALID_ARG in case partition is NULL + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition); + /** * @brief Deinitialize NVS storage for the default NVS partition * @@ -76,29 +120,133 @@ esp_err_t nvs_flash_deinit_partition(const char* partition_label); /** * @brief Erase the default NVS partition * - * This function erases all contents of the default NVS partition (one with label "nvs") + * Erases all contents of the default NVS partition (one with label "nvs"). + * + * @note If the partition is initialized, this function first de-initializes it. Afterwards, the partition has to + * be initialized again to be used. * * @return * - ESP_OK on success * - ESP_ERR_NOT_FOUND if there is no NVS partition labeled "nvs" in the * partition table + * - different error in case de-initialization fails (shouldn't happen) */ esp_err_t nvs_flash_erase(void); /** * @brief Erase specified NVS partition * - * This function erases all contents of specified NVS partition + * Erase all content of a specified NVS partition * - * @param[in] part_name Name (label) of the partition to be erased + * @note If the partition is initialized, this function first de-initializes it. Afterwards, the partition has to + * be initialized again to be used. + * + * @param[in] part_name Name (label) of the partition which should be erased * * @return * - ESP_OK on success * - ESP_ERR_NOT_FOUND if there is no NVS partition with the specified name * in the partition table + * - different error in case de-initialization fails (shouldn't happen) */ esp_err_t nvs_flash_erase_partition(const char *part_name); +/** + * @brief Erase custom partition. + * + * Erase all content of specified custom partition. + * + * @note + * If the partition is initialized, this function first de-initializes it. + * Afterwards, the partition has to be initialized again to be used. + * + * @param[in] partition pointer to a partition obtained by the ESP partition API. + * + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_FOUND if there is no partition with the specified + * parameters in the partition table + * - ESP_ERR_INVALID_ARG in case partition is NULL + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_erase_partition_ptr(const esp_partition_t *partition); + +/** + * @brief Initialize the default NVS partition. + * + * This API initialises the default NVS partition. The default NVS partition + * is the one that is labeled "nvs" in the partition table. + * + * @param[in] cfg Security configuration (keys) to be used for NVS encryption/decryption. + * If cfg is NULL, no encryption is used. + * + * @return + * - ESP_OK if storage was successfully initialized. + * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages + * (which may happen if NVS partition was truncated) + * - ESP_ERR_NOT_FOUND if no partition with label "nvs" is found in the partition table + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_secure_init(nvs_sec_cfg_t* cfg); + +/** + * @brief Initialize NVS flash storage for the specified partition. + * + * @param[in] partition_label Label of the partition. Note that internally a reference to + * passed value is kept and it should be accessible for future operations + * + * @param[in] cfg Security configuration (keys) to be used for NVS encryption/decryption. + * If cfg is null, no encryption/decryption is used. + * @return + * - ESP_OK if storage was successfully initialized. + * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages + * (which may happen if NVS partition was truncated) + * - ESP_ERR_NOT_FOUND if specified partition is not found in the partition table + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_secure_init_partition(const char *partition_label, nvs_sec_cfg_t* cfg); + +/** + * @brief Generate and store NVS keys in the provided esp partition + * + * @param[in] partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param[out] cfg Pointer to nvs security configuration structure. + * Pointer must be non-NULL. + * Generated keys will be populated in this structure. + * + * + * @return + * -ESP_OK, if cfg was read successfully; + * -or error codes from esp_partition_write/erase APIs. + */ + +esp_err_t nvs_flash_generate_keys(const esp_partition_t* partition, nvs_sec_cfg_t* cfg); + + +/** + * @brief Read NVS security configuration from a partition. + * + * @param[in] partition Pointer to partition structure obtained using + * esp_partition_find_first or esp_partition_get. + * Must be non-NULL. + * @param[out] cfg Pointer to nvs security configuration structure. + * Pointer must be non-NULL. + * + * @note Provided parition is assumed to be marked 'encrypted'. + * + * @return + * -ESP_OK, if cfg was read successfully; + * -ESP_ERR_NVS_KEYS_NOT_INITIALIZED, if the partition is not yet written with keys. + * -ESP_ERR_NVS_CORRUPT_KEY_PART, if the partition containing keys is found to be corrupt + * -or error codes from esp_partition_read API. + */ + +esp_err_t nvs_flash_read_security_cfg(const esp_partition_t* partition, nvs_sec_cfg_t* cfg); + #ifdef __cplusplus } #endif diff --git a/components/nvs_flash/include/nvs_handle.hpp b/components/nvs_flash/include/nvs_handle.hpp new file mode 100644 index 000000000..287866fad --- /dev/null +++ b/components/nvs_flash/include/nvs_handle.hpp @@ -0,0 +1,280 @@ +#ifndef NVS_HANDLE_HPP_ +#define NVS_HANDLE_HPP_ + +#include +#include +#include + +#include "nvs.h" + +namespace nvs { + +/** + * The possible blob types. This is a helper definition for template functions. + */ +enum class ItemType : uint8_t { + U8 = NVS_TYPE_U8, + I8 = NVS_TYPE_I8, + U16 = NVS_TYPE_U16, + I16 = NVS_TYPE_I16, + U32 = NVS_TYPE_U32, + I32 = NVS_TYPE_I32, + U64 = NVS_TYPE_U64, + I64 = NVS_TYPE_I64, + SZ = NVS_TYPE_STR, + BLOB = 0x41, + BLOB_DATA = NVS_TYPE_BLOB, + BLOB_IDX = 0x48, + ANY = NVS_TYPE_ANY +}; + + +/** + * @brief A handle allowing nvs-entry related operations on the NVS. + * + * @note The scope of this handle may vary depending on the implementation, but normally would be the namespace of + * a particular partition. Outside that scope, nvs entries can't be accessed/altered. + */ +class NVSHandle { +public: + virtual ~NVSHandle() { } + + /** + * @brief set value for given key + * + * Sets value for key. Note that physical storage will not be updated until nvs_commit function is called. + * + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[in] value The value to set. Allowed types are the ones declared in ItemType as well as enums. + * For strings, the maximum length (including null character) is + * 4000 bytes, if there is one complete page free for writing. + * This decreases, however, if the free space is fragmented. + * Note that enums loose their type information when stored in NVS. Ensure that the correct + * enum type is used during retrieval with \ref get_item! + * + * @return + * - ESP_OK if value was set successfully + * - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only + * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints + * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the + * underlying storage to save the value + * - ESP_ERR_NVS_REMOVE_FAILED if the value wasn't updated because flash + * write operation has failed. The value was written however, and + * update will be finished after re-initialization of nvs, provided that + * flash operation doesn't fail again. + * - ESP_ERR_NVS_VALUE_TOO_LONG if the string value is too long + */ + template + esp_err_t set_item(const char *key, T value); + virtual + esp_err_t set_string(const char *key, const char* value) = 0; + + /** + * @brief get value for given key + * + * These functions retrieve value for the key, given its name. If key does not + * exist, or the requested variable type doesn't match the type which was used + * when setting a value, an error is returned. + * + * In case of any error, out_value is not modified. + * + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param value The output value. All integral types which are declared in ItemType as well as enums + * are allowed. Note however that enums lost their type information when stored in NVS. + * Ensure that the correct enum type is used during retrieval with \ref get_item! + * + * @return + * - ESP_OK if the value was retrieved successfully + * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist + * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints + * - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data + */ + template + esp_err_t get_item(const char *key, T &value); + + /** + * @brief set variable length binary value for given key + * + * This family of functions set value for the key, given its name. Note that + * actual storage will not be updated until nvs_commit function is called. + * + * @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[in] blob The blob value to set. + * @param[in] len length of binary value to set, in bytes; Maximum length is + * 508000 bytes or (97.6% of the partition size - 4000) bytes + * whichever is lower. + * + * @return + * - ESP_OK if value was set successfully + * - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only + * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints + * - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the + * underlying storage to save the value + * - ESP_ERR_NVS_REMOVE_FAILED if the value wasn't updated because flash + * write operation has failed. The value was written however, and + * update will be finished after re-initialization of nvs, provided that + * flash operation doesn't fail again. + * - ESP_ERR_NVS_VALUE_TOO_LONG if the value is too long + * + * @note compare to \ref nvs_set_blob in nvs.h + */ + virtual esp_err_t set_blob(const char *key, const void* blob, size_t len) = 0; + + /** + * @brief get value for given key + * + * These functions retrieve the data of an entry, given its key. If key does not + * exist, or the requested variable type doesn't match the type which was used + * when setting a value, an error is returned. + * + * In case of any error, out_value is not modified. + * + * Both functions expect out_value to be a pointer to an already allocated variable + * of the given type. + * + * It is suggested that nvs_get/set_str is used for zero-terminated short C strings, and + * nvs_get/set_blob is used for arbitrary data structures and long C strings. + * + * @param[in] key Key name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param out_str/ Pointer to the output value. + * out_blob + * @param[inout] len The length of the output buffer pointed to by out_str/out_blob. + * Use \c get_item_size to query the size of the item beforehand. + * + * @return + * - ESP_OK if the value was retrieved successfully + * - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist + * - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints + * - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data + */ + virtual esp_err_t get_string(const char *key, char* out_str, size_t len) = 0; + virtual esp_err_t get_blob(const char *key, void* out_blob, size_t len) = 0; + + /** + * @brief Look up the size of an entry's data. + * + * @param[in] datatype Data type to search for. + * @param[in] key Key name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty. + * @param[out] size Size of the item, if it exists. + * For strings, this size includes the zero terminator. + * + * @return - ESP_OK if the item with specified type and key exists. Its size will be returned via \c size. + * - ESP_ERR_NVS_NOT_FOUND if an item with the requested key and type doesn't exist or any other + * error occurs. + */ + virtual esp_err_t get_item_size(ItemType datatype, const char *key, size_t &size) = 0; + + /** + * @brief Erases an entry. + */ + virtual esp_err_t erase_item(const char* key) = 0; + + /** + * Erases all entries in the scope of this handle. The scope may vary, depending on the implementation. + * + * @not If you want to erase the whole nvs flash (partition), refer to \ref + */ + virtual esp_err_t erase_all() = 0; + + /** + * Commits all changes done through this handle so far. + * Currently, NVS writes to storage right after the set and get functions, + * but this is not guaranteed. + */ + virtual esp_err_t commit() = 0; + + /** + * @brief Calculate all entries in the scope of the handle. + * + * @param[out] used_entries Returns amount of used entries from a namespace on success. + * + * + * @return + * - ESP_OK if the changes have been written successfully. + * Return param used_entries will be filled valid value. + * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized. + * Return param used_entries will be filled 0. + * - ESP_ERR_INVALID_ARG if nvs_stats equal to NULL. + * - Other error codes from the underlying storage driver. + * Return param used_entries will be filled 0. + */ + virtual esp_err_t get_used_entry_count(size_t& usedEntries) = 0; + +protected: + virtual esp_err_t set_typed_item(ItemType datatype, const char *key, const void* data, size_t dataSize) = 0; + + virtual esp_err_t get_typed_item(ItemType datatype, const char *key, void* data, size_t dataSize) = 0; +}; + +/** + * @brief Opens non-volatile storage and returns a handle object. + * + * The handle is automatically closed on desctruction. The scope of the handle is the namespace ns_name + * in a particular partition partition_name. + * The parameters partition_name, ns_name and open_mode have the same meaning and restrictions as the parameters + * part_name, name and open_mode in \ref nvs_open_from_partition, respectively. + * + * @param err an optional pointer to an esp_err_t result of the open operation, having the same meaning as the return + * value in \ref nvs_open_from_partition: + * - ESP_OK if storage handle was opened successfully + * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized + * - ESP_ERR_NVS_PART_NOT_FOUND if the partition with label "nvs" is not found + * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and + * mode is NVS_READONLY + * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - other error codes from the underlying storage driver + * + * @return shared pointer of an nvs handle on success, an empty shared pointer otherwise + */ +std::unique_ptr open_nvs_handle_from_partition(const char *partition_name, + const char *ns_name, + nvs_open_mode_t open_mode, + esp_err_t *err = nullptr); + +/** + * @brief This function does the same as \ref open_nvs_handle_from_partition but uses the default nvs partition + * instead of a partition_name parameter. + */ +std::unique_ptr open_nvs_handle(const char *ns_name, + nvs_open_mode_t open_mode, + esp_err_t *err = nullptr); + +// Helper functions for template usage +/** + * Help to translate all integral types into ItemType. + */ +template::value, void*>::type = nullptr> +constexpr ItemType itemTypeOf() +{ + return static_cast(((std::is_signed::value)?0x10:0x00) | sizeof(T)); +} + +/** + * Help to translate all enum types into integral ItemType. + */ +template::value, int>::type = 0> +constexpr ItemType itemTypeOf() +{ + return static_cast(((std::is_signed::value)?0x10:0x00) | sizeof(T)); +} + +template +constexpr ItemType itemTypeOf(const T&) +{ + return itemTypeOf(); +} + +// Template Implementations +template +esp_err_t NVSHandle::set_item(const char *key, T value) { + return set_typed_item(itemTypeOf(value), key, &value, sizeof(value)); +} + +template +esp_err_t NVSHandle::get_item(const char *key, T &value) { + return get_typed_item(itemTypeOf(value), key, &value, sizeof(value)); +} + +} // nvs + +#endif // NVS_HANDLE_HPP_ diff --git a/components/nvs_flash/test_nvs_host/crc.cpp b/components/nvs_flash/mock/int/crc.cpp similarity index 97% rename from components/nvs_flash/test_nvs_host/crc.cpp rename to components/nvs_flash/mock/int/crc.cpp index 4cbb9be9e..5c1b74564 100644 --- a/components/nvs_flash/test_nvs_host/crc.cpp +++ b/components/nvs_flash/mock/int/crc.cpp @@ -31,7 +31,7 @@ static const unsigned int crc32_le_table[256] = { 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, - + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, @@ -52,7 +52,7 @@ static const unsigned int crc32_le_table[256] = { -extern "C" unsigned int crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) +extern "C" uint32_t esp_rom_crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) { unsigned int i; crc = ~crc; @@ -61,4 +61,3 @@ extern "C" unsigned int crc32_le(unsigned int crc, unsigned char const * buf,uns } return ~crc; } - diff --git a/components/nvs_flash/test_nvs_host/crc.h b/components/nvs_flash/mock/int/crc.h similarity index 72% rename from components/nvs_flash/test_nvs_host/crc.h rename to components/nvs_flash/mock/int/crc.h index c752b3030..f675da327 100644 --- a/components/nvs_flash/test_nvs_host/crc.h +++ b/components/nvs_flash/mock/int/crc.h @@ -20,7 +20,11 @@ extern "C" { #endif -uint32_t crc32_le(uint32_t crc, const uint8_t* buf, size_t len); +/** + * Mock function to replace ESP ROM function used in IDF with a Linux implementation. + * Note: the name MUST have the prefix esp_rom_* since tools/ci/check_rom_apis.sh checks and complains otherwise. + */ +uint32_t esp_rom_crc32_le(uint32_t crc, const uint8_t* buf, size_t len); #ifdef __cplusplus } diff --git a/components/nvs_flash/nvs_partition_generator/README.rst b/components/nvs_flash/nvs_partition_generator/README.rst index 11f0a2ea6..3a78a109d 100644 --- a/components/nvs_flash/nvs_partition_generator/README.rst +++ b/components/nvs_flash/nvs_partition_generator/README.rst @@ -1,76 +1,312 @@ NVS Partition Generator Utility =============================== +:link_to_translation:`zh_CN:[中文]` + Introduction ------------ -:component_file:`nvs_flash/nvs_partition_generator/nvs_partition_gen.py` utility is designed to help create a binary file, compatible with NVS architecture defined in :doc:`Non-Volatile Storage `, based on user provided key-value pairs in a CSV file. -Utility is ideally suited for generating a binary blob, containing data specific to ODM/OEM, which can be flashed externally at the time of device manufacturing. This helps manufacturers set unique value for various parameters for each device, e.g. serial number, while using same application firmware for all devices. - -CSV file format ---------------- - -Each row of the .csv file should have 4 parameters, separated by comma. Below is the description of each of these parameters: +The utility :component_file:`nvs_flash/nvs_partition_generator/nvs_partition_gen.py` creates a binary file based on key-value pairs provided in a CSV file. The binary file is compatible with NVS architecture defined in :doc:`Non-Volatile Storage `. +This utility is ideally suited for generating a binary blob, containing data specific to ODM/OEM, which can be flashed externally at the time of device manufacturing. This allows manufacturers to generate many instances of the same application firmware with customized parameters for each device, such as a serial number. -Key - Key of the data. Data can later be accessed from an application via this key. +Prerequisites +------------- +To use this utility in encryption mode, install the following packages: + - cryptography package -Type - Supported values are ``file``, ``data`` and ``namespace``. +All the required packages are included in `requirements.txt` in the root of the esp-idf directory. -Encoding - Supported values are: ``u8``, ``i8``, ``u16``, ``u32``, ``i32``, ``string``, ``hex2bin``, ``base64`` and ``binary``. This specifies how actual data values are encoded in the resultant binary file. Difference between ``string`` and ``binary`` encoding is that ``string`` data is terminated with a NULL character, whereas ``binary`` data is not. - - .. note:: For ``file`` type, only ``hex2bin``, ``base64``, ``string`` and ``binary`` is supported as of now. +CSV file format +--------------- -Value - Data value. +Each line of a .csv file should contain 4 parameters, separated by a comma. The table below provides the description for each of these parameters. -.. note:: Encoding and Value cells for ``namespace`` field type should be empty. Encoding and Value of ``namespace`` is fixed and isn't configurable. Any value in these cells are ignored. ++-----+-----------+----------------------------------------------------------------------+-----------------------------------------------------+ +| No. | Parameter | Description | Notes | ++=====+===========+======================================================================+=====================================================+ +| 1 | Key | Key of the data. The data can be accessed later from | | +| | | an application using this key. | | ++-----+-----------+----------------------------------------------------------------------+-----------------------------------------------------+ +| 2 | Type | Supported values are ``file``, ``data`` and ``namespace``. | | ++-----+-----------+----------------------------------------------------------------------+-----------------------------------------------------+ +| 3 | Encoding | Supported values are: ``u8``, ``i8``, ``u16``, ``i16``, ``u32``, | As of now, for the ``file`` type, | +| | | ``i32``, ``u64``, ``i64``, ``string``, ``hex2bin``, ``base64`` | only ``hex2bin``, ``base64``, ``string``, | +| | | and ``binary``. | and ``binary`` encoding is supported. | +| | | This specifies how actual data values are encoded in the | | +| | | resulting binary file. The difference between the ``string`` | | +| | | and ``binary`` encoding is that ``string`` data is terminated | | +| | | with a NULL character, whereas ``binary`` data is not. | | ++-----+-----------+----------------------------------------------------------------------+-----------------------------------------------------+ +| 4 | Value | Data value. | Encoding and Value cells for the ``namespace`` | +| | | | field type should be empty. Encoding and Value | +| | | | of ``namespace`` is fixed and is not configurable. | +| | | | Any values in these cells are ignored. | ++-----+-----------+----------------------------------------------------------------------+-----------------------------------------------------+ -.. note:: First row of the CSV file should always be column header and isn't configurable. +.. note:: The first line of the CSV file should always be the column header and it is not configurable. -Below is an example dump of such CSV file:: +Below is an example dump of such a CSV file:: key,type,encoding,value <-- column header namespace_name,namespace,, <-- First entry should be of type "namespace" key1,data,u8,1 key2,file,string,/path/to/file -.. note:: Make sure there are no spaces before and after ',' in CSV file. +.. note:: + + Make sure there are **no spaces**: + - before and after ',' + - at the end of each line in a CSV file + NVS Entry and Namespace association ----------------------------------- -When a new namespace entry is encountered in the CSV file, each follow-up entries will be part of that namespace, until next namespace entry is found, in which case all the follow-up entries will be part of the new namespace. +When a namespace entry is encountered in a CSV file, each following entry will be treated as part of that namespace until the next namespace entry is found. At this point, all the following entries will be treated as part of the new namespace. + +.. note:: First entry in a CSV file should always be a ``namespace`` entry. + + +Multipage Blob Support +---------------------- + +By default, binary blobs are allowed to span over multiple pages and are written in the format mentioned in Section :ref:`structure_of_entry`. +If you intend to use an older format, the utility provides an option to disable this feature. + + +Encryption Support +------------------- -.. note:: First entry in a CSV file should always be ``namespace`` entry. +The NVS Partition Generator utility also allows you to create an encrypted binary file. The utility uses the AES-XTS encryption. Please refer to :ref:`nvs_encryption` for more details. + + +Decryption Support +------------------- +This utility allows you to decrypt an encrypted NVS binary file. The utility uses an NVS binary file encrypted using AES-XTS encryption. Please refer to :ref:`nvs_encryption` for more details. Running the utility ------------------- -python nvs_partition_gen.py [-h] [--input INPUT] [--output OUTPUT] [--size SIZE] +**Usage**:: + + python nvs_partition_gen.py [-h] {generate,generate-key,encrypt,decrypt} ... + + Optional Arguments: + +-----+------------+----------------------------------------------------------------------+ + | No. | Parameter | Description | + +=====+============+======================================================================+ + | 1 | -h, --help | show this help message and exit | + +-----+------------+----------------------------------------------------------------------+ + + Commands: + Run nvs_partition_gen.py {command} -h for additional help + +-----+--------------+--------------------------------------------------------------------+ + | No. | Parameter | Description | + +=====+==============+====================================================================+ + | 1 | generate | Generate NVS partition | + +-----+--------------+--------------------------------------------------------------------+ + | 2 | generate-key | Generate keys for encryption | + +-----+--------------+--------------------------------------------------------------------+ + | 3 | encrypt | Generate NVS encrypted partition | + +-----+--------------+--------------------------------------------------------------------+ + | 4 | decrypt | Decrypt NVS encrypted partition | + +-----+--------------+--------------------------------------------------------------------+ + + +To generate NVS partition (Default): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Usage**:: + + python nvs_partition_gen.py generate [-h] [--version {1,2}] [--outdir OUTDIR] + input output size + + Positional Arguments: + +--------------+----------------------------------------------------------------------+ + | Parameter | Description | + +==============+======================================================================+ + | input | Path to CSV file to parse | + +--------------+----------------------------------------------------------------------+ + | output | Path to output NVS binary file | + +--------------+----------------------------------------------------------------------+ + | size | Size of NVS partition in bytes (must be multiple of 4096) | + +--------------+----------------------------------------------------------------------+ -+------------------------+----------------------------------------------------------------------------------------------+ -| Arguments | Description | -+========================+==============================================================================================+ -| --input INPUT | Path to CSV file to parse. | -+------------------------+----------------------------------------------------------------------------------------------+ -| --output OUTPUT | Path to output generated binary file. | -+------------------------+----------------------------------------------------------------------------------------------+ -| --size SIZE | Size of NVS Partition in bytes (must be multiple of 4096) | -+------------------------+----------------------------------------------------------------------------------------------+ + Optional Arguments: + +-----------------+--------------------------------------------------------------------+ + | Parameter | Description | + +=================+====================================================================+ + | -h, --help | show this help message and exit | + +-----------------+--------------------------------------------------------------------+ + | --version {1,2} | Set multipage blob version. | + | | Version 1 - Multipage blob support disabled. | + | | Version 2 - Multipage blob support enabled. | + | | Default: Version 2 | + | | | + +-----------------+--------------------------------------------------------------------+ + | --outdir OUTDIR | Output directory to store files created | + | | (Default: current directory) | + +-----------------+--------------------------------------------------------------------+ + + +You can run the utility to generate NVS partition using the command below: A sample CSV file is provided with the utility:: - python nvs_partition_gen.py --input sample.csv --output sample.bin --size 0x3000 + python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000 + + +To generate only encryption keys: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Usage**:: + + python nvs_partition_gen.py generate-key [-h] [--keyfile KEYFILE] + [--outdir OUTDIR] + + Optional Arguments: + +--------------------+----------------------------------------------------------------------+ + | Parameter | Description | + +====================+======================================================================+ + | -h, --help | show this help message and exit | + +--------------------+----------------------------------------------------------------------+ + | --keyfile KEYFILE | Path to output encryption keys file | + +--------------------+----------------------------------------------------------------------+ + | --outdir OUTDIR | Output directory to store files created. | + | | (Default: current directory) | + +--------------------+----------------------------------------------------------------------+ + +You can run the utility to generate only encryption keys using the command below:: + + python nvs_partition_gen.py generate-key + + +To generate encrypted NVS partition: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Usage**:: + + python nvs_partition_gen.py encrypt [-h] [--version {1,2}] [--keygen] + [--keyfile KEYFILE] [--inputkey INPUTKEY] + [--outdir OUTDIR] + input output size + + Positional Arguments: + +--------------+----------------------------------------------------------------------+ + | Parameter | Description | + +==============+======================================================================+ + | input | Path to CSV file to parse | + +--------------+----------------------------------------------------------------------+ + | output | Path to output NVS binary file | + +--------------+----------------------------------------------------------------------+ + | size | Size of NVS partition in bytes (must be multiple of 4096) | + +--------------+----------------------------------------------------------------------+ + + + Optional Arguments: + +---------------------+--------------------------------------------------------------------+ + | Parameter | Description | + +=====================+====================================================================+ + | -h, --help | show this help message and exit | + | | | + +---------------------+--------------------------------------------------------------------+ + | --version {1,2} | Set multipage blob version. | + | | Version 1 - Multipage blob support disabled. | + | | Version 2 - Multipage blob support enabled. | + | | Default: Version 2 | + +---------------------+--------------------------------------------------------------------+ + | --keygen | Generates key for encrypting NVS partition | + +---------------------+--------------------------------------------------------------------+ + | --keyfile KEYFILE | Path to output encryption keys file | + +---------------------+--------------------------------------------------------------------+ + | --inputkey INPUTKEY | File having key for encrypting NVS partition | + +---------------------+--------------------------------------------------------------------+ + | --outdir OUTDIR | Output directory to store files created | + | | (Default: current directory) | + +---------------------+--------------------------------------------------------------------+ + + +You can run the utility to encrypt NVS partition using the command below: +A sample CSV file is provided with the utility: + +- Encrypt by allowing the utility to generate encryption keys:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen + +.. note:: Encryption key of the following format ``/keys/keys-.bin`` is created. + +- Encrypt by allowing the utility to generate encryption keys and store it in provided custom filename:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen --keyfile sample_keys.bin + +.. note:: Encryption key of the following format ``/keys/sample_keys.bin`` is created. +.. note:: This newly created file having encryption keys in ``keys/`` directory is compatible with NVS key-partition structure. Refer to :ref:`nvs_key_partition` for more details. + +- Encrypt by providing the encryption keys as input binary file:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --inputkey sample_keys.bin + +To decrypt encrypted NVS partition: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + **Usage**:: + + python nvs_partition_gen.py decrypt [-h] [--outdir OUTDIR] input key output + + Positional Arguments: + +--------------+----------------------------------------------------------------------+ + | Parameter | Description | + +==============+======================================================================+ + | input | Path to encrypted NVS partition file to parse | + +--------------+----------------------------------------------------------------------+ + | key | Path to file having keys for decryption | + +--------------+----------------------------------------------------------------------+ + | output | Path to output decrypted binary file | + +--------------+----------------------------------------------------------------------+ + + + Optional Arguments: + +---------------------+--------------------------------------------------------------------+ + | Parameter | Description | + +=====================+====================================================================+ + | -h, --help | show this help message and exit | + +---------------------+--------------------------------------------------------------------+ + | --outdir OUTDIR | Output directory to store files created | + | | (Default: current directory) | + +---------------------+--------------------------------------------------------------------+ + + +You can run the utility to decrypt encrypted NVS partition using the command below:: + + python nvs_partition_gen.py decrypt sample_encr.bin sample_keys.bin sample_decr.bin + +You can also provide the format version number: + - Multipage Blob Support Disabled (Version 1) + - Multipage Blob Support Enabled (Version 2) + + +Multipage Blob Support Disabled (Version 1): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can run the utility in this format by setting the version parameter to 1, as shown below. +A sample CSV file is provided with the utility:: + + python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000 --version 1 + + +Multipage Blob Support Enabled (Version 2): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can run the utility in this format by setting the version parameter to 2, as shown below. +A sample CSV file is provided with the utility:: + + python nvs_partition_gen.py generate sample_multipage_blob.csv sample.bin 0x4000 --version 2 + .. note:: *Minimum NVS Partition Size needed is 0x3000 bytes.* .. note:: *When flashing the binary onto the device, make sure it is consistent with the application's sdkconfig.* + Caveats ------- -- Utility doesn't check for duplicate keys and will write data pertaining to both keys. User needs to make sure keys are distinct. -- Once a new page is created, no data will be written in the space left in previous page. Fields in the CSV file need to be ordered in such a way so as to optimize memory. +- Utility does not check for duplicate keys and will write data pertaining to both keys. You need to make sure that the keys are distinct. +- Once a new page is created, no data will be written in the space left on the previous page. Fields in the CSV file need to be ordered in such a way as to optimize memory. - 64-bit datatype is not yet supported. + diff --git a/components/nvs_flash/nvs_partition_generator/README_CN.rst b/components/nvs_flash/nvs_partition_generator/README_CN.rst new file mode 100644 index 000000000..24094e993 --- /dev/null +++ b/components/nvs_flash/nvs_partition_generator/README_CN.rst @@ -0,0 +1,304 @@ +NVS 分区生æˆç¨‹åº +=============================== + +:link_to_translation:`en:[English]` + +ä»‹ç» +------------ + +NVS 分区生æˆç¨‹åº (:component_file:`nvs_flash/nvs_partition_generator/nvs_partition_gen.py`) æ ¹æ® CSV 文件中的键值对生æˆäºŒè¿›åˆ¶æ–‡ä»¶ã€‚该二进制文件与 :doc:`éžæ˜“失性存储器 (NVS) ` 中定义的 NVS 结构兼容。NVS 分区生æˆç¨‹åºé€‚åˆç”¨äºŽç”ŸæˆäºŒè¿›åˆ¶æ•°æ®ï¼ˆBlob),其中包括设备生产时å¯ä»Žå¤–部烧录的 ODM/OEM æ•°æ®ã€‚这也使得生产制造商在使用åŒä¸€ä¸ªå›ºä»¶çš„åŸºç¡€ä¸Šï¼Œé€šè¿‡è‡ªå®šä¹‰å‚æ•°ï¼Œå¦‚åºåˆ—å·ç­‰ï¼Œä¸ºæ¯ä¸ªè®¾å¤‡ç”Ÿæˆä¸åŒé…置。 + +准备工作 +------------- + +在加密模å¼ä¸‹ä½¿ç”¨è¯¥ç¨‹åºï¼Œéœ€å®‰è£…下列软件包: + - cryptography package + +根目录下的 `requirements.txt` 包å«å¿…需 python 包,请预先安装。 + + +CSV æ–‡ä»¶æ ¼å¼ +--------------- + +.csv 文件æ¯è¡Œéœ€åŒ…å«å››ä¸ªå‚数,以逗å·éš”å¼€ã€‚å…·ä½“å‚æ•°æè¿°è§ä¸‹è¡¨ï¼š + ++------+----------+--------------------------------------------------------------+-----------------------------------------------------------------+ +| åºå· | 傿•° | æè¿° | 说明 | ++======+==========+==============================================================+=================================================================+ +| 1 | Key | 主键,应用程åºå¯é€šè¿‡æŸ¥è¯¢æ­¤é”®æ¥èŽ·å–æ•°æ®ã€‚ | | ++------+----------+--------------------------------------------------------------+-----------------------------------------------------------------+ +| 2 | Type | æ”¯æŒ ``file``ã€``data`` å’Œ ``namespace``。 | | ++------+----------+--------------------------------------------------------------+-----------------------------------------------------------------+ +| 3 | Encoding | æ”¯æŒ ``u8``ã€``i8``ã€``u16``ã€``u32``〠| ``file`` | +| | | ``i32``ã€``string``ã€``hex2bin``ã€``base64`` å’Œ ``binary``。 | 类型当å‰ä»…æ”¯æŒ | +| | | 决定二进制 ``bin`` 文件中 value è¢«ç¼–ç æˆçš„类型。 | ``hex2bin``ã€``base64``〠| +| | | ``string`` å’Œ ``binary`` ç¼–ç çš„区别在于, | ``string`` å’Œ ``binary`` ç¼–ç ã€‚ | +| | | ``string`` æ•°æ®ä»¥ NULL 字符结尾,``binary`` æ•°æ®åˆ™ä¸æ˜¯ã€‚ | | ++------+----------+--------------------------------------------------------------+-----------------------------------------------------------------+ +| 4 | Value | Data value | ``namespace`` 字段的 ``encoding`` å’Œ ``value`` 应为空。 | +| | | | ``namespace`` çš„ ``encoding`` å’Œ ``value`` 为固定值,ä¸å¯è®¾ç½®ã€‚ | +| | | | 这些å•元格中的所有值都会被忽视。 | ++------+----------+--------------------------------------------------------------+-----------------------------------------------------------------+ + +.. note:: CSV 文件的第一行应为列标题,ä¸å¯è®¾ç½®ã€‚ + +此类 CSV 文件的 Dump 示例如下:: + + key,type,encoding,value <-- 列标题 + namespace_name,namespace,, <-- 第一个æ¡ç›®ä¸º "namespace" + key1,data,u8,1 + key2,file,string,/path/to/file + + +.. note:: + + 请确ä¿ï¼š + - é€—å· ',' å‰åŽæ— ç©ºæ ¼ï¼› + - CSV 文件æ¯è¡Œæœ«å°¾æ— ç©ºæ ¼ã€‚ + +NVS æ¡ç›®å’Œå‘½å空间 (namespace) +----------------------------------- + +如 CSV 文件中出现命å空间æ¡ç›®ï¼ŒåŽç»­æ¡ç›®å‡ä¼šè¢«è§†ä¸ºè¯¥å‘½å空间的一部分,直至找到下一个命å空间æ¡ç›®ã€‚找到新命å空间æ¡ç›®åŽï¼ŒåŽç»­æ‰€æœ‰æ¡ç›®éƒ½ä¼šè¢«è§†ä¸ºæ–°å‘½å空间的一部分。 + +.. note:: CSV 文件中第一个æ¡ç›®åº”始终为 ``namespace``。 + + +支æŒå¤šé¡µ Blob +---------------------- + +默认情况下,二进制 Blob å¯è·¨å¤šé¡µï¼Œæ ¼å¼å‚考 :ref:`structure_of_entry` 章节。如需使用旧版格å¼ï¼Œå¯åœ¨ç¨‹åºä¸­ç¦ç”¨è¯¥åŠŸèƒ½ã€‚ + + +支æŒåР坆 +------------------- + +NVS 分区生æˆç¨‹åºè¿˜å¯ä½¿ç”¨ AES-XTS 加密生æˆäºŒè¿›åˆ¶åŠ å¯†æ–‡ä»¶ã€‚æ›´å¤šä¿¡æ¯è¯¦è§ :ref:`nvs_encryption`。 + +支æŒè§£å¯† +------------------- +如果 NVS 二进制文件采用了 AES-XTS 加密,该程åºè¿˜å¯å¯¹æ­¤ç±»æ–‡ä»¶è¿›è¡Œè§£å¯†ï¼Œæ›´å¤šä¿¡æ¯è¯¦è§ :ref:`nvs_encryption`。 + +è¿è¡Œç¨‹åº +------------------- + +**使用方法**:: + + python nvs_partition_gen.py [-h] {generate,generate-key,encrypt,decrypt} ... + +**å¯é€‰å‚æ•°**: + ++------+------------+----------------------+ +| åºå· | 傿•° | æè¿° | ++------+------------+----------------------+ +| 1 | -h, --help | 显示帮助信æ¯å¹¶é€€å‡º | ++------+------------+----------------------+ + +**命令**:: + + è¿è¡Œ nvs_partition_gen.py {command} -h æŸ¥çœ‹æ›´å¤šå¸®åŠ©ä¿¡æ¯ + ++------+--------------+---------------+ +| åºå· | 傿•° | æè¿° | ++------+--------------+---------------+ +| 1 | generate | ç”Ÿæˆ NVS 分区 | ++------+--------------+---------------+ +| 2 | generate-key | 生æˆåР坆坆钥 | ++------+--------------+---------------+ +| 3 | encrypt | 加密 NVS 分区 | ++------+--------------+---------------+ +| 4 | decrypt | 解密 NVS 分区 | ++------+--------------+---------------+ + + +ç”Ÿæˆ NVS 分区(默认模å¼ï¼‰ +---------------------------------- + +**使用方法**:: + + python nvs_partition_gen.py generate [-h] [--version {1,2}] [--outdir OUTDIR] + input output size + +**ä½ç½®å‚æ•°**: + ++--------+--------------------------------------------------+ +| 傿•° | æè¿° | ++--------+--------------------------------------------------+ +| input | å¾…è§£æžçš„ CSV 文件路径 | ++--------+--------------------------------------------------+ +| output | NVS 二进制文件的输出路径 | ++--------+--------------------------------------------------+ +| size | NVS 分区大å°ï¼ˆä»¥å­—节为å•ä½ï¼Œä¸”为 4096 的整数å€ï¼‰ | ++--------+--------------------------------------------------+ + +**å¯é€‰å‚æ•°**: + ++-----------------+------------------------------------------------+ +| 傿•° | æè¿° | ++-----------------+------------------------------------------------+ +| -h, --help | 显示帮助信æ¯å¹¶é€€å‡º | ++-----------------+------------------------------------------------+ +| --version {1,2} | - 设置多页 Blob 版本。 | +| | - 版本 1:ç¦ç”¨å¤šé¡µ Blobï¼› | +| | - 版本 2:å¯ç”¨å¤šé¡µ Blobï¼› | +| | - 默认版本:版本 2。 | ++-----------------+------------------------------------------------+ +| --outdir OUTDIR | 输出目录,用于存储创建的文件。(默认当å‰ç›®å½•) | ++-----------------+------------------------------------------------+ + +è¿è¡Œå¦‚下命令创建 NVS 分区,该程åºåŒæ—¶ä¼šæä¾› CSV 示例文件:: + + python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000 + +仅生æˆåР坆坆钥 +----------------------- + +**使用方法**:: + + python nvs_partition_gen.py generate-key [-h] [--keyfile KEYFILE] + [--outdir OUTDIR] + +**å¯é€‰å‚æ•°**: + ++-------------------+------------------------------------------------+ +| 傿•° | æè¿° | ++-------------------+------------------------------------------------+ +| -h, --help | 显示帮助信æ¯å¹¶é€€å‡º | ++-------------------+------------------------------------------------+ +| --keyfile KEYFILE | 加密密钥文件的输出路径 | ++-------------------+------------------------------------------------+ +| --outdir OUTDIR | 输出目录,用于存储创建的文件。(默认当å‰ç›®å½•) | ++-------------------+------------------------------------------------+ + +è¿è¡Œä»¥ä¸‹å‘½ä»¤ä»…生æˆåР坆坆钥:: + + python nvs_partition_gen.py generate-key + +ç”Ÿæˆ NVS 加密分区 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**使用方法**:: + + python nvs_partition_gen.py encrypt [-h] [--version {1,2}] [--keygen] + [--keyfile KEYFILE] [--inputkey INPUTKEY] + [--outdir OUTDIR] + input output size + +**ä½ç½®å‚æ•°**: + ++--------+--------------------------------------+ +| 傿•° | æè¿° | ++--------+--------------------------------------+ +| input | å¾…è§£æž CSV 文件的路径 | ++--------+--------------------------------------+ +| output | NVS 二进制文件的输出路径 | ++--------+--------------------------------------+ +| size | NVS åˆ†åŒºå¤§å° | +| | (以字节为å•ä½ï¼Œä¸”为 4096 的整数å€ï¼‰ | ++--------+--------------------------------------+ + +**å¯é€‰å‚æ•°**: + ++---------------------+------------------------------+ +| 傿•° | æè¿° | ++---------------------+------------------------------+ +| -h, --help | 显示帮助信æ¯å¹¶é€€å‡º | ++---------------------+------------------------------+ +| --version {1,2} | - 设置多页 Blob 版本。 | +| | - 版本 1:ç¦ç”¨å¤šé¡µ Blobï¼› | +| | - 版本 2:å¯ç”¨å¤šé¡µ Blobï¼› | +| | - 默认版本:版本 2。 | ++---------------------+------------------------------+ +| --keygen | ç”Ÿæˆ NVS 分区加密密钥 | ++---------------------+------------------------------+ +| --keyfile KEYFILE | 密钥文件的输出路径 | ++---------------------+------------------------------+ +| --inputkey INPUTKEY | å†…å« NVS 分区加密密钥的文件 | ++---------------------+------------------------------+ +| --outdir OUTDIR | 输出目录,用于存储创建的文件 | +| | (默认当å‰ç›®å½•) | ++---------------------+------------------------------+ + +è¿è¡Œä»¥ä¸‹å‘½ä»¤åР坆 NVS 分区,该程åºåŒæ—¶ä¼šæä¾›ä¸€ä¸ª CSV 示例文件。 + +- 通过 NVS 分区生æˆç¨‹åºç”ŸæˆåР坆坆钥æ¥åР坆:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen + +.. note:: 创建的加密密钥格å¼ä¸º ``/keys/keys-.bin``。 + +- 通过 NVS 分区生æˆç¨‹åºç”ŸæˆåŠ å¯†å¯†é’¥ï¼Œå¹¶å°†å¯†é’¥å­˜å‚¨äºŽè‡ªå®šä¹‰çš„æ–‡ä»¶ä¸­:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --keygen --keyfile sample_keys.bin + +.. note:: 创建的加密密钥格å¼ä¸º ``/keys/keys-.bin``。 +.. note:: 加密密钥存储于新建文件的 ``keys/`` 目录下,与 NVS 密钥分区结构兼容。更多信æ¯è¯·å‚考 :ref:`nvs_key_partition`。 + +- 将加密密钥用作二进制输入文件æ¥è¿›è¡ŒåР坆:: + + python nvs_partition_gen.py encrypt sample_singlepage_blob.csv sample_encr.bin 0x3000 --inputkey sample_keys.bin + +解密 NVS 分区 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**使用方法**:: + + python nvs_partition_gen.py decrypt [-h] [--outdir OUTDIR] input key output + +**ä½ç½®å‚æ•°**: + ++--------+-------------------------------+ +| 傿•° | æè¿° | ++--------+-------------------------------+ +| input | å¾…è§£æžçš„ NVS 加密分区文件路径 | ++--------+-------------------------------+ +| key | 嫿œ‰è§£å¯†å¯†é’¥çš„æ–‡ä»¶è·¯å¾„ | ++--------+-------------------------------+ +| output | 已解密的二进制文件输出路径 | ++--------+-------------------------------+ + +**å¯é€‰å‚æ•°**: + ++-----------------+------------------------------+ +| 傿•° | æè¿° | ++-----------------+------------------------------+ +| -h, --help | 显示帮助信æ¯å¹¶é€€å‡º | ++-----------------+------------------------------+ +| --outdir OUTDIR | 输出目录,用于存储创建的文件 | +| | (默认当å‰ç›®å½•) | ++-----------------+------------------------------+ + +è¿è¡Œä»¥ä¸‹å‘½ä»¤è§£å¯†å·²åŠ å¯†çš„ NVS 分区:: + + python nvs_partition_gen.py decrypt sample_encr.bin sample_keys.bin sample_decr.bin + +您å¯ä»¥è‡ªå®šä¹‰æ ¼å¼ç‰ˆæœ¬å·ï¼š + +- 版本 1:ç¦ç”¨å¤šé¡µ Blob +- 版本 2:å¯ç”¨å¤šé¡µ Blob + +版本 1:ç¦ç”¨å¤šé¡µ Blob +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +如需ç¦ç”¨å¤šé¡µ Blobï¼Œè¯·æŒ‰ç…§å¦‚ä¸‹å‘½ä»¤å°†ç‰ˆæœ¬å‚æ•°è®¾ç½®ä¸º 1,以此格å¼è¿è¡Œåˆ†åŒºç”Ÿæˆç¨‹åºã€‚该程åºåŒæ—¶ä¼šæä¾›ä¸€ä¸ª CSV 示例文件:: + + python nvs_partition_gen.py generate sample_singlepage_blob.csv sample.bin 0x3000 --version 1 + +版本 2:å¯ç”¨å¤šé¡µ Blob +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +如需å¯ç”¨å¤šé¡µ Blobï¼Œè¯·æŒ‰ç…§å¦‚ä¸‹å‘½ä»¤å°†ç‰ˆæœ¬å‚æ•°è®¾ç½®ä¸º 2,以此格å¼è¿è¡Œåˆ†åŒºç”Ÿæˆç¨‹åºã€‚该程åºåŒæ—¶ä¼šæä¾›ä¸€ä¸ª CSV 示例文件:: + + python nvs_partition_gen.py generate sample_multipage_blob.csv sample.bin 0x4000 --version 2 + +.. note:: NVS 分区最å°ä¸º 0x3000 字节。 + +.. note:: 将二进制文件烧录至设备时,请确ä¿ä¸Žåº”用的 sdkconfig 设置一致。 + + +说明 +------- + +- 分区生æˆç¨‹åºä¸ä¼šå¯¹é‡å¤é”®è¿›è¡Œæ£€æŸ¥ï¼Œè€Œå°†æ•°æ®åŒæ—¶å†™å…¥è¿™ä¸¤ä¸ªé‡å¤é”®ä¸­ã€‚请注æ„ä¸è¦ä½¿ç”¨åŒå的键; +- 新页é¢åˆ›å»ºåŽï¼Œå‰ä¸€é¡µçš„空白处ä¸ä¼šå†å†™å…¥æ•°æ®ã€‚CSV æ–‡ä»¶ä¸­çš„å­—æ®µé¡»æŒ‰æ¬¡åºæŽ’åˆ—ä»¥ä¼˜åŒ–å†…å­˜ï¼› +- æš‚ä¸æ”¯æŒ 64 使•°æ®ç±»åž‹ã€‚ diff --git a/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py b/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py index d5ddc55b0..890f5222b 100755 --- a/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py +++ b/components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py @@ -19,32 +19,55 @@ # from __future__ import division, print_function -from builtins import int, range, bytes -from io import open -import sys + import argparse +import array import binascii +import codecs +import datetime +import distutils.dir_util +import os import random import struct -import os -import array -import csv +import sys import zlib -import codecs -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.backends import default_backend +from builtins import bytes, int, range +from io import open + +from future.moves.itertools import zip_longest -VERSION1_PRINT = "v1 - Multipage Blob Support Disabled" -VERSION2_PRINT = "v2 - Multipage Blob Support Enabled" +try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +except ImportError: + print('The cryptography package is not installed.' + 'Please refer to the Get Started section of the ESP-IDF Programming Guide for ' + 'setting up the required packages.') + raise + +VERSION1_PRINT = 'V1 - Multipage Blob Support Disabled' +VERSION2_PRINT = 'V2 - Multipage Blob Support Enabled' + + +def reverse_hexbytes(addr_tmp): + addr = [] + reversed_bytes = '' + for i in range(0, len(addr_tmp), 2): + addr.append(addr_tmp[i:i + 2]) + reversed_bytes = ''.join(reversed(addr)) + + return reversed_bytes """ Class for standard NVS page structure """ + + class Page(object): PAGE_PARAMS = { - "max_size": 4096, - "max_old_blob_size": 1984, - "max_new_blob_size": 4000, - "max_entries": 126 + 'max_size': 4096, + 'max_old_blob_size': 1984, + 'max_new_blob_size': 4000, + 'max_entries': 126 } # Item type codes @@ -54,6 +77,8 @@ class Page(object): I16 = 0x12 U32 = 0x04 I32 = 0x14 + U64 = 0x08 + I64 = 0x18 SZ = 0x21 BLOB = 0x41 BLOB_DATA = 0x42 @@ -68,25 +93,21 @@ class Page(object): CHUNK_ANY = 0xFF ACTIVE = 0xFFFFFFFE FULL = 0xFFFFFFFC - VERSION1=0xFF - VERSION2=0xFE + VERSION1 = 0xFF + VERSION2 = 0xFE - def __init__(self, page_num, is_rsrv_page=False): + def __init__(self, page_num, version, is_rsrv_page=False): self.entry_num = 0 - self.is_encrypt = False - self.encr_key = None self.bitmap_array = array.array('B') - self.version = Page.VERSION2 - self.page_buf = bytearray(b'\xff')*Page.PAGE_PARAMS["max_size"] + self.version = version + self.page_buf = bytearray(b'\xff') * Page.PAGE_PARAMS['max_size'] if not is_rsrv_page: self.bitmap_array = self.create_bitmap_array() - self.set_header(page_num) - - def set_header(self, page_num): - global page_header + self.set_header(page_num, version) + def set_header(self, page_num, version): # set page state to active - page_header= bytearray(b'\xff') *32 + page_header = bytearray(b'\xff') * 32 page_state_active_seq = Page.ACTIVE struct.pack_into(' 2: if not addr_len % 2: addr_tmp = addr - tweak_tmp = self.reverse_hexbytes(addr_tmp) - tweak_val = tweak_tmp + (init_tweak_val * (tweak_len_needed - (len(tweak_tmp)))) else: addr_tmp = init_tweak_val + addr - tweak_tmp = self.reverse_hexbytes(addr_tmp) - tweak_val = tweak_tmp + (init_tweak_val * (tweak_len_needed - (len(tweak_tmp)))) + tweak_tmp = reverse_hexbytes(addr_tmp) + tweak_val = tweak_tmp + (init_tweak_val * (tweak_len_needed - (len(tweak_tmp)))) else: tweak_val = addr + (init_tweak_val * (tweak_len_needed - len(addr))) @@ -207,11 +212,10 @@ def encrypt_data(self, data_input, no_of_entries, nvs_obj): return encr_data_to_write - def write_entry_to_buf(self, data, entrycount,nvs_obj): encr_data = bytearray() - if self.is_encrypt: + if nvs_obj.encrypt: encr_data_ret = self.encrypt_data(data, entrycount,nvs_obj) encr_data[0:len(encr_data_ret)] = encr_data_ret data = encr_data @@ -226,7 +230,6 @@ def write_entry_to_buf(self, data, entrycount,nvs_obj): self.write_bitmaparray() self.entry_num += 1 - def set_crc_header(self, entry_struct): crc_data = bytearray(b'28') crc_data[0:4] = entry_struct[0:4] @@ -236,7 +239,6 @@ def set_crc_header(self, entry_struct): struct.pack_into('=0, "Page overflow!!" + tailroom = (Page.PAGE_PARAMS['max_entries'] - self.entry_num - 1) * Page.SINGLE_ENTRY_SIZE + assert tailroom >= 0, 'Page overflow!!' # Split the binary data into two and store a chunk of available size onto curr page if tailroom < remaining_size: @@ -266,7 +268,7 @@ def write_varlen_binary_data(self, entry_struct, ns_index, key, data, data_size, # Calculate no. of entries data chunk will require datachunk_rounded_size = (chunk_size + 31) & ~31 datachunk_entry_count = datachunk_rounded_size // 32 - datachunk_total_entry_count = datachunk_entry_count + 1 # +1 for the entry header + datachunk_total_entry_count = datachunk_entry_count + 1 # +1 for the entry header # Set Span entry_struct[2] = datachunk_total_entry_count @@ -276,7 +278,7 @@ def write_varlen_binary_data(self, entry_struct, ns_index, key, data, data_size, entry_struct[3] = chunk_index # Set data chunk - data_chunk = data[offset:offset + chunk_size] + data_chunk = data[offset:offset + chunk_size] # Compute CRC of data chunk struct.pack_into(' Page.PAGE_PARAMS["max_old_blob_size"]: - raise InputError("Version %s\n%s: Size exceeds max allowed length." % (VERSION1_PRINT,key)) - - if version == Page.VERSION2: - if encoding == "string": - if datalen > Page.PAGE_PARAMS["max_new_blob_size"]: - raise InputError("Version %s\n%s: Size exceeds max allowed length." % (VERSION2_PRINT,key)) + if datalen > Page.PAGE_PARAMS['max_old_blob_size']: + if self.version == Page.VERSION1: + raise InputError(' Input File: Size (%d) exceeds max allowed length `%s` bytes for key `%s`.' + % (datalen, Page.PAGE_PARAMS['max_old_blob_size'], key)) + else: + if encoding == 'string': + raise InputError(' Input File: Size (%d) exceeds max allowed length `%s` bytes for key `%s`.' + % (datalen, Page.PAGE_PARAMS['max_old_blob_size'], key)) # Calculate no. of entries data will require rounded_size = (datalen + 31) & ~31 data_entry_count = rounded_size // 32 - total_entry_count = data_entry_count + 1 # +1 for the entry header + total_entry_count = data_entry_count + 1 # +1 for the entry header # Check if page is already full and new page is needed to be created right away - if version == Page.VERSION1: - if encoding in ["string", "hex2bin", "binary", "base64"]: - if (self.entry_num + total_entry_count) >= Page.PAGE_PARAMS["max_entries"]: - raise PageFullError() - else: - if encoding == "string": - if (self.entry_num + total_entry_count) >= Page.PAGE_PARAMS["max_entries"]: - raise PageFullError() + if self.entry_num >= Page.PAGE_PARAMS['max_entries']: + raise PageFullError() + elif (self.entry_num + total_entry_count) >= Page.PAGE_PARAMS['max_entries']: + if not (self.version == Page.VERSION2 and encoding in ['hex2bin', 'binary', 'base64']): + raise PageFullError() # Entry header - entry_struct = bytearray(b'\xff')*32 + entry_struct = bytearray(b'\xff') * 32 # Set Namespace Index entry_struct[0] = ns_index # Set Span - if version == Page.VERSION2: - if encoding == "string": + if self.version == Page.VERSION2: + if encoding == 'string': entry_struct[2] = data_entry_count + 1 # Set Chunk Index chunk_index = Page.CHUNK_ANY @@ -408,51 +401,58 @@ def write_varlen_data(self, key, data, encoding, ns_index,nvs_obj): entry_struct[8:8 + len(key)] = key.encode() # set Type - if encoding == "string": + if encoding == 'string': entry_struct[1] = Page.SZ - elif encoding in ["hex2bin", "binary", "base64"]: + elif encoding in ['hex2bin', 'binary', 'base64']: entry_struct[1] = Page.BLOB - if version == Page.VERSION2 and (encoding in ["hex2bin", "binary", "base64"]): - entry_struct = self.write_varlen_binary_data(entry_struct,ns_index,key,data,\ - datalen,total_entry_count, encoding, nvs_obj) + if self.version == Page.VERSION2 and (encoding in ['hex2bin', 'binary', 'base64']): + entry_struct = self.write_varlen_binary_data(entry_struct,ns_index,key,data, + datalen,total_entry_count, encoding, nvs_obj) else: self.write_single_page_entry(entry_struct, data, datalen, data_entry_count, nvs_obj) - - """ Low-level function to write data of primitive type into page buffer. """ def write_primitive_data(self, key, data, encoding, ns_index,nvs_obj): # Check if entry exceeds max number of entries allowed per page - if self.entry_num >= Page.PAGE_PARAMS["max_entries"]: + if self.entry_num >= Page.PAGE_PARAMS['max_entries']: raise PageFullError() - entry_struct = bytearray(b'\xff')*32 - entry_struct[0] = ns_index # namespace index - entry_struct[2] = 0x01 # Span + entry_struct = bytearray(b'\xff') * 32 + entry_struct[0] = ns_index # namespace index + entry_struct[2] = 0x01 # Span chunk_index = Page.CHUNK_ANY entry_struct[3] = chunk_index # write key - key_array = b'\x00' *16 + key_array = b'\x00' * 16 entry_struct[8:24] = key_array entry_struct[8:8 + len(key)] = key.encode() - if encoding == "u8": + if encoding == 'u8': entry_struct[1] = Page.U8 struct.pack_into('/ + :param outdir: Target output dir to store files + :param filepath: Path of target file + ''' + bin_ext = '.bin' + # Expand if tilde(~) provided in path + outdir = os.path.expanduser(outdir) + + if filepath: + key_file_name, ext = os.path.splitext(filepath) + if not ext: + filepath = key_file_name + bin_ext + elif bin_ext not in ext: + sys.exit('Error: `%s`. Only `%s` extension allowed.' % (filepath, bin_ext)) + + # Create dir if does not exist + if not (os.path.isdir(outdir)): + distutils.dir_util.mkpath(outdir) + + filedir, filename = os.path.split(filepath) + filedir = os.path.join(outdir,filedir,'') + if filedir and not os.path.isdir(filedir): + distutils.dir_util.mkpath(filedir) + + if os.path.isabs(filepath): + if not outdir == os.getcwd(): + print('\nWarning: `%s` \n\t==> absolute path given so outdir is ignored for this file.' % filepath) + # Set to empty as outdir is ignored here + outdir = '' + + # Set full path - outdir + filename + filepath = os.path.join(outdir, '') + filepath + + return outdir, filepath + + +def encrypt(args): + ''' + Generate encrypted NVS Partition + :param args: Command line arguments given + ''' + key = None + bin_ext = '.bin' + + check_size(args.size) + if (args.keygen is False) and (not args.inputkey): + sys.exit('Error. --keygen or --inputkey argument needed.') + elif args.keygen and args.inputkey: + sys.exit('Error. --keygen and --inputkey both are not allowed.') + elif not args.keygen and args.keyfile: + print('\nWarning:','--inputkey argument is given. --keyfile argument will be ignored...') + + if args.inputkey: + # Check if key file has .bin extension + filename, ext = os.path.splitext(args.inputkey) + if bin_ext not in ext: + sys.exit('Error: `%s`. Only `%s` extension allowed.' % (args.inputkey, bin_ext)) + key = bytearray() + with open(args.inputkey, 'rb') as key_f: + key = key_f.read(64) + + # Generate encrypted NVS Partition + generate(args, is_encr_enabled=True, encr_key=key) + + +def decrypt_data(data_input, decr_key, page_num, entry_no, entry_size): + ''' + Decrypt NVS data entry + ''' + page_max_size = 4096 + first_entry_offset = 64 + init_tweak_val = '0' + tweak_len_needed = 32 # in hex + tweak_tmp = '' + + data_input = binascii.hexlify(data_input) + rel_addr = page_num * page_max_size + first_entry_offset + + # Set tweak value + offset = entry_no * entry_size + addr = hex(rel_addr + offset)[2:] + addr_len = len(addr) + if addr_len > 2: + if not addr_len % 2: + addr_tmp = addr + else: + addr_tmp = init_tweak_val + addr + tweak_tmp = reverse_hexbytes(addr_tmp) + tweak_val = tweak_tmp + (init_tweak_val * (tweak_len_needed - (len(tweak_tmp)))) + else: + tweak_val = addr + (init_tweak_val * (tweak_len_needed - len(addr))) + + if type(data_input) == bytes: + data_input = data_input.decode() + + # Decrypt 32 bytes of data using AES-XTS decryption + backend = default_backend() + plain_text = codecs.decode(data_input, 'hex') + tweak = codecs.decode(tweak_val, 'hex') + cipher = Cipher(algorithms.AES(decr_key), modes.XTS(tweak), backend=backend) + decryptor = cipher.decryptor() + decrypted_data = decryptor.update(plain_text) + + return decrypted_data + + +def decrypt(args): + ''' + Decrypt encrypted NVS Partition + :param args: Command line arguments given + ''' + bin_ext = '.bin' + nvs_read_bytes = 32 + decrypted_entry_no = 0 + file_entry_no = 0 + page_num = 0 + page_max_size = 4096 + start_entry_offset = 0 + empty_data_entry = bytearray(b'\xff') * nvs_read_bytes + + # Check if key file has .bin extension + input_files = [args.input, args.key, args.output] + for filepath in input_files: + filename, ext = os.path.splitext(filepath) + if bin_ext not in ext: + sys.exit('Error: `%s`. Only `%s` extension allowed.' % (filepath, bin_ext)) + with open(args.key,'rb') as decr_key_file: + decr_key = decr_key_file.read(64) + + args.outdir, args.output = set_target_filepath(args.outdir, args.output) + + output_buf = bytearray(b'\xff') + + with open(args.input, 'rb') as input_file, open(args.output,'wb') as output_file: + while True: + if file_entry_no == 128: + decrypted_entry_no = 0 + file_entry_no = 0 + page_num += 1 + data_entry = input_file.read(nvs_read_bytes) + if not data_entry: + break + if data_entry != empty_data_entry and file_entry_no not in [0,1]: + data_entry = decrypt_data(data_entry, decr_key, page_num, decrypted_entry_no, nvs_read_bytes) + decrypted_entry_no += 1 + write_entry_no = ((page_num * page_max_size) + file_entry_no) + start_idx = start_entry_offset + (write_entry_no * nvs_read_bytes) + end_idx = nvs_read_bytes + output_buf[start_idx:end_idx] = data_entry + file_entry_no += 1 + start_entry_offset += nvs_read_bytes + output_file.write(output_buf) + + print('\nCreated NVS decrypted binary: ===>', args.output) + + +def generate_key(args): + ''' + Generate encryption keys + :param args: Command line arguments given + ''' + page_max_size = 4096 + keys_dir = 'keys' + output_keyfile = None + bin_ext = '.bin' + + if not args.keyfile: + timestamp = datetime.datetime.now().strftime('%m-%d_%H-%M') + args.keyfile = 'keys-' + timestamp + bin_ext + + keys_outdir = os.path.join(args.outdir,keys_dir, '') + # Create keys/ dir in if does not exist + if not (os.path.isdir(keys_outdir)): + distutils.dir_util.mkpath(keys_outdir) + keys_outdir, output_keyfile = set_target_filepath(keys_outdir, args.keyfile) + + key = ''.join(random.choice('0123456789abcdef') for _ in range(128)).strip() + encr_key_bytes = codecs.decode(key, 'hex') + key_len = len(encr_key_bytes) + + keys_buf = bytearray(b'\xff') * page_max_size + keys_buf[0:key_len] = encr_key_bytes + crc_data = keys_buf[0:key_len] + crc_data = bytes(crc_data) + crc = zlib.crc32(crc_data, 0xFFFFFFFF) + struct.pack_into(' ', output_keyfile) + + return key + + +def generate(args, is_encr_enabled=False, encr_key=None): + ''' + Generate NVS Partition + :param args: Command line arguments given + :param is_encr_enabled: Encryption enabled/disabled + :param encr_key: Key to encrypt NVS partition + ''' + is_dir_new = False + bin_ext = '.bin' + + input_size = check_size(args.size) + if args.version == 1: + args.version = Page.VERSION1 + elif args.version == 2: + args.version = Page.VERSION2 + + # Check if key file has .bin extension + filename, ext = os.path.splitext(args.output) + if bin_ext not in ext: + sys.exit('Error: `%s`. Only `.bin` extension allowed.' % args.output) + args.outdir, args.output = set_target_filepath(args.outdir, args.output) + + if is_encr_enabled and not encr_key: + encr_key = generate_key(args) + + input_file = open(args.input, 'rt', encoding='utf8') + output_file = open(args.output, 'wb') + + with open(args.input, 'rt', encoding='utf8') as input_file,\ + open(args.output, 'wb') as output_file,\ + nvs_open(output_file, input_size, args.version, is_encrypt=is_encr_enabled, key=encr_key) as nvs_obj: + + if nvs_obj.version == Page.VERSION1: + version_set = VERSION1_PRINT + else: + version_set = VERSION2_PRINT -def nvs_part_gen(input_filename=None, output_filename=None, input_part_size=None, is_key_gen=None, encrypt_mode=None, key_file=None, version_no=None): - """ Wrapper to generate nvs partition binary + print('\nCreating NVS binary with version:', version_set) - :param input_filename: Name of input file containing data - :param output_filename: Name of output file to store generated binary - :param input_part_size: Size of partition in bytes (must be multiple of 4096) - :param is_key_gen: Enable encryption key generation in encryption mode - :param encrypt_mode: Enable/Disable encryption mode - :param key_file: Input file having encryption keys in encryption mode - :param version_no: Format Version number - :return: None - """ + line = input_file.readline().strip() - global key_input, key_len_needed + # Comments are skipped + while line.startswith('#'): + line = input_file.readline().strip() + if not isinstance(line, str): + line = line.encode('utf-8') - key_len_needed = 64 - key_input = bytearray() + header = line.split(',') - if key_gen: - key_input = ''.join(random.choice('0123456789abcdef') for _ in range(128)).strip() - elif key_file: - with open(key_file, 'rb') as key_f: - key_input = key_f.read(64) + while True: + line = input_file.readline().strip() + if not isinstance(line, str): + line = line.encode('utf-8') - if all(arg is not None for arg in [input_filename, output_filename, input_size]): - input_file = open(input_filename, 'rt', encoding='utf8') - output_file = open(output_filename, 'wb') + value = line.split(',') + if len(value) == 1 and '' in value: + break - with nvs_open(output_file, input_size) as nvs_obj: - reader = csv.DictReader(input_file, delimiter=',') - for row in reader: - try: - write_entry(nvs_obj, row["key"], row["type"], row["encoding"], row["value"]) - except (InputError) as e: - print(e) - input_file.close() - output_file.close() - sys.exit(-2) - - input_file.close() - output_file.close() - - - if key_gen: - keys_page_buf = bytearray(b'\xff')*Page.PAGE_PARAMS["max_size"] - key_bytes = bytearray() - if len(key_input) == key_len_needed: - key_bytes = key_input - else: - key_bytes = codecs.decode(key_input, 'hex') - key_len = len(key_bytes) - keys_page_buf[0:key_len] = key_bytes - crc_data = keys_page_buf[0:key_len] - crc_data = bytes(crc_data) - crc = zlib.crc32(crc_data, 0xFFFFFFFF) - struct.pack_into(' 15: + raise InputError('Length of key `{}` should be <= 15 characters.'.format(data['key'])) + write_entry(nvs_obj, data['key'], data['type'], data['encoding'], data['value']) + except InputError as e: + print(e) + filedir, filename = os.path.split(args.output) + if filename: + print('\nWarning: NVS binary not created...') + os.remove(args.output) + if is_dir_new and not filedir == os.getcwd(): + print('\nWarning: Output dir not created...') + os.rmdir(filedir) + sys.exit(-2) + + print('\nCreated NVS binary: ===>', args.output) def main(): - parser = argparse.ArgumentParser(description="ESP32 NVS partition generation utility") - nvs_part_gen_group = parser.add_argument_group('To generate NVS partition') - nvs_part_gen_group.add_argument( - "--input", - help="Path to CSV file to parse.", - default=None) - - nvs_part_gen_group.add_argument( - "--output", - help='Path to output converted binary file.', - default=None) - - nvs_part_gen_group.add_argument( - "--size", - help='Size of NVS Partition in bytes (must be multiple of 4096)') - + parser = argparse.ArgumentParser(description='\nESP NVS partition generation utility', formatter_class=argparse.RawTextHelpFormatter) + subparser = parser.add_subparsers(title='Commands', + dest='command', + help='\nRun nvs_partition_gen.py {command} -h for additional help\n\n') + + parser_gen = subparser.add_parser('generate', + help='Generate NVS partition', + formatter_class=argparse.RawTextHelpFormatter) + parser_gen.set_defaults(func=generate) + parser_gen.add_argument('input', + default=None, + help='Path to CSV file to parse') + parser_gen.add_argument('output', + default=None, + help='Path to output NVS binary file') + parser_gen.add_argument('size', + default=None, + help='Size of NVS partition in bytes\ + \n(must be multiple of 4096)') + parser_gen.add_argument('--version', + choices=[1,2], + default=2, + type=int, + help='''Set multipage blob version.\ + \nVersion 1 - Multipage blob support disabled.\ + \nVersion 2 - Multipage blob support enabled.\ + \nDefault: Version 2''') + parser_gen.add_argument('--outdir', + default=os.getcwd(), + help='Output directory to store files created\ + \n(Default: current directory)') + parser_gen_key = subparser.add_parser('generate-key', + help='Generate keys for encryption', + formatter_class=argparse.RawTextHelpFormatter) + parser_gen_key.set_defaults(func=generate_key) + parser_gen_key.add_argument('--keyfile', + default=None, + help='Path to output encryption keys file') + parser_gen_key.add_argument('--outdir', + default=os.getcwd(), + help='Output directory to store files created.\ + \n(Default: current directory)') + parser_encr = subparser.add_parser('encrypt', + help='Generate NVS encrypted partition', + formatter_class=argparse.RawTextHelpFormatter) + parser_encr.set_defaults(func=encrypt) + parser_encr.add_argument('input', + default=None, + help='Path to CSV file to parse') + parser_encr.add_argument('output', + default=None, + help='Path to output NVS binary file') + parser_encr.add_argument('size', + default=None, + help='Size of NVS partition in bytes\ + \n(must be multiple of 4096)') + parser_encr.add_argument('--version', + choices=[1,2], + default=2, + type=int, + help='''Set multipage blob version.\ + \nVersion 1 - Multipage blob support disabled.\ + \nVersion 2 - Multipage blob support enabled.\ + \nDefault: Version 2''') + parser_encr.add_argument('--keygen', + action='store_true', + default=False, + help='Generates key for encrypting NVS partition') + parser_encr.add_argument('--keyfile', + default=None, + help='Path to output encryption keys file') + parser_encr.add_argument('--inputkey', + default=None, + help='File having key for encrypting NVS partition') + parser_encr.add_argument('--outdir', + default=os.getcwd(), + help='Output directory to store files created.\ + \n(Default: current directory)') + parser_decr = subparser.add_parser('decrypt', + help='Decrypt NVS encrypted partition', + formatter_class=argparse.RawTextHelpFormatter) + parser_decr.set_defaults(func=decrypt) + parser_decr.add_argument('input', + default=None, + help='Path to encrypted NVS partition file to parse') + parser_decr.add_argument('key', + default=None, + help='Path to file having keys for decryption') + parser_decr.add_argument('output', + default=None, + help='Path to output decrypted binary file') + parser_decr.add_argument('--outdir', + default=os.getcwd(), + help='Output directory to store files created.\ + \n(Default: current directory)') args = parser.parse_args() - input_filename = args.input - output_filename = args.output - part_size = args.size - version_no = 'v1' - is_key_gen = 'false' - is_encrypt_data = 'false' - key_file = None - - print_arg_str = "Invalid.\nTo generate nvs partition binary --input, --output and --size arguments are mandatory." - - check_input_args(input_filename,output_filename, part_size, is_key_gen, is_encrypt_data, key_file, version_no, print_arg_str) - nvs_part_gen(input_filename, output_filename, part_size, is_key_gen, is_encrypt_data, key_file, version_no) + args.func(args) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/components/nvs_flash/nvs_partition_generator/sample_multipage_blob.csv b/components/nvs_flash/nvs_partition_generator/sample_multipage_blob.csv index 384ac6919..e9546cb41 100644 --- a/components/nvs_flash/nvs_partition_generator/sample_multipage_blob.csv +++ b/components/nvs_flash/nvs_partition_generator/sample_multipage_blob.csv @@ -1,3 +1,4 @@ +# Sample csv file key,type,encoding,value dummyNamespace,namespace,, dummyU8Key,data,u8,127 diff --git a/components/nvs_flash/nvs_partition_generator/sample_singlepage_blob.csv b/components/nvs_flash/nvs_partition_generator/sample_singlepage_blob.csv index c99f513cf..10d3cc635 100644 --- a/components/nvs_flash/nvs_partition_generator/sample_singlepage_blob.csv +++ b/components/nvs_flash/nvs_partition_generator/sample_singlepage_blob.csv @@ -1,3 +1,4 @@ +# Sample csv file key,type,encoding,value dummyNamespace,namespace,, dummyU8Key,data,u8,127 diff --git a/components/nvs_flash/src/compressed_enum_table.hpp b/components/nvs_flash/src/compressed_enum_table.hpp index 319d86a45..dcf9d0999 100644 --- a/components/nvs_flash/src/compressed_enum_table.hpp +++ b/components/nvs_flash/src/compressed_enum_table.hpp @@ -35,7 +35,7 @@ class CompressedEnumTable Tenum get(size_t index) const { - assert(index >= 0 && index < Nitems); + assert(index < Nitems); size_t wordIndex = index / ITEMS_PER_WORD; size_t offset = (index % ITEMS_PER_WORD) * Nbits; @@ -44,7 +44,7 @@ class CompressedEnumTable void set(size_t index, Tenum val) { - assert(index >= 0 && index < Nitems); + assert(index < Nitems); size_t wordIndex = index / ITEMS_PER_WORD; size_t offset = (index % ITEMS_PER_WORD) * Nbits; diff --git a/components/nvs_flash/src/intrusive_list.h b/components/nvs_flash/src/intrusive_list.h index fc92442cd..bb580502e 100644 --- a/components/nvs_flash/src/intrusive_list.h +++ b/components/nvs_flash/src/intrusive_list.h @@ -15,6 +15,7 @@ #define intrusive_list_h #include +#include template class intrusive_list; @@ -229,8 +230,7 @@ class intrusive_list { return mSize == 0; } - - + void clear() { while (mFirst) { @@ -238,6 +238,16 @@ class intrusive_list } } + void clearAndFreeNodes() + { + while (mFirst) { + auto tmp = mFirst; + erase(mFirst); + delete tmp; + } + } + + protected: T* mFirst = nullptr; T* mLast = nullptr; diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index 15f76d1dd..fe58ccd2c 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -11,64 +11,64 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include "sdkconfig.h" #include "nvs.hpp" #include "nvs_flash.h" #include "nvs_storage.hpp" #include "intrusive_list.h" #include "nvs_platform.hpp" +#include "nvs_partition_manager.hpp" #include "esp_partition.h" -#include "sdkconfig.h" +#include +#include "nvs_handle_simple.hpp" +#include "esp_err.h" + +#ifdef LINUX_TARGET +#include "crc.h" +#define ESP_LOGD(...) +#else // LINUX_TARGET +#include -#ifdef ESP_PLATFORM // Uncomment this line to force output from this module // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #include "esp_log.h" static const char* TAG = "nvs"; -#else -#define ESP_LOGD(...) -#endif +#endif // ! LINUX_TARGET -class HandleEntry : public intrusive_list_node -{ - static uint32_t s_nvs_next_handle; +class NVSHandleEntry : public intrusive_list_node { public: - HandleEntry() {} + NVSHandleEntry(nvs::NVSHandleSimple *handle, const char* part_name) + : nvs_handle(handle), + mHandle(++s_nvs_next_handle), + handle_part_name(part_name) { } - HandleEntry(bool readOnly, uint8_t nsIndex, nvs::Storage* StoragePtr) : - mHandle(++s_nvs_next_handle), // Begin the handle value with 1 - mReadOnly(readOnly), - mNsIndex(nsIndex), - mStoragePtr(StoragePtr) - { + ~NVSHandleEntry() { + delete nvs_handle; } - nvs_handle mHandle; - uint8_t mReadOnly; - uint8_t mNsIndex; - nvs::Storage* mStoragePtr; + nvs::NVSHandleSimple *nvs_handle; + nvs_handle_t mHandle; + const char* handle_part_name; +private: + static uint32_t s_nvs_next_handle; }; -#ifdef ESP_PLATFORM -SemaphoreHandle_t nvs::Lock::mSemaphore = NULL; -#endif +uint32_t NVSHandleEntry::s_nvs_next_handle; + +extern "C" void nvs_dump(const char *partName); + +#ifndef LINUX_TARGET +SemaphoreHandle_t nvs::Lock::mSemaphore = nullptr; +#endif // ! LINUX_TARGET using namespace std; using namespace nvs; -static intrusive_list s_nvs_handles; -uint32_t HandleEntry::s_nvs_next_handle; -static intrusive_list s_nvs_storage_list; +static intrusive_list s_nvs_handles; static nvs::Storage* lookup_storage_from_name(const char *name) { - auto it = find_if(begin(s_nvs_storage_list), end(s_nvs_storage_list), [=](Storage& e) -> bool { - return (strcmp(e.getPartName(), name) == 0); - }); - - if (it == end(s_nvs_storage_list)) { - return NULL; - } - return it; + return NVSPartitionManager::get_instance()->lookup_storage_from_name(name); } extern "C" void nvs_dump(const char *partName) @@ -77,359 +77,415 @@ extern "C" void nvs_dump(const char *partName) nvs::Storage* pStorage; pStorage = lookup_storage_from_name(partName); - if (pStorage == NULL) { + if (pStorage == nullptr) { return; } pStorage->debugDump(); - return; } -extern "C" esp_err_t nvs_flash_init_custom(const char *partName, uint32_t baseSector, uint32_t sectorCount) +static esp_err_t close_handles_and_deinit(const char* part_name) { - ESP_LOGD(TAG, "nvs_flash_init_custom partition=%s start=%d count=%d", partName, baseSector, sectorCount); - nvs::Storage* new_storage = NULL; - nvs::Storage* storage = lookup_storage_from_name(partName); - if (storage == NULL) { - new_storage = new nvs::Storage((const char *)partName); - storage = new_storage; - } + auto belongs_to_part = [=](NVSHandleEntry& e) -> bool { + return strncmp(e.nvs_handle->get_partition_name(), part_name, NVS_PART_NAME_MAX_SIZE) == 0; + }; - esp_err_t err = storage->init(baseSector, sectorCount); - if (new_storage != NULL) { - if (err == ESP_OK) { - s_nvs_storage_list.push_back(new_storage); - } else { - delete new_storage; - } + auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), belongs_to_part); + + while (it != end(s_nvs_handles)) { + s_nvs_handles.erase(it); + it = find_if(begin(s_nvs_handles), end(s_nvs_handles), belongs_to_part); } - return err; + + // Deinit partition + return NVSPartitionManager::get_instance()->deinit_partition(part_name); } -#ifdef ESP_PLATFORM -extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) +extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition) { Lock::init(); Lock lock; - nvs::Storage* mStorage; - mStorage = lookup_storage_from_name(part_name); - if (mStorage) { - return ESP_OK; + if (partition == nullptr) { + return ESP_ERR_INVALID_ARG; } - const esp_partition_t* partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, part_name); - if (partition == NULL) { - return ESP_ERR_NOT_FOUND; + NVSPartition *part = new (std::nothrow) NVSPartition(partition); + if (part == nullptr) { + return ESP_ERR_NO_MEM; } - return nvs_flash_init_custom(part_name, partition->address / SPI_FLASH_SEC_SIZE, + esp_err_t init_res = NVSPartitionManager::get_instance()->init_custom(part, + partition->address / SPI_FLASH_SEC_SIZE, partition->size / SPI_FLASH_SEC_SIZE); -} -extern "C" esp_err_t nvs_flash_init(void) -{ - return nvs_flash_init_partition(NVS_DEFAULT_PART_NAME); + if (init_res != ESP_OK) { + delete part; + } + + return init_res; } -extern "C" esp_err_t nvs_flash_deinit_partition(const char* partition_name) +#ifndef LINUX_TARGET +extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) { Lock::init(); Lock lock; - nvs::Storage* storage = lookup_storage_from_name(partition_name); - if (!storage) { - return ESP_ERR_NVS_NOT_INITIALIZED; + return NVSPartitionManager::get_instance()->init_partition(part_name); +} + +extern "C" esp_err_t nvs_flash_init(void) +{ +#ifdef CONFIG_NVS_ENCRYPTION + esp_err_t ret = ESP_FAIL; + const esp_partition_t *key_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + if (key_part == NULL) { + ESP_LOGE(TAG, "CONFIG_NVS_ENCRYPTION is enabled, but no partition with subtype nvs_keys found in the partition table."); + return ret; } - /* Clean up handles related to the storage being deinitialized */ - auto it = s_nvs_handles.begin(); - auto next = it; - while(it != s_nvs_handles.end()) { - next++; - if (it->mStoragePtr == storage) { - ESP_LOGD(TAG, "Deleting handle %d (ns=%d) related to partition \"%s\" (missing call to nvs_close?)", - it->mHandle, it->mNsIndex, partition_name); - s_nvs_handles.erase(it); - delete static_cast(it); + nvs_sec_cfg_t cfg = {}; + ret = nvs_flash_read_security_cfg(key_part, &cfg); + if (ret == ESP_ERR_NVS_KEYS_NOT_INITIALIZED) { + ESP_LOGI(TAG, "NVS key partition empty, generating keys"); + ret = nvs_flash_generate_keys(key_part, &cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to generate keys: [0x%02X] (%s)", ret, esp_err_to_name(ret)); + return ret; } - it = next; + } else if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to read NVS security cfg: [0x%02X] (%s)", ret, esp_err_to_name(ret)); + return ret; } - /* Finally delete the storage itself */ - s_nvs_storage_list.erase(storage); - delete storage; + ret = nvs_flash_secure_init_partition(NVS_DEFAULT_PART_NAME, &cfg); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGE(TAG, "Failed to initialize NVS partition: [0x%02X] (%s)", ret, esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(TAG, "NVS partition \"%s\" is encrypted.", NVS_DEFAULT_PART_NAME); + return ret; +#else // CONFIG_NVS_ENCRYPTION + return nvs_flash_init_partition(NVS_DEFAULT_PART_NAME); +#endif +} - return ESP_OK; +#ifdef CONFIG_NVS_ENCRYPTION +extern "C" esp_err_t nvs_flash_secure_init_partition(const char *part_name, nvs_sec_cfg_t* cfg) +{ + Lock::init(); + Lock lock; + + return NVSPartitionManager::get_instance()->secure_init_partition(part_name, cfg); } -extern "C" esp_err_t nvs_flash_deinit(void) +extern "C" esp_err_t nvs_flash_secure_init(nvs_sec_cfg_t* cfg) { - return nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); + return nvs_flash_secure_init_partition(NVS_DEFAULT_PART_NAME, cfg); } +#endif extern "C" esp_err_t nvs_flash_erase_partition(const char *part_name) { + Lock::init(); + Lock lock; + + // if the partition is initialized, uninitialize it first + if (NVSPartitionManager::get_instance()->lookup_storage_from_name(part_name)) { + esp_err_t err = close_handles_and_deinit(part_name); + + // only hypothetical/future case, deinit_partition() only fails if partition is uninitialized + if (err != ESP_OK) { + return err; + } + } + const esp_partition_t* partition = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, part_name); - if (partition == NULL) { + if (partition == nullptr) { return ESP_ERR_NOT_FOUND; } return esp_partition_erase_range(partition, 0, partition->size); } -extern "C" esp_err_t nvs_flash_erase() +extern "C" esp_err_t nvs_flash_erase_partition_ptr(const esp_partition_t *partition) +{ + Lock::init(); + Lock lock; + + if (partition == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + // if the partition is initialized, uninitialize it first + if (NVSPartitionManager::get_instance()->lookup_storage_from_name(partition->label)) { + const esp_err_t err = close_handles_and_deinit(partition->label); + + // only hypothetical/future case, deinit_partition() only fails if partition is uninitialized + if (err != ESP_OK) { + return err; + } + } + + return esp_partition_erase_range(partition, 0, partition->size); +} + +extern "C" esp_err_t nvs_flash_erase(void) { return nvs_flash_erase_partition(NVS_DEFAULT_PART_NAME); } -#endif +#endif // ! LINUX_TARGET -static esp_err_t nvs_find_ns_handle(nvs_handle handle, HandleEntry& entry) +extern "C" esp_err_t nvs_flash_deinit_partition(const char* partition_name) { - auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](HandleEntry& e) -> bool { - return e.mHandle == handle; + Lock::init(); + Lock lock; + + return close_handles_and_deinit(partition_name); +} + +extern "C" esp_err_t nvs_flash_deinit(void) +{ + return nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); +} + +static esp_err_t nvs_find_ns_handle(nvs_handle_t c_handle, NVSHandleSimple** handle) +{ + auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](NVSHandleEntry& e) -> bool { + return e.mHandle == c_handle; }); if (it == end(s_nvs_handles)) { return ESP_ERR_NVS_INVALID_HANDLE; } - entry = *it; + *handle = it->nvs_handle; return ESP_OK; } -extern "C" esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode open_mode, nvs_handle *out_handle) +extern "C" esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle) { Lock lock; ESP_LOGD(TAG, "%s %s %d", __func__, name, open_mode); - uint8_t nsIndex; - nvs::Storage* sHandle; - - sHandle = lookup_storage_from_name(part_name); - if (sHandle == NULL) { - return ESP_ERR_NVS_PART_NOT_FOUND; - } - esp_err_t err = sHandle->createOrOpenNamespace(name, open_mode == NVS_READWRITE, nsIndex); - if (err != ESP_OK) { - return err; + NVSHandleSimple *handle; + esp_err_t result = NVSPartitionManager::get_instance()->open_handle(part_name, name, open_mode, &handle); + if (result == ESP_OK) { + NVSHandleEntry *entry = new (std::nothrow) NVSHandleEntry(handle, part_name); + if (entry) { + s_nvs_handles.push_back(entry); + *out_handle = entry->mHandle; + } else { + delete handle; + return ESP_ERR_NO_MEM; + } } - HandleEntry *handle_entry = new HandleEntry(open_mode==NVS_READONLY, nsIndex, sHandle); - s_nvs_handles.push_back(handle_entry); - - *out_handle = handle_entry->mHandle; - - return ESP_OK; + return result; } -extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle) +extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle) { - if (s_nvs_storage_list.size() == 0) { - return ESP_ERR_NVS_NOT_INITIALIZED; - } - return nvs_open_from_partition(NVS_DEFAULT_PART_NAME, name, open_mode, out_handle); } -extern "C" void nvs_close(nvs_handle handle) +extern "C" void nvs_close(nvs_handle_t handle) { Lock lock; ESP_LOGD(TAG, "%s %d", __func__, handle); - auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](HandleEntry& e) -> bool { + auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](NVSHandleEntry& e) -> bool { return e.mHandle == handle; }); if (it == end(s_nvs_handles)) { return; } s_nvs_handles.erase(it); - delete static_cast(it); + delete static_cast(it); } -extern "C" esp_err_t nvs_erase_key(nvs_handle handle, const char* key) +extern "C" esp_err_t nvs_erase_key(nvs_handle_t c_handle, const char* key) { Lock lock; ESP_LOGD(TAG, "%s %s\r\n", __func__, key); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - if (entry.mReadOnly) { - return ESP_ERR_NVS_READ_ONLY; - } - return entry.mStoragePtr->eraseItem(entry.mNsIndex, key); + + return handle->erase_item(key); } -extern "C" esp_err_t nvs_erase_all(nvs_handle handle) +extern "C" esp_err_t nvs_erase_all(nvs_handle_t c_handle) { Lock lock; ESP_LOGD(TAG, "%s\r\n", __func__); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - if (entry.mReadOnly) { - return ESP_ERR_NVS_READ_ONLY; - } - return entry.mStoragePtr->eraseNamespace(entry.mNsIndex); + + return handle->erase_all(); } template -static esp_err_t nvs_set(nvs_handle handle, const char* key, T value) +static esp_err_t nvs_set(nvs_handle_t c_handle, const char* key, T value) { Lock lock; ESP_LOGD(TAG, "%s %s %d %d", __func__, key, sizeof(T), (uint32_t) value); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - if (entry.mReadOnly) { - return ESP_ERR_NVS_READ_ONLY; - } - return entry.mStoragePtr->writeItem(entry.mNsIndex, key, value); + + return handle->set_item(key, value); } -extern "C" esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value) +extern "C" esp_err_t nvs_set_i8 (nvs_handle_t handle, const char* key, int8_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_u8 (nvs_handle handle, const char* key, uint8_t value) +extern "C" esp_err_t nvs_set_u8 (nvs_handle_t handle, const char* key, uint8_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_i16 (nvs_handle handle, const char* key, int16_t value) +extern "C" esp_err_t nvs_set_i16 (nvs_handle_t handle, const char* key, int16_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_u16 (nvs_handle handle, const char* key, uint16_t value) +extern "C" esp_err_t nvs_set_u16 (nvs_handle_t handle, const char* key, uint16_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_i32 (nvs_handle handle, const char* key, int32_t value) +extern "C" esp_err_t nvs_set_i32 (nvs_handle_t handle, const char* key, int32_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_u32 (nvs_handle handle, const char* key, uint32_t value) +extern "C" esp_err_t nvs_set_u32 (nvs_handle_t handle, const char* key, uint32_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_i64 (nvs_handle handle, const char* key, int64_t value) +extern "C" esp_err_t nvs_set_i64 (nvs_handle_t handle, const char* key, int64_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_set_u64 (nvs_handle handle, const char* key, uint64_t value) +extern "C" esp_err_t nvs_set_u64 (nvs_handle_t handle, const char* key, uint64_t value) { return nvs_set(handle, key, value); } -extern "C" esp_err_t nvs_commit(nvs_handle handle) +extern "C" esp_err_t nvs_commit(nvs_handle_t c_handle) { Lock lock; // no-op for now, to be used when intermediate cache is added - HandleEntry entry; - return nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); + if (err != ESP_OK) { + return err; + } + return handle->commit(); } -extern "C" esp_err_t nvs_set_str(nvs_handle handle, const char* key, const char* value) +extern "C" esp_err_t nvs_set_str(nvs_handle_t c_handle, const char* key, const char* value) { Lock lock; ESP_LOGD(TAG, "%s %s %s", __func__, key, value); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - return entry.mStoragePtr->writeItem(entry.mNsIndex, nvs::ItemType::SZ, key, value, strlen(value) + 1); + return handle->set_string(key, value); } -extern "C" esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length) +extern "C" esp_err_t nvs_set_blob(nvs_handle_t c_handle, const char* key, const void* value, size_t length) { Lock lock; ESP_LOGD(TAG, "%s %s %d", __func__, key, length); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - return entry.mStoragePtr->writeItem(entry.mNsIndex, nvs::ItemType::BLOB, key, value, length); + return handle->set_blob(key, value, length); } template -static esp_err_t nvs_get(nvs_handle handle, const char* key, T* out_value) +static esp_err_t nvs_get(nvs_handle_t c_handle, const char* key, T* out_value) { Lock lock; ESP_LOGD(TAG, "%s %s %d", __func__, key, sizeof(T)); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } - return entry.mStoragePtr->readItem(entry.mNsIndex, key, *out_value); + return handle->get_item(key, *out_value); } -extern "C" esp_err_t nvs_get_i8 (nvs_handle handle, const char* key, int8_t* out_value) +extern "C" esp_err_t nvs_get_i8 (nvs_handle_t c_handle, const char* key, int8_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_u8 (nvs_handle handle, const char* key, uint8_t* out_value) +extern "C" esp_err_t nvs_get_u8 (nvs_handle_t c_handle, const char* key, uint8_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_i16 (nvs_handle handle, const char* key, int16_t* out_value) +extern "C" esp_err_t nvs_get_i16 (nvs_handle_t c_handle, const char* key, int16_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_u16 (nvs_handle handle, const char* key, uint16_t* out_value) +extern "C" esp_err_t nvs_get_u16 (nvs_handle_t c_handle, const char* key, uint16_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_i32 (nvs_handle handle, const char* key, int32_t* out_value) +extern "C" esp_err_t nvs_get_i32 (nvs_handle_t c_handle, const char* key, int32_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_u32 (nvs_handle handle, const char* key, uint32_t* out_value) +extern "C" esp_err_t nvs_get_u32 (nvs_handle_t c_handle, const char* key, uint32_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_i64 (nvs_handle handle, const char* key, int64_t* out_value) +extern "C" esp_err_t nvs_get_i64 (nvs_handle_t c_handle, const char* key, int64_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -extern "C" esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value) +extern "C" esp_err_t nvs_get_u64 (nvs_handle_t c_handle, const char* key, uint64_t* out_value) { - return nvs_get(handle, key, out_value); + return nvs_get(c_handle, key, out_value); } -static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, const char* key, void* out_value, size_t* length) +static esp_err_t nvs_get_str_or_blob(nvs_handle_t c_handle, nvs::ItemType type, const char* key, void* out_value, size_t* length) { Lock lock; ESP_LOGD(TAG, "%s %s", __func__, key); - HandleEntry entry; - auto err = nvs_find_ns_handle(handle, entry); + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); if (err != ESP_OK) { return err; } size_t dataSize; - err = entry.mStoragePtr->getItemDataSize(entry.mNsIndex, type, key, dataSize); + err = handle->get_item_size(type, key, dataSize); if (err != ESP_OK) { return err; } @@ -445,16 +501,254 @@ static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, cons } *length = dataSize; - return entry.mStoragePtr->readItem(entry.mNsIndex, type, key, out_value, dataSize); + return handle->get_typed_item(type, key, out_value, dataSize); } -extern "C" esp_err_t nvs_get_str(nvs_handle handle, const char* key, char* out_value, size_t* length) +extern "C" esp_err_t nvs_get_str(nvs_handle_t c_handle, const char* key, char* out_value, size_t* length) { - return nvs_get_str_or_blob(handle, nvs::ItemType::SZ, key, out_value, length); + return nvs_get_str_or_blob(c_handle, nvs::ItemType::SZ, key, out_value, length); } -extern "C" esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size_t* length) +extern "C" esp_err_t nvs_get_blob(nvs_handle_t c_handle, const char* key, void* out_value, size_t* length) { - return nvs_get_str_or_blob(handle, nvs::ItemType::BLOB, key, out_value, length); + return nvs_get_str_or_blob(c_handle, nvs::ItemType::BLOB, key, out_value, length); } +extern "C" esp_err_t nvs_get_stats(const char* part_name, nvs_stats_t* nvs_stats) +{ + Lock lock; + nvs::Storage* pStorage; + + if (nvs_stats == nullptr) { + return ESP_ERR_INVALID_ARG; + } + nvs_stats->used_entries = 0; + nvs_stats->free_entries = 0; + nvs_stats->total_entries = 0; + nvs_stats->namespace_count = 0; + + pStorage = lookup_storage_from_name((part_name == nullptr) ? NVS_DEFAULT_PART_NAME : part_name); + if (pStorage == nullptr) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + if(!pStorage->isValid()){ + return ESP_ERR_NVS_INVALID_STATE; + } + + return pStorage->fillStats(*nvs_stats); +} + +extern "C" esp_err_t nvs_get_used_entry_count(nvs_handle_t c_handle, size_t* used_entries) +{ + Lock lock; + if(used_entries == nullptr){ + return ESP_ERR_INVALID_ARG; + } + *used_entries = 0; + + NVSHandleSimple *handle; + auto err = nvs_find_ns_handle(c_handle, &handle); + if (err != ESP_OK) { + return err; + } + + size_t used_entry_count; + err = handle->get_used_entry_count(used_entry_count); + if(err == ESP_OK){ + *used_entries = used_entry_count; + } + return err; +} + +#if (defined CONFIG_NVS_ENCRYPTION) && (!defined LINUX_TARGET) + +extern "C" esp_err_t nvs_flash_generate_keys(const esp_partition_t* partition, nvs_sec_cfg_t* cfg) +{ + auto err = esp_partition_erase_range(partition, 0, partition->size); + if(err != ESP_OK) { + return err; + } + + for(uint8_t cnt = 0; cnt < NVS_KEY_SIZE; cnt++) { + /* Adjacent 16-byte blocks should be different */ + if (((cnt / 16) & 1) == 0) { + cfg->eky[cnt] = 0xff; + cfg->tky[cnt] = 0xee; + } else { + cfg->eky[cnt] = 0x99; + cfg->tky[cnt] = 0x88; + } + } + + /** + * Write key configuration without encryption engine (using raw partition write APIs). + * But the read is decrypted through flash encryption engine. This allows unique NVS encryption configuration, + * as flash encryption key is randomly generated per device. + */ + err = esp_partition_write_raw(partition, 0, cfg->eky, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + /* Write without encryption, see note above */ + err = esp_partition_write_raw(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, 0, cfg->eky, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + uint32_t crc_calc = crc32_le(0xffffffff, cfg->eky, NVS_KEY_SIZE); + crc_calc = crc32_le(crc_calc, cfg->tky, NVS_KEY_SIZE); + + uint8_t crc_wr[16]; + memset(crc_wr, 0xff, sizeof(crc_wr)); + memcpy(crc_wr, &crc_calc, 4); + + err = esp_partition_write(partition, 2 * NVS_KEY_SIZE, crc_wr, sizeof(crc_wr)); + if(err != ESP_OK) { + return err; + } + + return ESP_OK; + +} + +extern "C" esp_err_t nvs_flash_read_security_cfg(const esp_partition_t* partition, nvs_sec_cfg_t* cfg) +{ + uint8_t eky_raw[NVS_KEY_SIZE], tky_raw[NVS_KEY_SIZE]; + uint32_t crc_raw, crc_read, crc_calc; + + auto check_if_initialized = [](uint8_t* eky, uint8_t* tky, uint32_t crc) { + uint8_t cnt = 0; + while(cnt < NVS_KEY_SIZE && eky[cnt] == 0xff && tky[cnt] == 0xff) cnt++; + + if(cnt == NVS_KEY_SIZE && crc == 0xffffffff) { + return false; + } + return true; + }; + + auto err = esp_partition_read_raw(partition, 0, eky_raw, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read_raw(partition, NVS_KEY_SIZE, tky_raw, NVS_KEY_SIZE); + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read_raw(partition, 2 * NVS_KEY_SIZE, &crc_raw, 4); + if(err != ESP_OK) { + return err; + } + + if(!check_if_initialized(eky_raw, tky_raw, crc_raw)) { + /* This is an uninitialized key partition*/ + return ESP_ERR_NVS_KEYS_NOT_INITIALIZED; + } + + err = esp_partition_read(partition, 0, cfg->eky, NVS_KEY_SIZE); + + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); + + if(err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, 2 * NVS_KEY_SIZE, &crc_read, 4); + + if(err != ESP_OK) { + return err; + } + + crc_calc = crc32_le(0xffffffff, cfg->eky, NVS_KEY_SIZE); + crc_calc = crc32_le(crc_calc, cfg->tky, NVS_KEY_SIZE); + + if(crc_calc != crc_read) { + if(!check_if_initialized(cfg->eky, cfg->tky, crc_read)) { + /* This is an uninitialized key partition*/ + return ESP_ERR_NVS_KEYS_NOT_INITIALIZED; + } + return ESP_ERR_NVS_CORRUPT_KEY_PART; + } + + return ESP_OK; +} + +#endif + +static nvs_iterator_t create_iterator(nvs::Storage *storage, nvs_type_t type) +{ + nvs_iterator_t it = (nvs_iterator_t)calloc(1, sizeof(nvs_opaque_iterator_t)); + if (it == nullptr) { + return nullptr; + } + + it->storage = storage; + it->type = type; + + return it; +} + +extern "C" nvs_iterator_t nvs_entry_find(const char *part_name, const char *namespace_name, nvs_type_t type) +{ + Lock lock; + nvs::Storage *pStorage; + + pStorage = lookup_storage_from_name(part_name); + if (pStorage == nullptr) { + return nullptr; + } + + nvs_iterator_t it = create_iterator(pStorage, type); + if (it == nullptr) { + return nullptr; + } + + bool entryFound = pStorage->findEntry(it, namespace_name); + if (!entryFound) { + free(it); + return nullptr; + } + + return it; +} + +extern "C" nvs_iterator_t nvs_entry_next(nvs_iterator_t it) +{ + Lock lock; + assert(it); + + bool entryFound = it->storage->nextEntry(it); + if (!entryFound) { + free(it); + return nullptr; + } + + return it; +} + +extern "C" void nvs_entry_info(nvs_iterator_t it, nvs_entry_info_t *out_info) +{ + *out_info = it->entry_info; +} + +extern "C" void nvs_release_iterator(nvs_iterator_t it) +{ + free(it); +} diff --git a/components/nvs_flash/src/nvs_cxx_api.cpp b/components/nvs_flash/src/nvs_cxx_api.cpp new file mode 100644 index 000000000..65fb1128b --- /dev/null +++ b/components/nvs_flash/src/nvs_cxx_api.cpp @@ -0,0 +1,68 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "nvs_partition_manager.hpp" +#include "nvs_handle.hpp" +#include "nvs_handle_simple.hpp" +#include "nvs_handle_locked.hpp" +#include "nvs_platform.hpp" + +namespace nvs { + +std::unique_ptr open_nvs_handle_from_partition(const char *partition_name, + const char *ns_name, + nvs_open_mode_t open_mode, + esp_err_t *err) +{ + if (partition_name == nullptr || ns_name == nullptr) { + if (err) { + *err = ESP_ERR_INVALID_ARG; + } + return nullptr; + } + + Lock lock; + + NVSHandleSimple *handle_simple; + esp_err_t result = nvs::NVSPartitionManager::get_instance()-> + open_handle(partition_name, ns_name, open_mode, &handle_simple); + + if (err) { + *err = result; + } + + if (result != ESP_OK) { + return nullptr; + } + + NVSHandleLocked *locked_handle = new (nothrow) NVSHandleLocked(handle_simple); + + if (!locked_handle) { + if (err) { + *err = ESP_ERR_NO_MEM; + } + delete handle_simple; + return nullptr; + } + + return std::unique_ptr(locked_handle); +} + +std::unique_ptr open_nvs_handle(const char *ns_name, + nvs_open_mode_t open_mode, + esp_err_t *err) +{ + return open_nvs_handle_from_partition(NVS_DEFAULT_PART_NAME, ns_name, open_mode, err); +} + +} // namespace nvs diff --git a/components/nvs_flash/src/nvs_encrypted_partition.cpp b/components/nvs_flash/src/nvs_encrypted_partition.cpp new file mode 100644 index 000000000..26e8a3314 --- /dev/null +++ b/components/nvs_flash/src/nvs_encrypted_partition.cpp @@ -0,0 +1,121 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "nvs_encrypted_partition.hpp" +#include "nvs_types.hpp" + +namespace nvs { + +NVSEncryptedPartition::NVSEncryptedPartition(const esp_partition_t *partition) + : NVSPartition(partition) { } + +esp_err_t NVSEncryptedPartition::init(nvs_sec_cfg_t* cfg) +{ + uint8_t* eky = reinterpret_cast(cfg); + + mbedtls_aes_xts_init(&mEctxt); + mbedtls_aes_xts_init(&mDctxt); + + if (mbedtls_aes_xts_setkey_enc(&mEctxt, eky, 2 * NVS_KEY_SIZE * 8) != 0) { + return ESP_ERR_NVS_XTS_CFG_FAILED; + } + + if (mbedtls_aes_xts_setkey_dec(&mDctxt, eky, 2 * NVS_KEY_SIZE * 8) != 0) { + return ESP_ERR_NVS_XTS_CFG_FAILED; + } + + return ESP_OK; +} + +esp_err_t NVSEncryptedPartition::read(size_t src_offset, void* dst, size_t size) +{ + /** Currently upper layer of NVS reads entries one by one even for variable size + * multi-entry data types. So length should always be equal to size of an entry.*/ + if (size != sizeof(Item)) return ESP_ERR_INVALID_SIZE; + + // read data + esp_err_t read_result = esp_partition_read(mESPPartition, src_offset, dst, size); + if (read_result != ESP_OK) { + return read_result; + } + + // decrypt data + //sector num required as an arr by mbedtls. Should have been just uint64/32. + uint8_t data_unit[16]; + + uint32_t relAddr = src_offset; + + memset(data_unit, 0, sizeof(data_unit)); + + memcpy(data_unit, &relAddr, sizeof(relAddr)); + + uint8_t *destination = reinterpret_cast(dst); + + if (mbedtls_aes_crypt_xts(&mDctxt, MBEDTLS_AES_DECRYPT, size, data_unit, destination, destination) != 0) { + return ESP_ERR_NVS_XTS_DECR_FAILED; + } + + return ESP_OK; +} + +esp_err_t NVSEncryptedPartition::write(size_t addr, const void* src, size_t size) +{ + if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) return ESP_ERR_INVALID_SIZE; + + // copy data to buffer for encryption + uint8_t* buf = new (std::nothrow) uint8_t [size]; + + if (!buf) return ESP_ERR_NO_MEM; + + memcpy(buf, src, size); + + // encrypt data + uint8_t entrySize = sizeof(Item); + + //sector num required as an arr by mbedtls. Should have been just uint64/32. + uint8_t data_unit[16]; + + /* Use relative address instead of absolute address (relocatable), so that host-generated + * encrypted nvs images can be used*/ + uint32_t relAddr = addr; + + memset(data_unit, 0, sizeof(data_unit)); + + for(uint8_t entry = 0; entry < (size/entrySize); entry++) + { + uint32_t offset = entry * entrySize; + uint32_t *addr_loc = (uint32_t*) &data_unit[0]; + + *addr_loc = relAddr + offset; + if (mbedtls_aes_crypt_xts(&mEctxt, + MBEDTLS_AES_ENCRYPT, + entrySize, + data_unit, + buf + offset, + buf + offset) != 0) { + delete buf; + return ESP_ERR_NVS_XTS_ENCR_FAILED; + } + } + + // write data + esp_err_t result = esp_partition_write(mESPPartition, addr, buf, size); + + delete buf; + + return result; +} + +} // nvs diff --git a/components/nvs_flash/src/nvs_encrypted_partition.hpp b/components/nvs_flash/src/nvs_encrypted_partition.hpp new file mode 100644 index 000000000..76c0607a0 --- /dev/null +++ b/components/nvs_flash/src/nvs_encrypted_partition.hpp @@ -0,0 +1,43 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NVS_ENCRYPTED_PARTITION_HPP_ +#define NVS_ENCRYPTED_PARTITION_HPP_ + +#include "mbedtls/aes.h" +#include "nvs_flash.h" +#include "nvs_partition.hpp" + +namespace nvs { + +class NVSEncryptedPartition : public NVSPartition { +public: + NVSEncryptedPartition(const esp_partition_t *partition); + + virtual ~NVSEncryptedPartition() { } + + esp_err_t init(nvs_sec_cfg_t* cfg); + + esp_err_t read(size_t src_offset, void* dst, size_t size) override; + + esp_err_t write(size_t dst_offset, const void* src, size_t size) override; + +protected: + mbedtls_aes_xts_context mEctxt; + mbedtls_aes_xts_context mDctxt; +}; + +} // nvs + +#endif // NVS_ENCRYPTED_PARTITION_HPP_ diff --git a/components/nvs_flash/src/nvs_handle_locked.cpp b/components/nvs_flash/src/nvs_handle_locked.cpp new file mode 100644 index 000000000..89e5cbbca --- /dev/null +++ b/components/nvs_flash/src/nvs_handle_locked.cpp @@ -0,0 +1,82 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "nvs_handle_locked.hpp" + +namespace nvs { + +NVSHandleLocked::NVSHandleLocked(NVSHandleSimple *handle) : handle(handle) { + Lock::init(); +} + +NVSHandleLocked::~NVSHandleLocked() { + Lock lock; + delete handle; +} + +esp_err_t NVSHandleLocked::set_string(const char *key, const char* str) { + Lock lock; + return handle->set_string(key, str); +} + +esp_err_t NVSHandleLocked::set_blob(const char *key, const void* blob, size_t len) { + Lock lock; + return handle->set_blob(key, blob, len); +} + +esp_err_t NVSHandleLocked::get_string(const char *key, char* out_str, size_t len) { + Lock lock; + return handle->get_string(key, out_str, len); +} + +esp_err_t NVSHandleLocked::get_blob(const char *key, void* out_blob, size_t len) { + Lock lock; + return handle->get_blob(key, out_blob, len); +} + +esp_err_t NVSHandleLocked::get_item_size(ItemType datatype, const char *key, size_t &size) { + Lock lock; + return handle->get_item_size(datatype, key, size); +} + +esp_err_t NVSHandleLocked::erase_item(const char* key) { + Lock lock; + return handle->erase_item(key); +} + +esp_err_t NVSHandleLocked::erase_all() { + Lock lock; + return handle->erase_all(); +} + +esp_err_t NVSHandleLocked::commit() { + Lock lock; + return handle->commit(); +} + +esp_err_t NVSHandleLocked::get_used_entry_count(size_t& usedEntries) { + Lock lock; + return handle->get_used_entry_count(usedEntries); +} + +esp_err_t NVSHandleLocked::set_typed_item(ItemType datatype, const char *key, const void* data, size_t dataSize) { + Lock lock; + return handle->set_typed_item(datatype, key, data, dataSize); +} + +esp_err_t NVSHandleLocked::get_typed_item(ItemType datatype, const char *key, void* data, size_t dataSize) { + Lock lock; + return handle->get_typed_item(datatype, key, data, dataSize); +} + +} // namespace nvs diff --git a/components/nvs_flash/src/nvs_handle_locked.hpp b/components/nvs_flash/src/nvs_handle_locked.hpp new file mode 100644 index 000000000..39d514c7a --- /dev/null +++ b/components/nvs_flash/src/nvs_handle_locked.hpp @@ -0,0 +1,66 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef NVS_HANDLE_LOCKED_HPP_ +#define NVS_HANDLE_LOCKED_HPP_ + +#include "nvs_handle_simple.hpp" + +namespace nvs { + +/** + * @brief A class which behaves the same as NVSHandleSimple, except that all public member functions are locked. + * + * This class follows the decorator design pattern. The reason why we don't want locks in NVSHandleSimple is that + * NVSHandleSimple can also be used by the C-API which locks its public functions already. + * Thus, we avoid double-locking. + * + * @note this class becomes responsible for its internal NVSHandleSimple object, i.e. it deletes the handle object on + * destruction + */ +class NVSHandleLocked : public NVSHandle { +public: + NVSHandleLocked(NVSHandleSimple *handle); + + virtual ~NVSHandleLocked(); + + esp_err_t set_string(const char *key, const char* str) override; + + esp_err_t set_blob(const char *key, const void* blob, size_t len) override; + + esp_err_t get_string(const char *key, char* out_str, size_t len) override; + + esp_err_t get_blob(const char *key, void* out_blob, size_t len) override; + + esp_err_t get_item_size(ItemType datatype, const char *key, size_t &size) override; + + esp_err_t erase_item(const char* key) override; + + esp_err_t erase_all() override; + + esp_err_t commit() override; + + esp_err_t get_used_entry_count(size_t& usedEntries) override; + +protected: + esp_err_t set_typed_item(ItemType datatype, const char *key, const void* data, size_t dataSize) override; + + esp_err_t get_typed_item(ItemType datatype, const char *key, void* data, size_t dataSize) override; + +private: + NVSHandleSimple *handle; +}; + +} // namespace nvs + +#endif // NVS_HANDLE_LOCKED_HPP_ diff --git a/components/nvs_flash/src/nvs_handle_simple.cpp b/components/nvs_flash/src/nvs_handle_simple.cpp new file mode 100644 index 000000000..348e197b7 --- /dev/null +++ b/components/nvs_flash/src/nvs_handle_simple.cpp @@ -0,0 +1,137 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include "nvs_handle.hpp" +#include "nvs_partition_manager.hpp" + +namespace nvs { + +NVSHandleSimple::~NVSHandleSimple() { + NVSPartitionManager::get_instance()->close_handle(this); +} + +esp_err_t NVSHandleSimple::set_typed_item(ItemType datatype, const char *key, const void* data, size_t dataSize) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + if (mReadOnly) return ESP_ERR_NVS_READ_ONLY; + + return mStoragePtr->writeItem(mNsIndex, datatype, key, data, dataSize); +} + +esp_err_t NVSHandleSimple::get_typed_item(ItemType datatype, const char *key, void* data, size_t dataSize) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + return mStoragePtr->readItem(mNsIndex, datatype, key, data, dataSize); +} + +esp_err_t NVSHandleSimple::set_string(const char *key, const char* str) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + if (mReadOnly) return ESP_ERR_NVS_READ_ONLY; + + return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::SZ, key, str, strlen(str) + 1); +} + +esp_err_t NVSHandleSimple::set_blob(const char *key, const void* blob, size_t len) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + if (mReadOnly) return ESP_ERR_NVS_READ_ONLY; + + return mStoragePtr->writeItem(mNsIndex, nvs::ItemType::BLOB, key, blob, len); +} + +esp_err_t NVSHandleSimple::get_string(const char *key, char* out_str, size_t len) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + return mStoragePtr->readItem(mNsIndex, nvs::ItemType::SZ, key, out_str, len); +} + +esp_err_t NVSHandleSimple::get_blob(const char *key, void* out_blob, size_t len) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + return mStoragePtr->readItem(mNsIndex, nvs::ItemType::BLOB, key, out_blob, len); +} + +esp_err_t NVSHandleSimple::get_item_size(ItemType datatype, const char *key, size_t &size) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + return mStoragePtr->getItemDataSize(mNsIndex, datatype, key, size); +} + +esp_err_t NVSHandleSimple::erase_item(const char* key) +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + if (mReadOnly) return ESP_ERR_NVS_READ_ONLY; + + return mStoragePtr->eraseItem(mNsIndex, key); +} + +esp_err_t NVSHandleSimple::erase_all() +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + if (mReadOnly) return ESP_ERR_NVS_READ_ONLY; + + return mStoragePtr->eraseNamespace(mNsIndex); +} + +esp_err_t NVSHandleSimple::commit() +{ + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + return ESP_OK; +} + +esp_err_t NVSHandleSimple::get_used_entry_count(size_t& used_entries) +{ + used_entries = 0; + + if (!valid) return ESP_ERR_NVS_INVALID_HANDLE; + + size_t used_entry_count; + esp_err_t err = mStoragePtr->calcEntriesInNamespace(mNsIndex, used_entry_count); + if(err == ESP_OK){ + used_entries = used_entry_count; + } + return err; +} + +void NVSHandleSimple::debugDump() { + return mStoragePtr->debugDump(); +} + +esp_err_t NVSHandleSimple::fillStats(nvs_stats_t& nvsStats) { + return mStoragePtr->fillStats(nvsStats); +} + +esp_err_t NVSHandleSimple::calcEntriesInNamespace(size_t& usedEntries) { + return mStoragePtr->calcEntriesInNamespace(mNsIndex, usedEntries); +} + +bool NVSHandleSimple::findEntry(nvs_opaque_iterator_t* it, const char* name) { + return mStoragePtr->findEntry(it, name); +} + +bool NVSHandleSimple::nextEntry(nvs_opaque_iterator_t* it) { + return mStoragePtr->nextEntry(it); +} + +const char *NVSHandleSimple::get_partition_name() const { + return mStoragePtr->getPartName(); +} + +} diff --git a/components/nvs_flash/src/nvs_handle_simple.hpp b/components/nvs_flash/src/nvs_handle_simple.hpp new file mode 100644 index 000000000..0a20aa4e8 --- /dev/null +++ b/components/nvs_flash/src/nvs_handle_simple.hpp @@ -0,0 +1,107 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef NVS_HANDLE_SIMPLE_HPP_ +#define NVS_HANDLE_SIMPLE_HPP_ + +#include "intrusive_list.h" +#include "nvs_storage.hpp" +#include "nvs_platform.hpp" + +#include "nvs_handle.hpp" + +namespace nvs { + +/** + * @brief This class implements NVSHandle according to the ESP32's flash and partitioning scheme. + * + * It is used by both the C API and the C++ API. The main responsibility is to check whether the handle is valid + * and in the right read/write mode and then forward the calls to the storage object. + * + * For more details about the general member functions, see nvs_handle.hpp. + */ +class NVSHandleSimple : public intrusive_list_node, public NVSHandle { + friend class NVSPartitionManager; +public: + NVSHandleSimple(bool readOnly, uint8_t nsIndex, Storage *StoragePtr) : + mStoragePtr(StoragePtr), + mNsIndex(nsIndex), + mReadOnly(readOnly), + valid(1) + { } + + ~NVSHandleSimple(); + + esp_err_t set_typed_item(ItemType datatype, const char *key, const void *data, size_t dataSize) override; + + esp_err_t get_typed_item(ItemType datatype, const char *key, void *data, size_t dataSize) override; + + esp_err_t set_string(const char *key, const char *str) override; + + esp_err_t set_blob(const char *key, const void *blob, size_t len) override; + + esp_err_t get_string(const char *key, char *out_str, size_t len) override; + + esp_err_t get_blob(const char *key, void *out_blob, size_t len) override; + + esp_err_t get_item_size(ItemType datatype, const char *key, size_t &size) override; + + esp_err_t erase_item(const char *key) override; + + esp_err_t erase_all() override; + + esp_err_t commit() override; + + esp_err_t get_used_entry_count(size_t &usedEntries) override; + + esp_err_t getItemDataSize(ItemType datatype, const char *key, size_t &dataSize); + + void debugDump(); + + esp_err_t fillStats(nvs_stats_t &nvsStats); + + esp_err_t calcEntriesInNamespace(size_t &usedEntries); + + bool findEntry(nvs_opaque_iterator_t *it, const char *name); + + bool nextEntry(nvs_opaque_iterator_t *it); + + const char *get_partition_name() const; + +private: + /** + * The underlying storage's object. + */ + Storage *mStoragePtr; + + /** + * Numeric representation of the namespace as it is saved in flash (see README.rst for further details). + */ + uint8_t mNsIndex; + + /** + * Whether this handle is marked as read-only or read-write. + * 0 indicates read-only, any other value read-write. + */ + uint8_t mReadOnly; + + /** + * Indicates the validity of this handle. + * Upon opening, a handle is valid. It becomes invalid if the underlying storage is de-initialized. + */ + uint8_t valid; +}; + +} // nvs + +#endif // NVS_HANDLE_SIMPLE_HPP_ diff --git a/components/nvs_flash/src/nvs_item_hash_list.cpp b/components/nvs_flash/src/nvs_item_hash_list.cpp index 845dd0910..7e1c1241a 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.cpp +++ b/components/nvs_flash/src/nvs_item_hash_list.cpp @@ -20,7 +20,7 @@ namespace nvs HashList::HashList() { } - + void HashList::clear() { for (auto it = mBlockList.begin(); it != mBlockList.end();) { @@ -30,7 +30,7 @@ void HashList::clear() delete static_cast(tmp); } } - + HashList::~HashList() { clear(); @@ -42,7 +42,7 @@ HashList::HashListBlock::HashListBlock() "cache block size calculation incorrect"); } -void HashList::insert(const Item& item, size_t index) +esp_err_t HashList::insert(const Item& item, size_t index) { const uint32_t hash_24 = item.calculateCrc32WithoutValue() & 0xffffff; // add entry to the end of last block if possible @@ -50,29 +50,41 @@ void HashList::insert(const Item& item, size_t index) auto& block = mBlockList.back(); if (block.mCount < HashListBlock::ENTRY_COUNT) { block.mNodes[block.mCount++] = HashListNode(hash_24, index); - return; + return ESP_OK; } } // if the above failed, create a new block and add entry to it - HashListBlock* newBlock = new HashListBlock; + HashListBlock* newBlock = new (std::nothrow) HashListBlock; + + if (!newBlock) return ESP_ERR_NO_MEM; + mBlockList.push_back(newBlock); newBlock->mNodes[0] = HashListNode(hash_24, index); newBlock->mCount++; + + return ESP_OK; } void HashList::erase(size_t index, bool itemShouldExist) { for (auto it = mBlockList.begin(); it != mBlockList.end();) { bool haveEntries = false; + bool foundIndex = false; for (size_t i = 0; i < it->mCount; ++i) { if (it->mNodes[i].mIndex == index) { it->mNodes[i].mIndex = 0xff; - return; + foundIndex = true; + /* found the item and removed it */ } if (it->mNodes[i].mIndex != 0xff) { haveEntries = true; } + if (haveEntries && foundIndex) { + /* item was found, and HashListBlock still has some items */ + return; + } } + /* no items left in HashListBlock, can remove */ if (!haveEntries) { auto tmp = it; ++it; @@ -81,6 +93,10 @@ void HashList::erase(size_t index, bool itemShouldExist) } else { ++it; } + if (foundIndex) { + /* item was found and empty HashListBlock was removed */ + return; + } } if (itemShouldExist) { assert(false && "item should have been present in cache"); diff --git a/components/nvs_flash/src/nvs_item_hash_list.hpp b/components/nvs_flash/src/nvs_item_hash_list.hpp index e759cd818..ca21c92c1 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.hpp +++ b/components/nvs_flash/src/nvs_item_hash_list.hpp @@ -27,16 +27,16 @@ class HashList public: HashList(); ~HashList(); - - void insert(const Item& item, size_t index); + + esp_err_t insert(const Item& item, size_t index); void erase(const size_t index, bool itemShouldExist=true); size_t find(size_t start, const Item& item); void clear(); - + private: HashList(const HashList& other); const HashList& operator= (const HashList& rhs); - + protected: struct HashListNode { diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index acbae5358..2f72a2090 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -12,28 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "nvs_page.hpp" -#include +#if defined(LINUX_TARGET) +#include "crc.h" +#else +#include +#endif #include #include namespace nvs { +Page::Page() : mPartition(nullptr) { } + uint32_t Page::Header::calculateCrc32() { - return crc32_le(0xffffffff, + return esp_rom_crc32_le(0xffffffff, reinterpret_cast(this) + offsetof(Header, mSeqNumber), offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber)); } -esp_err_t Page::load(uint32_t sectorNumber) +esp_err_t Page::load(Partition *partition, uint32_t sectorNumber) { + if (partition == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + mPartition = partition; mBaseAddress = sectorNumber * SEC_SIZE; mUsedEntryCount = 0; mErasedEntryCount = 0; Header header; - auto rc = spi_flash_read(mBaseAddress, &header, sizeof(header)); + auto rc = mPartition->read_raw(mBaseAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -42,24 +53,35 @@ esp_err_t Page::load(uint32_t sectorNumber) mState = header.mState; // check if the whole page is really empty // reading the whole page takes ~40 times less than erasing it - uint32_t line[8]; - for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += sizeof(line)) { - rc = spi_flash_read(mBaseAddress + i, line, sizeof(line)); + const int BLOCK_SIZE = 128; + uint32_t* block = new (std::nothrow) uint32_t[BLOCK_SIZE]; + + if (!block) return ESP_ERR_NO_MEM; + + for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += 4 * BLOCK_SIZE) { + rc = mPartition->read_raw(mBaseAddress + i, block, 4 * BLOCK_SIZE); if (rc != ESP_OK) { mState = PageState::INVALID; + delete[] block; return rc; } - if (std::any_of(line, line + 4, [](uint32_t val) -> bool { return val != 0xffffffff; })) { + if (std::any_of(block, block + BLOCK_SIZE, [](uint32_t val) -> bool { return val != 0xffffffff; })) { // page isn't as empty after all, mark it as corrupted mState = PageState::CORRUPT; break; } } + delete[] block; } else if (header.mCrc32 != header.calculateCrc32()) { header.mState = PageState::CORRUPT; } else { mState = header.mState; mSeqNumber = header.mSeqNumber; + if(header.mVersion < NVS_VERSION) { + return ESP_ERR_NVS_NEW_VERSION_FOUND; + } else { + mVersion = header.mVersion; + } } switch (mState) { @@ -82,13 +104,16 @@ esp_err_t Page::load(uint32_t sectorNumber) esp_err_t Page::writeEntry(const Item& item) { - auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), &item, sizeof(item)); - if (rc != ESP_OK) { + esp_err_t err; + + err = mPartition->write(getEntryAddress(mNextFreeEntry), &item, sizeof(item)); + + if (err != ESP_OK) { mState = PageState::INVALID; - return rc; + return err; } - auto err = alterEntryState(mNextFreeEntry, EntryState::WRITTEN); + err = alterEntryState(mNextFreeEntry, EntryState::WRITTEN); if (err != ESP_OK) { return err; } @@ -102,17 +127,18 @@ esp_err_t Page::writeEntry(const Item& item) return ESP_OK; } - + esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) { assert(size % ENTRY_SIZE == 0); assert(mNextFreeEntry != INVALID_ENTRY); assert(mFirstUsedEntry != INVALID_ENTRY); const uint16_t count = size / ENTRY_SIZE; - + const uint8_t* buf = data; - -#ifdef ESP_PLATFORM + +#if !defined LINUX_TARGET + // TODO: check whether still necessary with esp_partition* API /* On the ESP32, data can come from DROM, which is not accessible by spi_flash_write * function. To work around this, we copy the data to heap if it came from DROM. * Hopefully this won't happen very often in practice. For data from DRAM, we should @@ -127,13 +153,15 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) } memcpy((void*)buf, data, size); } -#endif //ESP_PLATFORM - auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), buf, size); -#ifdef ESP_PLATFORM +#endif // ! LINUX_TARGET + + auto rc = mPartition->write(getEntryAddress(mNextFreeEntry), buf, size); + +#if !defined LINUX_TARGET if (buf != data) { free((void*)buf); } -#endif //ESP_PLATFORM +#endif // ! LINUX_TARGET if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -147,15 +175,15 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) return ESP_OK; } -esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize) +esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx) { Item item; esp_err_t err; - + if (mState == PageState::INVALID) { return ESP_ERR_NVS_INVALID_STATE; } - + if (mState == PageState::UNINITIALIZED) { err = initialize(); if (err != ESP_OK) { @@ -171,21 +199,22 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c if (keySize > Item::MAX_KEY_LENGTH) { return ESP_ERR_NVS_KEY_TOO_LONG; } - - if (dataSize > Page::BLOB_MAX_SIZE) { + + if (dataSize > Page::CHUNK_MAX_SIZE) { return ESP_ERR_NVS_VALUE_TOO_LONG; } size_t totalSize = ENTRY_SIZE; size_t entriesCount = 1; - if (datatype == ItemType::SZ || datatype == ItemType::BLOB) { + if (isVariableLengthType(datatype)) { size_t roundedSize = (dataSize + ENTRY_SIZE - 1) & ~(ENTRY_SIZE - 1); totalSize += roundedSize; entriesCount += roundedSize / ENTRY_SIZE; } // primitive types should fit into one entry - assert(totalSize == ENTRY_SIZE || datatype == ItemType::BLOB || datatype == ItemType::SZ); + assert(totalSize == ENTRY_SIZE || + isVariableLengthType(datatype)); if (mNextFreeEntry == INVALID_ENTRY || mNextFreeEntry + entriesCount > ENTRY_COUNT) { // page will not fit this amount of data @@ -194,10 +223,14 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c // write first item size_t span = (totalSize + ENTRY_SIZE - 1) / ENTRY_SIZE; - item = Item(nsIndex, datatype, span, key); - mHashList.insert(item, mNextFreeEntry); + item = Item(nsIndex, datatype, span, key, chunkIdx); + err = mHashList.insert(item, mNextFreeEntry); - if (datatype != ItemType::SZ && datatype != ItemType::BLOB) { + if (err != ESP_OK) { + return err; + } + + if (!isVariableLengthType(datatype)) { memcpy(item.data, data, dataSize); item.crc32 = item.calculateCrc32(); err = writeEntry(item); @@ -208,7 +241,7 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c const uint8_t* src = reinterpret_cast(data); item.varLength.dataCrc32 = Item::calculateCrc32(src, dataSize); item.varLength.dataSize = dataSize; - item.varLength.reserved2 = 0xffff; + item.varLength.reserved = 0xffff; item.crc32 = item.calculateCrc32(); err = writeEntry(item); if (err != ESP_OK) { @@ -222,36 +255,36 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c return err; } } - + size_t tail = dataSize - left; if (tail > 0) { - std::fill_n(item.rawData, ENTRY_SIZE / 4, 0xffffffff); + std::fill_n(item.rawData, ENTRY_SIZE, 0xff); memcpy(item.rawData, static_cast(data) + left, tail); err = writeEntry(item); if (err != ESP_OK) { return err; } } - + } return ESP_OK; } -esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize) +esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart) { size_t index = 0; Item item; - + if (mState == PageState::INVALID) { return ESP_ERR_NVS_INVALID_STATE; } - - esp_err_t rc = findItem(nsIndex, datatype, key, index, item); + + esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart); if (rc != ESP_OK) { return rc; } - if (datatype != ItemType::SZ && datatype != ItemType::BLOB) { + if (!isVariableLengthType(datatype)) { if (dataSize != getAlignmentForType(datatype)) { return ESP_ERR_NVS_TYPE_MISMATCH; } @@ -288,22 +321,74 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo return ESP_OK; } -esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key) +esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart) { size_t index = 0; Item item; - esp_err_t rc = findItem(nsIndex, datatype, key, index, item); + + if (mState == PageState::INVALID) { + return ESP_ERR_NVS_INVALID_STATE; + } + + esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart); + if (rc != ESP_OK) { + return rc; + } + + if (!isVariableLengthType(datatype)) { + if (dataSize != getAlignmentForType(datatype)) { + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + if (memcmp(data, item.data, dataSize)) { + return ESP_ERR_NVS_CONTENT_DIFFERS; + } + return ESP_OK; + } + + if (dataSize < static_cast(item.varLength.dataSize)) { + return ESP_ERR_NVS_INVALID_LENGTH; + } + + const uint8_t* dst = reinterpret_cast(data); + size_t left = item.varLength.dataSize; + for (size_t i = index + 1; i < index + item.span; ++i) { + Item ditem; + rc = readEntry(i, ditem); + if (rc != ESP_OK) { + return rc; + } + size_t willCopy = ENTRY_SIZE; + willCopy = (left < willCopy)?left:willCopy; + if (memcmp(dst, ditem.rawData, willCopy)) { + return ESP_ERR_NVS_CONTENT_DIFFERS; + } + left -= willCopy; + dst += willCopy; + } + if (Item::calculateCrc32(reinterpret_cast(data), item.varLength.dataSize) != item.varLength.dataCrc32) { + return ESP_ERR_NVS_NOT_FOUND; + } + + return ESP_OK; +} + +esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx, VerOffset chunkStart) +{ + size_t index = 0; + Item item; + esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart); if (rc != ESP_OK) { return rc; } return eraseEntryAndSpan(index); } -esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key) +esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx, VerOffset chunkStart) { size_t index = 0; Item item; - return findItem(nsIndex, datatype, key, index, item); + return findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart); } esp_err_t Page::eraseEntryAndSpan(size_t index) @@ -406,7 +491,11 @@ esp_err_t Page::copyItems(Page& other) return err; } - other.mHashList.insert(entry, other.mNextFreeEntry); + err = other.mHashList.insert(entry, other.mNextFreeEntry); + if (err != ESP_OK) { + return err; + } + err = other.writeEntry(entry); if (err != ESP_OK) { return err; @@ -435,7 +524,7 @@ esp_err_t Page::mLoadEntryTable() if (mState == PageState::ACTIVE || mState == PageState::FULL || mState == PageState::FREEING) { - auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(), + auto rc = mPartition->read_raw(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(), mEntryTable.byteSize()); if (rc != ESP_OK) { mState = PageState::INVALID; @@ -474,7 +563,7 @@ esp_err_t Page::mLoadEntryTable() while (mNextFreeEntry < ENTRY_COUNT) { uint32_t entryAddress = getEntryAddress(mNextFreeEntry); uint32_t header; - auto rc = spi_flash_read(entryAddress, &header, sizeof(header)); + auto rc = mPartition->read_raw(entryAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -519,7 +608,7 @@ esp_err_t Page::mLoadEntryTable() mState = PageState::INVALID; return err; } - + if (item.crc32 != item.calculateCrc32()) { err = eraseEntryAndSpan(i); if (err != ESP_OK) { @@ -529,12 +618,16 @@ esp_err_t Page::mLoadEntryTable() continue; } - mHashList.insert(item, i); + err = mHashList.insert(item, i); + if (err != ESP_OK) { + mState = PageState::INVALID; + return err; + } // search for potential duplicate item size_t duplicateIndex = mHashList.find(0, item); - - if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) { + + if (isVariableLengthType(item.datatype)) { span = item.span; bool needErase = false; for (size_t j = i; j < i + span; ++j) { @@ -549,7 +642,11 @@ esp_err_t Page::mLoadEntryTable() continue; } } - + + /* Note that logic for duplicate detections works fine even + * when old-format blob is present along with new-format blob-index + * for same key on active page. Since datatype is not used in hash calculation, + * old-format blob will be removed.*/ if (duplicateIndex < i) { eraseEntryAndSpan(duplicateIndex); } @@ -592,12 +689,18 @@ esp_err_t Page::mLoadEntryTable() } continue; } + assert(item.span > 0); - mHashList.insert(item, i); + err = mHashList.insert(item, i); + if (err != ESP_OK) { + mState = PageState::INVALID; + return err; + } + size_t span = item.span; - if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) { + if (isVariableLengthType(item.datatype)) { for (size_t j = i + 1; j < i + span; ++j) { if (mEntryTable.get(j) != EntryState::WRITTEN) { eraseEntryAndSpan(i); @@ -622,9 +725,10 @@ esp_err_t Page::initialize() Header header; header.mState = mState; header.mSeqNumber = mSeqNumber; + header.mVersion = mVersion; header.mCrc32 = header.calculateCrc32(); - auto rc = spi_flash_write(mBaseAddress, &header, sizeof(header)); + auto rc = mPartition->write_raw(mBaseAddress, &header, sizeof(header)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -641,7 +745,7 @@ esp_err_t Page::alterEntryState(size_t index, EntryState state) mEntryTable.set(index, state); size_t wordToWrite = mEntryTable.getWordIndex(index); uint32_t word = mEntryTable.data()[wordToWrite]; - auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, + auto rc = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordToWrite) * 4, &word, sizeof(word)); if (rc != ESP_OK) { mState = PageState::INVALID; @@ -665,7 +769,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) } if (nextWordIndex != wordIndex) { uint32_t word = mEntryTable.data()[wordIndex]; - auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, + auto rc = mPartition->write_raw(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast(wordIndex) * 4, &word, 4); if (rc != ESP_OK) { return rc; @@ -679,7 +783,7 @@ esp_err_t Page::alterEntryRangeState(size_t begin, size_t end, EntryState state) esp_err_t Page::alterPageState(PageState state) { uint32_t state_val = static_cast(state); - auto rc = spi_flash_write(mBaseAddress, &state_val, sizeof(state)); + auto rc = mPartition->write_raw(mBaseAddress, &state_val, sizeof(state)); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -690,19 +794,19 @@ esp_err_t Page::alterPageState(PageState state) esp_err_t Page::readEntry(size_t index, Item& dst) const { - auto rc = spi_flash_read(getEntryAddress(index), &dst, sizeof(dst)); + auto rc = mPartition->read(getEntryAddress(index), &dst, sizeof(dst)); if (rc != ESP_OK) { return rc; } return ESP_OK; } -esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item) +esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx, VerOffset chunkStart) { if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) { return ESP_ERR_NVS_NOT_FOUND; } - + size_t findBeginIndex = itemIndex; if (findBeginIndex >= ENTRY_COUNT) { return ESP_ERR_NVS_NOT_FOUND; @@ -719,7 +823,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si } if (nsIndex != NS_ANY && datatype != ItemType::ANY && key != NULL) { - size_t cachedIndex = mHashList.find(start, Item(nsIndex, datatype, 0, key)); + size_t cachedIndex = mHashList.find(start, Item(nsIndex, datatype, 0, key, chunkIdx)); if (cachedIndex < ENTRY_COUNT) { start = cachedIndex; } else { @@ -750,7 +854,7 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si continue; } - if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) { + if (isVariableLengthType(item.datatype)) { next = i + item.span; } @@ -761,8 +865,31 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si if (key != nullptr && strncmp(key, item.key, Item::MAX_KEY_LENGTH) != 0) { continue; } + /* For blob data, chunkIndex should match*/ + if (chunkIdx != CHUNK_ANY + && datatype == ItemType::BLOB_DATA + && item.chunkIndex != chunkIdx) { + continue; + } + /* Blob-index will match the with blob data. + * Skip data chunks when searching for blob index*/ + if (datatype == ItemType::BLOB_IDX + && item.chunkIndex != CHUNK_ANY) { + continue; + } + /* Match the version for blob-index*/ + if (datatype == ItemType::BLOB_IDX + && chunkStart != VerOffset::VER_ANY + && item.blobIndex.chunkStart != chunkStart) { + continue; + } + if (datatype != ItemType::ANY && item.datatype != datatype) { + if (key == nullptr && nsIndex == NS_ANY && chunkIdx == CHUNK_ANY) { + continue; // continue for bruteforce search on blob indices. + } + itemIndex = i; return ESP_ERR_NVS_TYPE_MISMATCH; } @@ -793,10 +920,18 @@ esp_err_t Page::setSeqNumber(uint32_t seqNumber) return ESP_OK; } +esp_err_t Page::setVersion(uint8_t ver) +{ + if (mState != PageState::UNINITIALIZED) { + return ESP_ERR_NVS_INVALID_STATE; + } + mVersion = ver; + return ESP_OK; +} + esp_err_t Page::erase() { - auto sector = mBaseAddress / SPI_FLASH_SEC_SIZE; - auto rc = spi_flash_erase_sector(sector); + auto rc = mPartition->erase_range(mBaseAddress, SPI_FLASH_SEC_SIZE); if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -825,28 +960,39 @@ esp_err_t Page::markFull() } return alterPageState(PageState::FULL); } - + +size_t Page::getVarDataTailroom() const +{ + if (mState == PageState::UNINITIALIZED) { + return CHUNK_MAX_SIZE; + } else if (mState == PageState::FULL) { + return 0; + } + /* Skip one entry for blob data item precessing the data */ + return ((mNextFreeEntry < (ENTRY_COUNT-1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE): 0); +} + const char* Page::pageStateToName(PageState ps) { switch (ps) { case PageState::CORRUPT: return "CORRUPT"; - + case PageState::ACTIVE: return "ACTIVE"; - + case PageState::FREEING: return "FREEING"; - + case PageState::FULL: return "FULL"; - + case PageState::INVALID: return "INVALID"; - + case PageState::UNINITIALIZED: return "UNINITIALIZED"; - + default: assert(0 && "invalid state value"); return ""; @@ -868,7 +1014,7 @@ void Page::debugDump() const Item item; readEntry(i, item); if (skip == 0) { - printf("W ns=%2u type=%2u span=%3u key=\"%s\" len=%d\n", item.nsIndex, static_cast(item.datatype), item.span, item.key, (item.span != 1)?((int)item.varLength.dataSize):-1); + printf("W ns=%2u type=%2u span=%3u key=\"%s\" chunkIdx=%d len=%d\n", item.nsIndex, static_cast(item.datatype), item.span, item.key, item.chunkIndex, (item.span != 1)?((int)item.varLength.dataSize):-1); if (item.span > 0 && item.span <= ENTRY_COUNT - i) { skip = item.span - 1; } else { @@ -882,4 +1028,33 @@ void Page::debugDump() const } } +esp_err_t Page::calcEntries(nvs_stats_t &nvsStats) +{ + assert(mState != PageState::FREEING); + + nvsStats.total_entries += ENTRY_COUNT; + + switch (mState) { + case PageState::UNINITIALIZED: + case PageState::CORRUPT: + nvsStats.free_entries += ENTRY_COUNT; + break; + + case PageState::FULL: + case PageState::ACTIVE: + nvsStats.used_entries += mUsedEntryCount; + nvsStats.free_entries += ENTRY_COUNT - mUsedEntryCount; // it's equivalent free + erase entries. + break; + + case PageState::INVALID: + return ESP_ERR_INVALID_STATE; + break; + + default: + assert(false && "Unhandled state"); + break; + } + return ESP_OK; +} + } // namespace nvs diff --git a/components/nvs_flash/src/nvs_page.hpp b/components/nvs_flash/src/nvs_page.hpp index 413da4589..5857f1ffe 100644 --- a/components/nvs_flash/src/nvs_page.hpp +++ b/components/nvs_flash/src/nvs_page.hpp @@ -24,6 +24,7 @@ #include "compressed_enum_table.hpp" #include "intrusive_list.h" #include "nvs_item_hash_list.hpp" +#include "partition.hpp" namespace nvs { @@ -45,12 +46,16 @@ class Page : public intrusive_list_node static const size_t ENTRY_SIZE = 32; static const size_t ENTRY_COUNT = 126; static const uint32_t INVALID_ENTRY = 0xffffffff; - - static const size_t BLOB_MAX_SIZE = ENTRY_SIZE * (ENTRY_COUNT / 2 - 1); + + static const size_t CHUNK_MAX_SIZE = ENTRY_SIZE * (ENTRY_COUNT - 1); static const uint8_t NS_INDEX = 0; static const uint8_t NS_ANY = 255; + static const uint8_t CHUNK_ANY = Item::CHUNK_ANY; + + static const uint8_t NVS_VERSION = 0xfe; // Decrement to upgrade + enum class PageState : uint32_t { // All bits set, default state after flash erase. Page has not been initialized yet. UNINITIALIZED = 0xffffffff, @@ -73,26 +78,32 @@ class Page : public intrusive_list_node INVALID = 0 }; + Page(); + PageState state() const { return mState; } - esp_err_t load(uint32_t sectorNumber); + esp_err_t load(Partition *partition, uint32_t sectorNumber); esp_err_t getSeqNumber(uint32_t& seqNumber) const; esp_err_t setSeqNumber(uint32_t seqNumber); - esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize); + esp_err_t setVersion(uint8_t version); - esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize); + esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY); - esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key); + esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); - esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key); + esp_err_t cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); - esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item); + esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); + + esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); + + esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); template esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value) @@ -106,6 +117,12 @@ class Page : public intrusive_list_node return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value)); } + template + esp_err_t cmpItem(uint8_t nsIndex, const char* key, const T& value) + { + return cmpItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value)); + } + template esp_err_t eraseItem(uint8_t nsIndex, const char* key) { @@ -121,7 +138,7 @@ class Page : public intrusive_list_node { return mErasedEntryCount; } - + size_t getVarDataTailroom() const ; esp_err_t markFull(); @@ -133,6 +150,8 @@ class Page : public intrusive_list_node void debugDump() const; + esp_err_t calcEntries(nvs_stats_t &nvsStats); + protected: class Header @@ -140,12 +159,13 @@ class Page : public intrusive_list_node public: Header() { - std::fill_n(mReserved, sizeof(mReserved)/sizeof(mReserved[0]), UINT32_MAX); + std::fill_n(mReserved, sizeof(mReserved)/sizeof(mReserved[0]), UINT8_MAX); } PageState mState; // page state uint32_t mSeqNumber; // sequence number of this page - uint32_t mReserved[5]; // unused, must be 0xffffffff + uint8_t mVersion; // nvs format version + uint8_t mReserved[19]; // unused, must be 0xff uint32_t mCrc32; // crc of everything except mState uint32_t calculateCrc32(); @@ -171,7 +191,7 @@ class Page : public intrusive_list_node esp_err_t readEntry(size_t index, Item& dst) const; esp_err_t writeEntry(const Item& item); - + esp_err_t writeEntryData(const uint8_t* data, size_t size); esp_err_t eraseEntryAndSpan(size_t index); @@ -188,7 +208,7 @@ class Page : public intrusive_list_node assert(entry < ENTRY_COUNT); return mBaseAddress + ENTRY_DATA_OFFSET + static_cast(entry) * ENTRY_SIZE; } - + static const char* pageStateToName(PageState ps); @@ -196,6 +216,7 @@ class Page : public intrusive_list_node uint32_t mBaseAddress = 0; PageState mState = PageState::INVALID; uint32_t mSeqNumber = UINT32_MAX; + uint8_t mVersion = NVS_VERSION; typedef CompressedEnumTable TEntryTable; TEntryTable mEntryTable; size_t mNextFreeEntry = INVALID_ENTRY; @@ -203,8 +224,13 @@ class Page : public intrusive_list_node uint16_t mUsedEntryCount = 0; uint16_t mErasedEntryCount = 0; + /** + * This hash list stores hashes of namespace index, key, and ChunkIndex for quick lookup when searching items. + */ HashList mHashList; + Partition *mPartition; + static const uint32_t HEADER_OFFSET = 0; static const uint32_t ENTRY_TABLE_OFFSET = HEADER_OFFSET + 32; static const uint32_t ENTRY_DATA_OFFSET = ENTRY_TABLE_OFFSET + 32; diff --git a/components/nvs_flash/src/nvs_pagemanager.cpp b/components/nvs_flash/src/nvs_pagemanager.cpp index 31240d98b..88a830bd0 100644 --- a/components/nvs_flash/src/nvs_pagemanager.cpp +++ b/components/nvs_flash/src/nvs_pagemanager.cpp @@ -15,16 +15,22 @@ namespace nvs { -esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount) +esp_err_t PageManager::load(Partition *partition, uint32_t baseSector, uint32_t sectorCount) { + if (partition == nullptr) { + return ESP_ERR_INVALID_ARG; + } + mBaseSector = baseSector; mPageCount = sectorCount; mPageList.clear(); mFreePageList.clear(); - mPages.reset(new Page[sectorCount]); + mPages.reset(new (nothrow) Page[sectorCount]); + + if (!mPages) return ESP_ERR_NO_MEM; for (uint32_t i = 0; i < sectorCount; ++i) { - auto err = mPages[i].load(baseSector + i); + auto err = mPages[i].load(partition, baseSector + i); if (err != ESP_OK) { return err; } @@ -66,13 +72,26 @@ esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount) if (lastItemIndex != SIZE_MAX) { auto last = PageManager::TPageListIterator(&lastPage); - for (auto it = begin(); it != last; ++it) { + TPageListIterator it; + + for (it = begin(); it != last; ++it) { if ((it->state() != Page::PageState::FREEING) && - (it->eraseItem(item.nsIndex, item.datatype, item.key) == ESP_OK)) { + (it->eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex) == ESP_OK)) { break; } } + if ((it == last) && (item.datatype == ItemType::BLOB_IDX)) { + /* Rare case in which the blob was stored using old format, but power went just after writing + * blob index during modification. Loop again and delete the old version blob*/ + for (it = begin(); it != last; ++it) { + + if ((it->state() != Page::PageState::FREEING) && + (it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, item.chunkIndex) == ESP_OK)) { + break; + } + } + } } // check if power went out while page was being freed @@ -111,7 +130,7 @@ esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount) } // partition should have at least one free page - if (mFreePageList.size() == 0) { + if (mFreePageList.empty()) { return ESP_ERR_NVS_NO_FREE_PAGES; } @@ -200,4 +219,26 @@ esp_err_t PageManager::activatePage() return ESP_OK; } +esp_err_t PageManager::fillStats(nvs_stats_t& nvsStats) +{ + nvsStats.used_entries = 0; + nvsStats.free_entries = 0; + nvsStats.total_entries = 0; + esp_err_t err = ESP_OK; + + // list of used pages + for (auto p = mPageList.begin(); p != mPageList.end(); ++p) { + err = p->calcEntries(nvsStats); + if (err != ESP_OK) { + return err; + } + } + + // free pages + nvsStats.total_entries += mFreePageList.size() * Page::ENTRY_COUNT; + nvsStats.free_entries += mFreePageList.size() * Page::ENTRY_COUNT; + + return err; +} + } // namespace nvs diff --git a/components/nvs_flash/src/nvs_pagemanager.hpp b/components/nvs_flash/src/nvs_pagemanager.hpp index 10c545f0f..4a1c19f7e 100644 --- a/components/nvs_flash/src/nvs_pagemanager.hpp +++ b/components/nvs_flash/src/nvs_pagemanager.hpp @@ -18,7 +18,7 @@ #include #include "nvs_types.hpp" #include "nvs_page.hpp" -#include "nvs_pagemanager.hpp" +#include "partition.hpp" #include "intrusive_list.h" namespace nvs @@ -31,7 +31,7 @@ class PageManager PageManager() {} - esp_err_t load(uint32_t baseSector, uint32_t sectorCount); + esp_err_t load(Partition *partition, uint32_t baseSector, uint32_t sectorCount); TPageListIterator begin() { @@ -48,8 +48,19 @@ class PageManager return mPageList.back(); } + uint32_t getPageCount() { + return mPageCount; + } + esp_err_t requestNewPage(); + esp_err_t fillStats(nvs_stats_t& nvsStats); + + uint32_t getBaseSector() + { + return mBaseSector; + } + protected: friend class Iterator; diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp new file mode 100644 index 000000000..f1b7d36d2 --- /dev/null +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -0,0 +1,77 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "nvs_partition.hpp" + +namespace nvs { + +NVSPartition::NVSPartition(const esp_partition_t* partition) + : mESPPartition(partition) +{ + // ensure the class is in a valid state + if (partition == nullptr) { + std::abort(); + } +} + +const char *NVSPartition::get_partition_name() +{ + return mESPPartition->label; +} + +esp_err_t NVSPartition::read_raw(size_t src_offset, void* dst, size_t size) +{ + return esp_partition_read_raw(mESPPartition, src_offset, dst, size); +} + +esp_err_t NVSPartition::read(size_t src_offset, void* dst, size_t size) +{ + if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + + return esp_partition_read(mESPPartition, src_offset, dst, size); +} + +esp_err_t NVSPartition::write_raw(size_t dst_offset, const void* src, size_t size) +{ + return esp_partition_write_raw(mESPPartition, dst_offset, src, size); +} + +esp_err_t NVSPartition::write(size_t dst_offset, const void* src, size_t size) +{ + if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + + return esp_partition_write(mESPPartition, dst_offset, src, size); +} + +esp_err_t NVSPartition::erase_range(size_t dst_offset, size_t size) +{ + return esp_partition_erase_range(mESPPartition, dst_offset, size); +} + +uint32_t NVSPartition::get_address() +{ + return mESPPartition->address; +} + +uint32_t NVSPartition::get_size() +{ + return mESPPartition->size; +} + +} // nvs diff --git a/components/nvs_flash/src/nvs_partition.hpp b/components/nvs_flash/src/nvs_partition.hpp new file mode 100644 index 000000000..2af3e238e --- /dev/null +++ b/components/nvs_flash/src/nvs_partition.hpp @@ -0,0 +1,115 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_PARTITION_HPP_ +#define ESP_PARTITION_HPP_ + +#include "esp_partition.h" +#include "intrusive_list.h" +#include "partition.hpp" + +#define ESP_ENCRYPT_BLOCK_SIZE 16 + +#define PART_NAME_MAX_SIZE 16 /*!< maximum length of partition name (excluding null terminator) */ + +namespace nvs { + +/** + * Implementation of Partition for NVS. + * + * It is implemented as an intrusive_list_node to easily store instances of it. NVSStorage and NVSPage take pointer + * references of this class to abstract their partition operations. + */ +class NVSPartition : public Partition, public intrusive_list_node { +public: + /** + * Copy partition_name to mPartitionName and initialize mESPPartition. + * + * @param partition_name the name of the partition as in the partition table, must be non-NULL! + * @param partition an already initialized partition structure + */ + NVSPartition(const esp_partition_t* partition); + + /** + * No need to de-initialize mESPPartition here, if you used esp_partition_find_first. + * Otherwise, the user is responsible for de-initializing it. + */ + virtual ~NVSPartition() { } + + const char *get_partition_name() override; + + /** + * Look into \c esp_partition_read_raw for more details. + * + * @return + * - ESP_OK on success + * - other error codes from the esp_partition API + */ + esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override; + + /** + * Look into \c esp_partition_read for more details. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if size isn't a multiple of ESP_ENCRYPT_BLOCK_SIZE + * - other error codes from the esp_partition API + */ + esp_err_t read(size_t src_offset, void* dst, size_t size) override; + + /** + * Look into \c esp_partition_write_raw for more details. + * + * @return + * - ESP_OK on success + * - error codes from the esp_partition API + */ + esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override; + + /** + * Look into \c esp_partition_write for more details. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if size isn't a multiple of ESP_ENCRYPT_BLOCK_SIZE + * - other error codes from the esp_partition API + */ + esp_err_t write(size_t dst_offset, const void* src, size_t size) override; + + /** + * Look into \c esp_partition_erase_range for more details. + * + * @return + * - ESP_OK on success + * - error codes from the esp_partition API + */ + esp_err_t erase_range(size_t dst_offset, size_t size) override; + + /** + * @return the base address of the partition. + */ + uint32_t get_address() override; + + /** + * @return the size of the partition in bytes. + */ + uint32_t get_size() override; + +protected: + const esp_partition_t* mESPPartition; +}; + +} // nvs + +#endif // ESP_PARTITION_HPP_ diff --git a/components/nvs_flash/src/nvs_partition_lookup.cpp b/components/nvs_flash/src/nvs_partition_lookup.cpp new file mode 100644 index 000000000..e9d27256f --- /dev/null +++ b/components/nvs_flash/src/nvs_partition_lookup.cpp @@ -0,0 +1,69 @@ +#include "esp_partition.h" +#include "nvs_partition_lookup.hpp" + +#ifdef CONFIG_NVS_ENCRYPTION +#include "nvs_encrypted_partition.hpp" +#endif // CONFIG_NVS_ENCRYPTION + +namespace nvs { + +namespace partition_lookup { + +esp_err_t lookup_nvs_partition(const char* label, NVSPartition **p) +{ + const esp_partition_t* esp_partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); + + if (esp_partition == nullptr) { + return ESP_ERR_NOT_FOUND; + } + + if (esp_partition->encrypted) { + return ESP_ERR_NVS_WRONG_ENCRYPTION; + } + + NVSPartition *partition = new (std::nothrow) NVSPartition(esp_partition); + if (partition == nullptr) { + return ESP_ERR_NO_MEM; + } + + *p = partition; + + return ESP_OK; +} + +#ifdef CONFIG_NVS_ENCRYPTION +esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, NVSPartition **p) +{ + const esp_partition_t* esp_partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); + + if (esp_partition == nullptr) { + return ESP_ERR_NOT_FOUND; + } + + if (esp_partition->encrypted) { + return ESP_ERR_NVS_WRONG_ENCRYPTION; + } + + NVSEncryptedPartition *enc_p = new (std::nothrow) NVSEncryptedPartition(esp_partition); + if (enc_p == nullptr) { + return ESP_ERR_NO_MEM; + } + + esp_err_t result = enc_p->init(cfg); + if (result != ESP_OK) { + delete enc_p; + return result; + } + + *p = enc_p; + + return ESP_OK; +} + +#endif // CONFIG_NVS_ENCRYPTION + +} // partition_lookup + +} // nvs diff --git a/components/nvs_flash/src/nvs_partition_lookup.hpp b/components/nvs_flash/src/nvs_partition_lookup.hpp new file mode 100644 index 000000000..62a8e8b6f --- /dev/null +++ b/components/nvs_flash/src/nvs_partition_lookup.hpp @@ -0,0 +1,22 @@ +#include "esp_err.h" +#include "nvs_partition.hpp" +#include "nvs_flash.h" + +#ifndef NVS_PARTITION_LOOKUP_HPP_ +#define NVS_PARTITION_LOOKUP_HPP_ + +namespace nvs { + +namespace partition_lookup { + +esp_err_t lookup_nvs_partition(const char* label, NVSPartition **p); + +#ifdef CONFIG_NVS_ENCRYPTION +esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, NVSPartition **p); +#endif // CONFIG_NVS_ENCRYPTION + +} // partition_lookup + +} // nvs + +#endif // NVS_PARTITION_LOOKUP_HPP_ diff --git a/components/nvs_flash/src/nvs_partition_manager.cpp b/components/nvs_flash/src/nvs_partition_manager.cpp new file mode 100644 index 000000000..b95db7e81 --- /dev/null +++ b/components/nvs_flash/src/nvs_partition_manager.cpp @@ -0,0 +1,244 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp_partition.h" +#include "nvs_partition_manager.hpp" +#include "nvs_partition_lookup.hpp" + +#ifdef CONFIG_NVS_ENCRYPTION +#include "nvs_encrypted_partition.hpp" +#endif // CONFIG_NVS_ENCRYPTION + +namespace nvs { + +NVSPartitionManager* NVSPartitionManager::instance = nullptr; + +NVSPartitionManager* NVSPartitionManager::get_instance() +{ + if (!instance) { + instance = new (std::nothrow) NVSPartitionManager(); + } + + return instance; +} + +#ifdef ESP_PLATFORM +esp_err_t NVSPartitionManager::init_partition(const char *partition_label) +{ + if (strlen(partition_label) > NVS_PART_NAME_MAX_SIZE) { + return ESP_ERR_INVALID_ARG; + } + + uint32_t size; + Storage* mStorage; + + mStorage = lookup_storage_from_name(partition_label); + if (mStorage) { + return ESP_OK; + } + + assert(SPI_FLASH_SEC_SIZE != 0); + + NVSPartition *p = nullptr; + esp_err_t result = partition_lookup::lookup_nvs_partition(partition_label, &p); + + if (result != ESP_OK) { + goto error; + } + + size = p->get_size(); + + result = init_custom(p, 0, size / SPI_FLASH_SEC_SIZE); + if (result != ESP_OK) { + goto error; + } + + nvs_partition_list.push_back(p); + + return ESP_OK; + +error: + delete p; + return result; +} +#endif // ESP_PLATFORM + +esp_err_t NVSPartitionManager::init_custom(Partition *partition, uint32_t baseSector, uint32_t sectorCount) +{ + Storage* new_storage = nullptr; + Storage* storage = lookup_storage_from_name(partition->get_partition_name()); + if (storage == nullptr) { + new_storage = new (std::nothrow) Storage(partition); + + if (new_storage == nullptr) { + return ESP_ERR_NO_MEM; + } + + storage = new_storage; + } else { + // if storage was initialized already, we don't need partition and hence delete it + for (auto it = nvs_partition_list.begin(); it != nvs_partition_list.end(); ++it) { + if (partition == it) { + nvs_partition_list.erase(it); + delete partition; + break; + } + } + } + + esp_err_t err = storage->init(baseSector, sectorCount); + if (new_storage != nullptr) { + if (err == ESP_OK) { + nvs_storage_list.push_back(new_storage); + } else { + delete new_storage; + } + } + return err; +} + +#ifdef CONFIG_NVS_ENCRYPTION +#ifdef ESP_PLATFORM +esp_err_t NVSPartitionManager::secure_init_partition(const char *part_name, nvs_sec_cfg_t* cfg) +{ + if (strlen(part_name) > NVS_PART_NAME_MAX_SIZE) { + return ESP_ERR_INVALID_ARG; + } + + Storage* mStorage; + + mStorage = lookup_storage_from_name(part_name); + if (mStorage != nullptr) { + return ESP_OK; + } + + NVSPartition *p; + esp_err_t result; + if (cfg != nullptr) { + result = partition_lookup::lookup_nvs_encrypted_partition(part_name, cfg, &p); + } else { + result = partition_lookup::lookup_nvs_partition(part_name, &p); + } + + if (result != ESP_OK) { + return result; + } + + uint32_t size = p->get_size(); + + result = init_custom(p, 0, size / SPI_FLASH_SEC_SIZE); + if (result != ESP_OK) { + delete p; + return result; + } + + nvs_partition_list.push_back(p); + + return ESP_OK; +} +#endif // ESP_PLATFORM +#endif // CONFIG_NVS_ENCRYPTION + +esp_err_t NVSPartitionManager::deinit_partition(const char *partition_label) +{ + Storage* storage = lookup_storage_from_name(partition_label); + if (!storage) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + /* Clean up handles related to the storage being deinitialized */ + for (auto it = nvs_handles.begin(); it != nvs_handles.end(); ++it) { + if (it->mStoragePtr == storage) { + it->valid = false; + nvs_handles.erase(it); + } + } + + /* Finally delete the storage and its partition */ + nvs_storage_list.erase(storage); + delete storage; + + for (auto it = nvs_partition_list.begin(); it != nvs_partition_list.end(); ++it) { + if (strcmp(it->get_partition_name(), partition_label) == 0) { + NVSPartition *p = it; + nvs_partition_list.erase(it); + delete p; + break; + } + } + + return ESP_OK; +} + +esp_err_t NVSPartitionManager::open_handle(const char *part_name, + const char *ns_name, + nvs_open_mode_t open_mode, + NVSHandleSimple** handle) +{ + uint8_t nsIndex; + Storage* sHandle; + + if (nvs_storage_list.empty()) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + sHandle = lookup_storage_from_name(part_name); + if (sHandle == nullptr) { + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + esp_err_t err = sHandle->createOrOpenNamespace(ns_name, open_mode == NVS_READWRITE, nsIndex); + if (err != ESP_OK) { + return err; + } + + *handle = new (std::nothrow) NVSHandleSimple(open_mode==NVS_READONLY, nsIndex, sHandle); + + if (handle == nullptr) { + return ESP_ERR_NO_MEM; + } + + nvs_handles.push_back(*handle); + + return ESP_OK; +} + +esp_err_t NVSPartitionManager::close_handle(NVSHandleSimple* handle) { + for (auto it = nvs_handles.begin(); it != nvs_handles.end(); ++it) { + if (it == intrusive_list::iterator(handle)) { + nvs_handles.erase(it); + return ESP_OK; + } + } + + return ESP_ERR_NVS_INVALID_HANDLE; +} + +size_t NVSPartitionManager::open_handles_size() +{ + return nvs_handles.size(); +} + +Storage* NVSPartitionManager::lookup_storage_from_name(const char* name) +{ + auto it = find_if(begin(nvs_storage_list), end(nvs_storage_list), [=](Storage& e) -> bool { + return (strcmp(e.getPartName(), name) == 0); + }); + + if (it == end(nvs_storage_list)) { + return nullptr; + } + return it; +} + +} // nvs diff --git a/components/nvs_flash/src/nvs_partition_manager.hpp b/components/nvs_flash/src/nvs_partition_manager.hpp new file mode 100644 index 000000000..885787de4 --- /dev/null +++ b/components/nvs_flash/src/nvs_partition_manager.hpp @@ -0,0 +1,62 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef NVS_PARTITION_MANAGER_HPP_ +#define NVS_PARTITION_MANAGER_HPP_ + +#include "nvs_handle_simple.hpp" +#include "nvs_storage.hpp" +#include "nvs_partition.hpp" +#include "nvs_flash.h" + +namespace nvs { + +class NVSPartitionManager { +public: + virtual ~NVSPartitionManager() { } + + static NVSPartitionManager* get_instance(); + + esp_err_t init_partition(const char *partition_label); + + esp_err_t init_custom(Partition *partition, uint32_t baseSector, uint32_t sectorCount); + +#ifdef CONFIG_NVS_ENCRYPTION + esp_err_t secure_init_partition(const char *part_name, nvs_sec_cfg_t* cfg); +#endif + + esp_err_t deinit_partition(const char *partition_label); + + Storage* lookup_storage_from_name(const char* name); + + esp_err_t open_handle(const char *part_name, const char *ns_name, nvs_open_mode_t open_mode, NVSHandleSimple** handle); + + esp_err_t close_handle(NVSHandleSimple* handle); + + size_t open_handles_size(); + +protected: + NVSPartitionManager() { } + + static NVSPartitionManager* instance; + + intrusive_list nvs_handles; + + intrusive_list nvs_storage_list; + + intrusive_list nvs_partition_list; +}; + +} // nvs + +#endif // NVS_PARTITION_MANAGER_HPP_ diff --git a/components/nvs_flash/src/nvs_platform.hpp b/components/nvs_flash/src/nvs_platform.hpp index 0973c4875..5c6b5b8b2 100644 --- a/components/nvs_flash/src/nvs_platform.hpp +++ b/components/nvs_flash/src/nvs_platform.hpp @@ -11,11 +11,23 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#ifndef nvs_platform_h -#define nvs_platform_h +#pragma once +#ifdef LINUX_TARGET +namespace nvs +{ +class Lock +{ +public: + Lock() { } + ~Lock() { } + static void init() {} + static void uninit() {} +}; +} // namespace nvs + +#else // LINUX_TARGET -#ifdef ESP_PLATFORM #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -63,19 +75,4 @@ class Lock }; } // namespace nvs -#else // ESP_PLATFORM -namespace nvs -{ -class Lock -{ -public: - Lock() { } - ~Lock() { } - static void init() {} - static void uninit() {} -}; -} // namespace nvs -#endif // ESP_PLATFORM - - -#endif /* nvs_platform_h */ +#endif // LINUX_TARGET diff --git a/components/nvs_flash/src/nvs_storage.cpp b/components/nvs_flash/src/nvs_storage.cpp index f8da28fa2..52952b7a3 100644 --- a/components/nvs_flash/src/nvs_storage.cpp +++ b/components/nvs_flash/src/nvs_storage.cpp @@ -14,9 +14,15 @@ #include "nvs_storage.hpp" #ifndef ESP_PLATFORM +// We need NO_DEBUG_STORAGE here since the integration tests on the host add some debug code. +// The unit tests, however, don't want debug code since they check the behavior via data in/output and disturb +// the order of calling mocked functions. +#ifndef NO_DEBUG_STORAGE #include #include +#define DEBUG_STORAGE #endif +#endif // !ESP_PLATFORM namespace nvs { @@ -28,17 +34,69 @@ Storage::~Storage() void Storage::clearNamespaces() { - for (auto it = std::begin(mNamespaces); it != std::end(mNamespaces); ) { - auto tmp = it; - ++it; - mNamespaces.erase(tmp); - delete static_cast(tmp); + mNamespaces.clearAndFreeNodes(); +} + +esp_err_t Storage::populateBlobIndices(TBlobIndexList& blobIdxList) +{ + for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) { + Page& p = *it; + size_t itemIndex = 0; + Item item; + + /* If the power went off just after writing a blob index, the duplicate detection + * logic in pagemanager will remove the earlier index. So we should never find a + * duplicate index at this point */ + + while (p.findItem(Page::NS_ANY, ItemType::BLOB_IDX, nullptr, itemIndex, item) == ESP_OK) { + BlobIndexNode* entry = new (std::nothrow) BlobIndexNode; + + if (!entry) return ESP_ERR_NO_MEM; + + item.getKey(entry->key, sizeof(entry->key)); + entry->nsIndex = item.nsIndex; + entry->chunkStart = item.blobIndex.chunkStart; + entry->chunkCount = item.blobIndex.chunkCount; + + blobIdxList.push_back(entry); + itemIndex += item.span; + } + } + + return ESP_OK; +} + +void Storage::eraseOrphanDataBlobs(TBlobIndexList& blobIdxList) +{ + for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) { + Page& p = *it; + size_t itemIndex = 0; + Item item; + /* Chunks with same and with chunkIndex in the following ranges + * belong to same family. + * 1) VER_0_OFFSET <= chunkIndex < VER_1_OFFSET-1 => Version0 chunks + * 2) VER_1_OFFSET <= chunkIndex < VER_ANY => Version1 chunks + */ + while (p.findItem(Page::NS_ANY, ItemType::BLOB_DATA, nullptr, itemIndex, item) == ESP_OK) { + + auto iter = std::find_if(blobIdxList.begin(), + blobIdxList.end(), + [=] (const BlobIndexNode& e) -> bool + {return (strncmp(item.key, e.key, sizeof(e.key) - 1) == 0) + && (item.nsIndex == e.nsIndex) + && (item.chunkIndex >= static_cast (e.chunkStart)) + && (item.chunkIndex < static_cast (e.chunkStart) + e.chunkCount);}); + if (iter == std::end(blobIdxList)) { + p.eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex); + } + itemIndex += item.span; + } } } esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount) { - auto err = mPageManager.load(baseSector, sectorCount); + auto err = mPageManager.load(mPartition, baseSector, sectorCount); if (err != ESP_OK) { mState = StorageState::INVALID; return err; @@ -52,8 +110,14 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount) size_t itemIndex = 0; Item item; while (p.findItem(Page::NS_INDEX, ItemType::U8, nullptr, itemIndex, item) == ESP_OK) { - NamespaceEntry* entry = new NamespaceEntry; - item.getKey(entry->mName, sizeof(entry->mName) - 1); + NamespaceEntry* entry = new (std::nothrow) NamespaceEntry; + + if (!entry) { + mState = StorageState::INVALID; + return ESP_ERR_NO_MEM; + } + + item.getKey(entry->mName, sizeof(entry->mName)); item.getValue(entry->mIndex); mNamespaces.push_back(entry); mNamespaceUsage.set(entry->mIndex, true); @@ -63,7 +127,22 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount) mNamespaceUsage.set(0, true); mNamespaceUsage.set(255, true); mState = StorageState::ACTIVE; -#ifndef ESP_PLATFORM + + // Populate list of multi-page index entries. + TBlobIndexList blobIdxList; + err = populateBlobIndices(blobIdxList); + if (err != ESP_OK) { + mState = StorageState::INVALID; + return ESP_ERR_NO_MEM; + } + + // Remove the entries for which there is no parent multi-page index. + eraseOrphanDataBlobs(blobIdxList); + + // Purge the blob index list + blobIdxList.clearAndFreeNodes(); + +#ifdef DEBUG_STORAGE debugCheck(); #endif return ESP_OK; @@ -74,11 +153,11 @@ bool Storage::isValid() const return mState == StorageState::ACTIVE; } -esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item) +esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item, uint8_t chunkIdx, VerOffset chunkStart) { for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) { size_t itemIndex = 0; - auto err = it->findItem(nsIndex, datatype, key, itemIndex, item); + auto err = it->findItem(nsIndex, datatype, key, itemIndex, item, chunkIdx, chunkStart); if (err == ESP_OK) { page = it; return ESP_OK; @@ -87,6 +166,109 @@ esp_err_t Storage::findItem(uint8_t nsIndex, ItemType datatype, const char* key, return ESP_ERR_NVS_NOT_FOUND; } +esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart) +{ + uint8_t chunkCount = 0; + TUsedPageList usedPages; + size_t remainingSize = dataSize; + size_t offset = 0; + esp_err_t err = ESP_OK; + + /* Check how much maximum data can be accommodated**/ + uint32_t max_pages = mPageManager.getPageCount() - 1; + + if(max_pages > (Page::CHUNK_ANY-1)/2) { + max_pages = (Page::CHUNK_ANY-1)/2; + } + + if (dataSize > max_pages * Page::CHUNK_MAX_SIZE) { + return ESP_ERR_NVS_VALUE_TOO_LONG; + } + + do { + Page& page = getCurrentPage(); + size_t tailroom = page.getVarDataTailroom(); + size_t chunkSize = 0; + if (chunkCount == 0U && ((tailroom < dataSize) || (tailroom == 0 && dataSize == 0)) && tailroom < Page::CHUNK_MAX_SIZE/10) { + /** This is the first chunk and tailroom is too small ***/ + if (page.state() != Page::PageState::FULL) { + err = page.markFull(); + if (err != ESP_OK) { + return err; + } + } + err = mPageManager.requestNewPage(); + if (err != ESP_OK) { + return err; + } else if(getCurrentPage().getVarDataTailroom() == tailroom) { + /* We got the same page or we are not improving.*/ + return ESP_ERR_NVS_NOT_ENOUGH_SPACE; + } else { + continue; + } + } else if (!tailroom) { + err = ESP_ERR_NVS_NOT_ENOUGH_SPACE; + break; + } + + /* Split the blob into two and store the chunk of available size onto the current page */ + assert(tailroom != 0); + chunkSize = (remainingSize > tailroom)? tailroom : remainingSize; + remainingSize -= chunkSize; + + err = page.writeItem(nsIndex, ItemType::BLOB_DATA, key, + static_cast (data) + offset, chunkSize, static_cast (chunkStart) + chunkCount); + chunkCount++; + assert(err != ESP_ERR_NVS_PAGE_FULL); + if (err != ESP_OK) { + break; + } else { + UsedPageNode* node = new (std::nothrow) UsedPageNode(); + if (!node) { + err = ESP_ERR_NO_MEM; + break; + } + node->mPage = &page; + usedPages.push_back(node); + if (remainingSize || (tailroom - chunkSize) < Page::ENTRY_SIZE) { + if (page.state() != Page::PageState::FULL) { + err = page.markFull(); + if (err != ESP_OK) { + break; + } + } + err = mPageManager.requestNewPage(); + if (err != ESP_OK) { + break; + } + } + } + offset += chunkSize; + if (!remainingSize) { + /* All pages are stored. Now store the index.*/ + Item item; + std::fill_n(item.data, sizeof(item.data), 0xff); + item.blobIndex.dataSize = dataSize; + item.blobIndex.chunkCount = chunkCount; + item.blobIndex.chunkStart = chunkStart; + + err = getCurrentPage().writeItem(nsIndex, ItemType::BLOB_IDX, key, item.data, sizeof(item.data)); + assert(err != ESP_ERR_NVS_PAGE_FULL); + break; + } + } while (1); + + if (err != ESP_OK) { + /* Anything failed, then we should erase all the written chunks*/ + int ii=0; + for (auto it = std::begin(usedPages); it != std::end(usedPages); it++) { + it->mPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, ii++); + } + } + usedPages.clearAndFreeNodes(); + return err; +} + esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize) { if (mState != StorageState::ACTIVE) { @@ -95,40 +277,109 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key Page* findPage = nullptr; Item item; - auto err = findItem(nsIndex, datatype, key, findPage, item); + + esp_err_t err; + if (datatype == ItemType::BLOB) { + err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item); + } else { + err = findItem(nsIndex, datatype, key, findPage, item); + } + if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) { return err; } - Page& page = getCurrentPage(); - err = page.writeItem(nsIndex, datatype, key, data, dataSize); - if (err == ESP_ERR_NVS_PAGE_FULL) { - if (page.state() != Page::PageState::FULL) { - err = page.markFull(); - if (err != ESP_OK) { - return err; + if (datatype == ItemType::BLOB) { + VerOffset prevStart, nextStart; + prevStart = nextStart = VerOffset::VER_0_OFFSET; + if (findPage) { + // Do a sanity check that the item in question is actually being modified. + // If it isn't, it is cheaper to purposefully not write out new data. + // since it may invoke an erasure of flash. + if (cmpMultiPageBlob(nsIndex, key, data, dataSize) == ESP_OK) { + return ESP_OK; } + + if (findPage->state() == Page::PageState::UNINITIALIZED || + findPage->state() == Page::PageState::INVALID) { + ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item)); + } + /* Get the version of the previous index with same */ + prevStart = item.blobIndex.chunkStart; + assert(prevStart == VerOffset::VER_0_OFFSET || prevStart == VerOffset::VER_1_OFFSET); + + /* Toggle the version by changing the offset */ + nextStart + = (prevStart == VerOffset::VER_1_OFFSET) ? VerOffset::VER_0_OFFSET : VerOffset::VER_1_OFFSET; } - err = mPageManager.requestNewPage(); - if (err != ESP_OK) { - return err; - } + /* Write the blob with new version*/ + err = writeMultiPageBlob(nsIndex, key, data, dataSize, nextStart); - err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize); if (err == ESP_ERR_NVS_PAGE_FULL) { return ESP_ERR_NVS_NOT_ENOUGH_SPACE; } if (err != ESP_OK) { return err; } - } else if (err != ESP_OK) { - return err; + + if (findPage) { + /* Erase the blob with earlier version*/ + err = eraseMultiPageBlob(nsIndex, key, prevStart); + + if (err == ESP_ERR_FLASH_OP_FAIL) { + return ESP_ERR_NVS_REMOVE_FAILED; + } + if (err != ESP_OK) { + return err; + } + + findPage = nullptr; + } else { + /* Support for earlier versions where BLOBS were stored without index */ + err = findItem(nsIndex, datatype, key, findPage, item); + if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) { + return err; + } + } + } else { + // Do a sanity check that the item in question is actually being modified. + // If it isn't, it is cheaper to purposefully not write out new data. + // since it may invoke an erasure of flash. + if (findPage != nullptr && + findPage->cmpItem(nsIndex, datatype, key, data, dataSize) == ESP_OK) { + return ESP_OK; + } + + Page& page = getCurrentPage(); + err = page.writeItem(nsIndex, datatype, key, data, dataSize); + if (err == ESP_ERR_NVS_PAGE_FULL) { + if (page.state() != Page::PageState::FULL) { + err = page.markFull(); + if (err != ESP_OK) { + return err; + } + } + err = mPageManager.requestNewPage(); + if (err != ESP_OK) { + return err; + } + + err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize); + if (err == ESP_ERR_NVS_PAGE_FULL) { + return ESP_ERR_NVS_NOT_ENOUGH_SPACE; + } + if (err != ESP_OK) { + return err; + } + } else if (err != ESP_OK) { + return err; + } } if (findPage) { if (findPage->state() == Page::PageState::UNINITIALIZED || findPage->state() == Page::PageState::INVALID) { - ESP_ERROR_CHECK( findItem(nsIndex, datatype, key, findPage, item) ); + ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item)); } err = findPage->eraseItem(nsIndex, datatype, key); if (err == ESP_ERR_FLASH_OP_FAIL) { @@ -138,7 +389,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key return err; } } -#ifndef ESP_PLATFORM +#ifdef DEBUG_STORAGE debugCheck(); #endif return ESP_OK; @@ -168,6 +419,11 @@ esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uin return ESP_ERR_NVS_NOT_ENOUGH_SPACE; } + NamespaceEntry* entry = new (std::nothrow) NamespaceEntry; + if (!entry) { + return ESP_ERR_NO_MEM; + } + auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns)); if (err != ESP_OK) { return err; @@ -175,7 +431,6 @@ esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uin mNamespaceUsage.set(ns, true); nsIndex = ns; - NamespaceEntry* entry = new NamespaceEntry; entry->mIndex = ns; strncpy(entry->mName, nsName, sizeof(entry->mName) - 1); entry->mName[sizeof(entry->mName) - 1] = 0; @@ -187,6 +442,91 @@ esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uin return ESP_OK; } +esp_err_t Storage::readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize) +{ + Item item; + Page* findPage = nullptr; + + /* First read the blob index */ + auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item); + if (err != ESP_OK) { + return err; + } + + uint8_t chunkCount = item.blobIndex.chunkCount; + VerOffset chunkStart = item.blobIndex.chunkStart; + size_t readSize = item.blobIndex.dataSize; + size_t offset = 0; + + assert(dataSize == readSize); + + /* Now read corresponding chunks */ + for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) { + err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast (chunkStart) + chunkNum); + if (err != ESP_OK) { + if (err == ESP_ERR_NVS_NOT_FOUND) { + break; + } + return err; + } + err = findPage->readItem(nsIndex, ItemType::BLOB_DATA, key, static_cast(data) + offset, item.varLength.dataSize, static_cast (chunkStart) + chunkNum); + if (err != ESP_OK) { + return err; + } + assert(static_cast (chunkStart) + chunkNum == item.chunkIndex); + offset += item.varLength.dataSize; + } + if (err == ESP_OK) { + assert(offset == dataSize); + } + if (err == ESP_ERR_NVS_NOT_FOUND) { + eraseMultiPageBlob(nsIndex, key); // cleanup if a chunk is not found + } + return err; +} + +esp_err_t Storage::cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize) +{ + Item item; + Page* findPage = nullptr; + + /* First read the blob index */ + auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item); + if (err != ESP_OK) { + return err; + } + + uint8_t chunkCount = item.blobIndex.chunkCount; + VerOffset chunkStart = item.blobIndex.chunkStart; + size_t readSize = item.blobIndex.dataSize; + size_t offset = 0; + + if (dataSize != readSize) { + return ESP_ERR_NVS_CONTENT_DIFFERS; + } + + /* Now read corresponding chunks */ + for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) { + err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast (chunkStart) + chunkNum); + if (err != ESP_OK) { + if (err == ESP_ERR_NVS_NOT_FOUND) { + break; + } + return err; + } + err = findPage->cmpItem(nsIndex, ItemType::BLOB_DATA, key, static_cast(data) + offset, item.varLength.dataSize, static_cast (chunkStart) + chunkNum); + if (err != ESP_OK) { + return err; + } + assert(static_cast (chunkStart) + chunkNum == item.chunkIndex); + offset += item.varLength.dataSize; + } + if (err == ESP_OK) { + assert(offset == dataSize); + } + return err; +} + esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize) { if (mState != StorageState::ACTIVE) { @@ -195,12 +535,64 @@ esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, Item item; Page* findPage = nullptr; + if (datatype == ItemType::BLOB) { + auto err = readMultiPageBlob(nsIndex, key, data, dataSize); + if (err != ESP_ERR_NVS_NOT_FOUND) { + return err; + } // else check if the blob is stored with earlier version format without index + } + auto err = findItem(nsIndex, datatype, key, findPage, item); if (err != ESP_OK) { return err; } - return findPage->readItem(nsIndex, datatype, key, data, dataSize); + +} + +esp_err_t Storage::eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart) +{ + if (mState != StorageState::ACTIVE) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + Item item; + Page* findPage = nullptr; + + auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item, Page::CHUNK_ANY, chunkStart); + if (err != ESP_OK) { + return err; + } + /* Erase the index first and make children blobs orphan*/ + err = findPage->eraseItem(nsIndex, ItemType::BLOB_IDX, key, Page::CHUNK_ANY, chunkStart); + if (err != ESP_OK) { + return err; + } + + uint8_t chunkCount = item.blobIndex.chunkCount; + + if (chunkStart == VerOffset::VER_ANY) { + chunkStart = item.blobIndex.chunkStart; + } else { + assert(chunkStart == item.blobIndex.chunkStart); + } + + /* Now erase corresponding chunks*/ + for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) { + err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast (chunkStart) + chunkNum); + + if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) { + return err; + } else if (err == ESP_ERR_NVS_NOT_FOUND) { + continue; // Keep erasing other chunks + } + err = findPage->eraseItem(nsIndex, ItemType::BLOB_DATA, key, static_cast (chunkStart) + chunkNum); + if (err != ESP_OK) { + return err; + } + + } + + return ESP_OK; } esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key) @@ -209,6 +601,10 @@ esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key return ESP_ERR_NVS_NOT_INITIALIZED; } + if (datatype == ItemType::BLOB) { + return eraseMultiPageBlob(nsIndex, key); + } + Item item; Page* findPage = nullptr; auto err = findItem(nsIndex, datatype, key, findPage, item); @@ -216,6 +612,10 @@ esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key return err; } + if (item.datatype == ItemType::BLOB_DATA || item.datatype == ItemType::BLOB_IDX) { + return eraseMultiPageBlob(nsIndex, key); + } + return findPage->eraseItem(nsIndex, datatype, key); } @@ -250,7 +650,15 @@ esp_err_t Storage::getItemDataSize(uint8_t nsIndex, ItemType datatype, const cha Page* findPage = nullptr; auto err = findItem(nsIndex, datatype, key, findPage, item); if (err != ESP_OK) { - return err; + if (datatype != ItemType::BLOB) { + return err; + } + err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item); + if (err != ESP_OK) { + return err; + } + dataSize = item.blobIndex.dataSize; + return ESP_OK; } dataSize = item.varLength.dataSize; @@ -264,18 +672,18 @@ void Storage::debugDump() } } -#ifndef ESP_PLATFORM +#ifdef DEBUG_STORAGE void Storage::debugCheck() { std::map keys; - + for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) { size_t itemIndex = 0; size_t usedCount = 0; Item item; while (p->findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) { std::stringstream keyrepr; - keyrepr << static_cast(item.nsIndex) << "_" << static_cast(item.datatype) << "_" << item.key; + keyrepr << static_cast(item.nsIndex) << "_" << static_cast(item.datatype) << "_" << item.key <<"_"<(item.chunkIndex); std::string keystr = keyrepr.str(); if (keys.find(keystr) != std::end(keys)) { printf("Duplicate key: %s\n", keystr.c_str()); @@ -289,6 +697,104 @@ void Storage::debugCheck() assert(usedCount == p->getUsedEntryCount()); } } -#endif //ESP_PLATFORM +#endif //DEBUG_STORAGE + +esp_err_t Storage::fillStats(nvs_stats_t& nvsStats) +{ + nvsStats.namespace_count = mNamespaces.size(); + return mPageManager.fillStats(nvsStats); +} + +esp_err_t Storage::calcEntriesInNamespace(uint8_t nsIndex, size_t& usedEntries) +{ + usedEntries = 0; + + if (mState != StorageState::ACTIVE) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) { + size_t itemIndex = 0; + Item item; + while (true) { + auto err = it->findItem(nsIndex, ItemType::ANY, nullptr, itemIndex, item); + if (err == ESP_ERR_NVS_NOT_FOUND) { + break; + } + else if (err != ESP_OK) { + return err; + } + usedEntries += item.span; + itemIndex += item.span; + if (itemIndex >= it->ENTRY_COUNT) break; + } + } + return ESP_OK; +} + +void Storage::fillEntryInfo(Item &item, nvs_entry_info_t &info) +{ + info.type = static_cast(item.datatype); + strncpy(info.key, item.key, sizeof(info.key)); + + for (auto &name : mNamespaces) { + if(item.nsIndex == name.mIndex) { + strncpy(info.namespace_name, name.mName, sizeof(info.namespace_name)); + break; + } + } +} + +bool Storage::findEntry(nvs_opaque_iterator_t* it, const char* namespace_name) +{ + it->entryIndex = 0; + it->nsIndex = Page::NS_ANY; + it->page = mPageManager.begin(); + + if (namespace_name != nullptr) { + if(createOrOpenNamespace(namespace_name, false, it->nsIndex) != ESP_OK) { + return false; + } + } + + return nextEntry(it); +} + +inline bool isIterableItem(Item& item) +{ + return (item.nsIndex != 0 && + item.datatype != ItemType::BLOB && + item.datatype != ItemType::BLOB_IDX); +} + +inline bool isMultipageBlob(Item& item) +{ + return (item.datatype == ItemType::BLOB_DATA && + !(item.chunkIndex == static_cast(VerOffset::VER_0_OFFSET) + || item.chunkIndex == static_cast(VerOffset::VER_1_OFFSET))); +} + +bool Storage::nextEntry(nvs_opaque_iterator_t* it) +{ + Item item; + esp_err_t err; + + for (auto page = it->page; page != mPageManager.end(); ++page) { + do { + err = page->findItem(it->nsIndex, (ItemType)it->type, nullptr, it->entryIndex, item); + it->entryIndex += item.span; + if(err == ESP_OK && isIterableItem(item) && !isMultipageBlob(item)) { + fillEntryInfo(item, it->entry_info); + it->page = page; + return true; + } + } while (err != ESP_ERR_NVS_NOT_FOUND); + + it->entryIndex = 0; + } + + return false; +} + } diff --git a/components/nvs_flash/src/nvs_storage.hpp b/components/nvs_flash/src/nvs_storage.hpp index 3c0e0c85a..fd6eb1cc9 100644 --- a/components/nvs_flash/src/nvs_storage.hpp +++ b/components/nvs_flash/src/nvs_storage.hpp @@ -15,12 +15,12 @@ #define nvs_storage_hpp #include -#include #include #include "nvs.hpp" #include "nvs_types.hpp" #include "nvs_page.hpp" #include "nvs_pagemanager.hpp" +#include "partition.hpp" //extern void dumpBytes(const uint8_t* data, size_t count); @@ -42,10 +42,30 @@ class Storage : public intrusive_list_node typedef intrusive_list TNamespaces; + struct UsedPageNode: public intrusive_list_node { + public: Page* mPage; + }; + + typedef intrusive_list TUsedPageList; + + struct BlobIndexNode: public intrusive_list_node { + public: + char key[Item::MAX_KEY_LENGTH + 1]; + uint8_t nsIndex; + uint8_t chunkCount; + VerOffset chunkStart; + }; + + typedef intrusive_list TBlobIndexList; + public: ~Storage(); - Storage(const char *pName = NVS_DEFAULT_PART_NAME) : mPartitionName(pName) { }; + Storage(Partition *partition) : mPartition(partition) { + if (partition == nullptr) { + abort(); + } + }; esp_err_t init(uint32_t baseSector, uint32_t sectorCount); @@ -77,18 +97,43 @@ class Storage : public intrusive_list_node { return eraseItem(nsIndex, ItemType::ANY, key); } - + esp_err_t eraseNamespace(uint8_t nsIndex); + const Partition *getPart() const + { + return mPartition; + } + const char *getPartName() const { - return mPartitionName; + return mPartition->get_partition_name(); + } + + uint32_t getBaseSector() + { + return mPageManager.getBaseSector(); } + esp_err_t writeMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize, VerOffset chunkStart); + + esp_err_t readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize); + + esp_err_t cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize); + + esp_err_t eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart = VerOffset::VER_ANY); + void debugDump(); - + void debugCheck(); + esp_err_t fillStats(nvs_stats_t& nvsStats); + + esp_err_t calcEntriesInNamespace(uint8_t nsIndex, size_t& usedEntries); + + bool findEntry(nvs_opaque_iterator_t*, const char* name); + + bool nextEntry(nvs_opaque_iterator_t* it); protected: @@ -99,10 +144,16 @@ class Storage : public intrusive_list_node void clearNamespaces(); - esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item); + esp_err_t populateBlobIndices(TBlobIndexList&); + + void eraseOrphanDataBlobs(TBlobIndexList&); + + void fillEntryInfo(Item &item, nvs_entry_info_t &info); + + esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item, uint8_t chunkIdx = Page::CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY); protected: - const char *mPartitionName; + Partition *mPartition; size_t mPageCount; PageManager mPageManager; TNamespaces mNamespaces; @@ -112,6 +163,14 @@ class Storage : public intrusive_list_node } // namespace nvs - +struct nvs_opaque_iterator_t +{ + nvs_type_t type; + uint8_t nsIndex; + size_t entryIndex; + nvs::Storage *storage; + intrusive_list::iterator page; + nvs_entry_info_t entry_info; +}; #endif /* nvs_storage_hpp */ diff --git a/components/nvs_flash/src/nvs_test_api.h b/components/nvs_flash/src/nvs_test_api.h index 3cf3e7fa1..8ba2c4faa 100644 --- a/components/nvs_flash/src/nvs_test_api.h +++ b/components/nvs_flash/src/nvs_test_api.h @@ -17,22 +17,38 @@ #ifdef __cplusplus extern "C" { #endif - + #include "nvs_flash.h" /** * @brief Initialize NVS flash storage with custom flash sector layout * * @note This API is intended to be used in unit tests. - * + * * @param partName Partition name of the NVS partition as per partition table * @param baseSector Flash sector (units of 4096 bytes) offset to start NVS - * @param sectorCount Length (in flash sectors) of NVS region. + * @param sectorCount Length (in flash sectors) of NVS region. NVS partition must be at least 3 sectors long. * @return ESP_OK if flash was successfully initialized */ esp_err_t nvs_flash_init_custom(const char *partName, uint32_t baseSector, uint32_t sectorCount); +#ifdef CONFIG_NVS_ENCRYPTION +/** + * @brief Initialize NVS flash storage with custom flash sector layout + * + * @note This API is intended to be used in unit tests. + * + * @param partName Partition name of the NVS partition as per partition table + * @param baseSector Flash sector (units of 4096 bytes) offset to start NVS + * @param sectorCount Length (in flash sectors) of NVS region. + NVS partition must be at least 3 sectors long. + * @param[in] cfg Security configuration (keys) to be used for NVS encryption/decryption. + * If cfg is null, no encryption/decryption is used. + * @return ESP_OK if flash was successfully initialized + */ +esp_err_t nvs_flash_secure_init_custom(const char *partName, uint32_t baseSector, uint32_t sectorCount, nvs_sec_cfg_t* cfg); +#endif /** * @brief Dump contents of NVS storage to stdout diff --git a/components/nvs_flash/src/nvs_types.cpp b/components/nvs_flash/src/nvs_types.cpp index 8a1a42fb2..0189dd70b 100644 --- a/components/nvs_flash/src/nvs_types.cpp +++ b/components/nvs_flash/src/nvs_types.cpp @@ -13,7 +13,11 @@ // limitations under the License. #include "nvs_types.hpp" -#include +#if defined(LINUX_TARGET) +#include "crc.h" +#else +#include +#endif namespace nvs { @@ -21,10 +25,10 @@ uint32_t Item::calculateCrc32() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = crc32_le(result, p + offsetof(Item, nsIndex), + result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, crc32) - offsetof(Item, nsIndex)); - result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); - result = crc32_le(result, p + offsetof(Item, data), sizeof(data)); + result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data)); return result; } @@ -32,16 +36,17 @@ uint32_t Item::calculateCrc32WithoutValue() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = crc32_le(result, p + offsetof(Item, nsIndex), + result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, datatype) - offsetof(Item, nsIndex)); - result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); return result; } uint32_t Item::calculateCrc32(const uint8_t* data, size_t size) { uint32_t result = 0xffffffff; - result = crc32_le(result, data, size); + result = esp_rom_crc32_le(result, data, size); return result; } diff --git a/components/nvs_flash/src/nvs_types.hpp b/components/nvs_flash/src/nvs_types.hpp index 5306744b5..d05d4d94b 100644 --- a/components/nvs_flash/src/nvs_types.hpp +++ b/components/nvs_flash/src/nvs_types.hpp @@ -15,41 +15,36 @@ #define nvs_types_h #include -#include #include #include #include #include "nvs.h" +#include "nvs_handle.hpp" #include "compressed_enum_table.hpp" +#include "string.h" +using namespace std; namespace nvs { -enum class ItemType : uint8_t { - U8 = 0x01, - I8 = 0x11, - U16 = 0x02, - I16 = 0x12, - U32 = 0x04, - I32 = 0x14, - U64 = 0x08, - I64 = 0x18, - SZ = 0x21, - BLOB = 0x41, - ANY = 0xff +/** + * Used to recognize transient states of a blob. Once a blob is modified, new chunks with the new data are written + * with a new version. The version is saved in the highest bit of Item::chunkIndex as well as in + * Item::blobIndex::chunkStart. + * If a chunk is modified and hence re-written, the version swaps: 0x0 -> 0x80 or 0x80 -> 0x0. + */ +enum class VerOffset: uint8_t { + VER_0_OFFSET = 0x0, + VER_1_OFFSET = 0x80, + VER_ANY = 0xff, }; -template::value, void*>::type = nullptr> -constexpr ItemType itemTypeOf() +inline bool isVariableLengthType(ItemType type) { - return static_cast(((std::is_signed::value)?0x10:0x00) | sizeof(T)); -} - -template -constexpr ItemType itemTypeOf(const T&) -{ - return itemTypeOf(); + return (type == ItemType::BLOB || + type == ItemType::SZ || + type == ItemType::BLOB_DATA); } class Item @@ -60,15 +55,21 @@ class Item uint8_t nsIndex; ItemType datatype; uint8_t span; - uint8_t reserved; + uint8_t chunkIndex; uint32_t crc32; - char key[16]; + char key[NVS_KEY_NAME_MAX_SIZE]; union { struct { uint16_t dataSize; - uint16_t reserved2; + uint16_t reserved; uint32_t dataCrc32; } varLength; + struct { + uint32_t dataSize; + uint8_t chunkCount; // Number of children data blobs. + VerOffset chunkStart; // Offset from which the chunkIndex for children blobs starts + uint16_t reserved; + } blobIndex; uint8_t data[8]; }; }; @@ -77,8 +78,12 @@ class Item static const size_t MAX_KEY_LENGTH = sizeof(key) - 1; - Item(uint8_t nsIndex, ItemType datatype, uint8_t span, const char* key_) - : nsIndex(nsIndex), datatype(datatype), span(span), reserved(0xff) + // 0xff cannot be used as a valid chunkIndex for blob datatype. + static const uint8_t CHUNK_ANY = 0xff; + + + Item(uint8_t nsIndex, ItemType datatype, uint8_t span, const char* key_, uint8_t chunkIdx = CHUNK_ANY) + : nsIndex(nsIndex), datatype(datatype), span(span), chunkIndex(chunkIdx) { std::fill_n(reinterpret_cast(key), sizeof(key) / 4, 0xffffffff); std::fill_n(reinterpret_cast(data), sizeof(data) / 4, 0xffffffff); @@ -100,7 +105,8 @@ class Item void getKey(char* dst, size_t dstSize) { - strncpy(dst, key, (dstSize @@ -113,6 +119,4 @@ class Item } // namespace nvs - - #endif /* nvs_types_h */ diff --git a/components/nvs_flash/src/partition.hpp b/components/nvs_flash/src/partition.hpp new file mode 100644 index 000000000..4cbfb6d9d --- /dev/null +++ b/components/nvs_flash/src/partition.hpp @@ -0,0 +1,59 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PARTITION_HPP_ +#define PARTITION_HPP_ + +#include "esp_err.h" + +namespace nvs { + +/** + * @brief Abstract interface for partition related operations, currently in NVS. + * + * It resembles the main operations according to esp_partition.h. + */ +class Partition { +public: + virtual ~Partition() { } + + /** + * Return the partition name as in the partition table. + */ + virtual const char *get_partition_name() = 0; + + virtual esp_err_t read_raw(size_t src_offset, void* dst, size_t size) = 0; + + virtual esp_err_t read(size_t src_offset, void* dst, size_t size) = 0; + + virtual esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) = 0; + + virtual esp_err_t write(size_t dst_offset, const void* src, size_t size) = 0; + + virtual esp_err_t erase_range(size_t dst_offset, size_t size) = 0; + + /** + * Return the address of the beginning of the partition. + */ + virtual uint32_t get_address() = 0; + + /** + * Return the partition size in bytes. + */ + virtual uint32_t get_size() = 0; +}; + +} // nvs + +#endif // PARTITION_HPP_ diff --git a/components/nvs_flash/test/CMakeLists.txt b/components/nvs_flash/test/CMakeLists.txt new file mode 100644 index 000000000..f69580398 --- /dev/null +++ b/components/nvs_flash/test/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + PRIV_REQUIRES cmock test_utils nvs_flash bootloader_support + EMBED_TXTFILES encryption_keys.bin partition_encrypted.bin sample.bin) diff --git a/components/nvs_flash/test/component.mk b/components/nvs_flash/test/component.mk index 5dd172bdb..a0ea7a406 100644 --- a/components/nvs_flash/test/component.mk +++ b/components/nvs_flash/test/component.mk @@ -3,3 +3,4 @@ # COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive +COMPONENT_EMBED_TXTFILES += encryption_keys.bin partition_encrypted.bin sample.bin diff --git a/components/nvs_flash/test/encryption_keys.bin b/components/nvs_flash/test/encryption_keys.bin new file mode 100644 index 000000000..9ef4439d8 --- /dev/null +++ b/components/nvs_flash/test/encryption_keys.bin @@ -0,0 +1 @@ +"""""""""""""""""""""""""""""""",ïÏ<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/components/nvs_flash/test/partition_encrypted.bin b/components/nvs_flash/test/partition_encrypted.bin new file mode 100644 index 0000000000000000000000000000000000000000..93c900ff4a7e8c5a6cb9f21e28cd1a3cb77110da GIT binary patch literal 8192 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 +#include "esp_system.h" + +#ifdef CONFIG_NVS_ENCRYPTION +#include "mbedtls/aes.h" +#endif static const char* TAG = "test_nvs"; +TEST_CASE("Partition name no longer than 16 characters", "[nvs]") +{ + const char *TOO_LONG_NAME = "0123456789abcdefg"; + + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, nvs_flash_init_partition(TOO_LONG_NAME)); + + nvs_flash_deinit_partition(TOO_LONG_NAME); // just in case +} + +TEST_CASE("flash erase deinitializes initialized partition", "[nvs]") +{ + nvs_handle_t handle; + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + err = nvs_flash_init(); + } + TEST_ESP_OK( err ); + + TEST_ESP_OK(nvs_flash_init()); + TEST_ESP_OK(nvs_open("uninit_ns", NVS_READWRITE, &handle)); + nvs_close(handle); + TEST_ESP_OK(nvs_flash_erase()); + + // exptected: no partition is initialized since nvs_flash_erase() deinitialized the partition again + TEST_ESP_ERR(ESP_ERR_NVS_NOT_INITIALIZED, nvs_open("uninit_ns", NVS_READWRITE, &handle)); + + // just to be sure it's deinitialized in case of error and not affecting other tests + nvs_flash_deinit(); +} + +// test could have different output on host tests +TEST_CASE("nvs deinit with open handle", "[nvs]") +{ + nvs_handle_t handle_1; + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "nvs_flash_init failed (0x%x), erasing partition and retrying", err); + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + + TEST_ESP_OK(nvs_open("deinit_ns", NVS_READWRITE, &handle_1)); + nvs_flash_deinit(); +} + TEST_CASE("various nvs tests", "[nvs]") { - nvs_handle handle_1; + nvs_handle_t handle_1; esp_err_t err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_LOGW(TAG, "nvs_flash_init failed (0x%x), erasing partition and retrying", err); - const esp_partition_t* nvs_partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); - assert(nvs_partition && "partition table must have an NVS partition"); - ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) ); + ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_ERROR_CHECK( err ); - TEST_ESP_ERR(nvs_open("test_namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, nvs_open("test_namespace1", NVS_READONLY, &handle_1)); - TEST_ESP_ERR(nvs_set_i32(handle_1, "foo", 0x12345678), ESP_ERR_NVS_INVALID_HANDLE); + TEST_ESP_ERR(ESP_ERR_NVS_INVALID_HANDLE, nvs_set_i32(handle_1, "foo", 0x12345678)); nvs_close(handle_1); TEST_ESP_OK(nvs_open("test_namespace2", NVS_READWRITE, &handle_1)); @@ -36,7 +86,7 @@ TEST_CASE("various nvs tests", "[nvs]") TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789)); - nvs_handle handle_2; + nvs_handle_t handle_2; TEST_ESP_OK(nvs_open("test_namespace3", NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_erase_all(handle_2)); TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a)); @@ -65,3 +115,430 @@ TEST_CASE("various nvs tests", "[nvs]") nvs_close(handle_2); } + +TEST_CASE("calculate used and free space", "[nvs]") +{ + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, nvs_get_stats(NULL, NULL)); + nvs_stats_t stat1; + nvs_stats_t stat2; + TEST_ESP_ERR(ESP_ERR_NVS_NOT_INITIALIZED, nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries == 0); + TEST_ASSERT_TRUE(stat1.namespace_count == 0); + TEST_ASSERT_TRUE(stat1.total_entries == 0); + TEST_ASSERT_TRUE(stat1.used_entries == 0); + + nvs_handle_t handle = 0; + size_t h_count_entries; + TEST_ESP_ERR(ESP_ERR_NVS_INVALID_HANDLE, nvs_get_used_entry_count(handle, &h_count_entries)); + TEST_ASSERT_TRUE(h_count_entries == 0); + + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "nvs_flash_init failed (0x%x), erasing partition and retrying", err); + const esp_partition_t* nvs_partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + assert(nvs_partition && "partition table must have an NVS partition"); + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + + // erase if have any namespace + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + if(stat1.namespace_count != 0) { + TEST_ESP_OK(nvs_flash_deinit()); + TEST_ESP_OK(nvs_flash_erase()); + TEST_ESP_OK(nvs_flash_init()); + } + + // after erase. empty partition + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries != 0); + TEST_ASSERT_TRUE(stat1.namespace_count == 0); + TEST_ASSERT_TRUE(stat1.total_entries != 0); + TEST_ASSERT_TRUE(stat1.used_entries == 0); + + // create namespace test_k1 + nvs_handle_t handle_1; + TEST_ESP_OK(nvs_open("test_k1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + TEST_ASSERT_TRUE(stat2.free_entries + 1 == stat1.free_entries); + TEST_ASSERT_TRUE(stat2.namespace_count == 1); + TEST_ASSERT_TRUE(stat2.total_entries == stat1.total_entries); + TEST_ASSERT_TRUE(stat2.used_entries == 1); + + // create pair key-value com + TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x12345678)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries + 1 == stat2.free_entries); + TEST_ASSERT_TRUE(stat1.namespace_count == 1); + TEST_ASSERT_TRUE(stat1.total_entries == stat2.total_entries); + TEST_ASSERT_TRUE(stat1.used_entries == 2); + + // change value in com + TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x01234567)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + TEST_ASSERT_TRUE(stat2.free_entries == stat1.free_entries); + TEST_ASSERT_TRUE(stat2.namespace_count == 1); + TEST_ASSERT_TRUE(stat2.total_entries != 0); + TEST_ASSERT_TRUE(stat2.used_entries == 2); + + // create pair key-value ru + TEST_ESP_OK(nvs_set_i32(handle_1, "ru", 0x00FF00FF)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries + 1 == stat2.free_entries); + TEST_ASSERT_TRUE(stat1.namespace_count == 1); + TEST_ASSERT_TRUE(stat1.total_entries != 0); + TEST_ASSERT_TRUE(stat1.used_entries == 3); + + // amount valid pair in namespace 1 + size_t h1_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_1, &h1_count_entries)); + TEST_ASSERT_TRUE(h1_count_entries == 2); + + nvs_handle_t handle_2; + // create namespace test_k2 + TEST_ESP_OK(nvs_open("test_k2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + TEST_ASSERT_TRUE(stat2.free_entries + 1 == stat1.free_entries); + TEST_ASSERT_TRUE(stat2.namespace_count == 2); + TEST_ASSERT_TRUE(stat2.total_entries == stat1.total_entries); + TEST_ASSERT_TRUE(stat2.used_entries == 4); + + // create pair key-value + TEST_ESP_OK(nvs_set_i32(handle_2, "su1", 0x00000001)); + TEST_ESP_OK(nvs_set_i32(handle_2, "su2", 0x00000002)); + TEST_ESP_OK(nvs_set_i32(handle_2, "sus", 0x00000003)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries + 3 == stat2.free_entries); + TEST_ASSERT_TRUE(stat1.namespace_count == 2); + TEST_ASSERT_TRUE(stat1.total_entries == stat2.total_entries); + TEST_ASSERT_TRUE(stat1.used_entries == 7); + + TEST_ASSERT_TRUE(stat1.total_entries == (stat1.used_entries + stat1.free_entries)); + + // amount valid pair in namespace 2 + size_t h2_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_2, &h2_count_entries)); + TEST_ASSERT_TRUE(h2_count_entries == 3); + + TEST_ASSERT_TRUE(stat1.used_entries == (h1_count_entries + h2_count_entries + stat1.namespace_count)); + + nvs_close(handle_1); + nvs_close(handle_2); + + size_t temp = h2_count_entries; + TEST_ESP_ERR(ESP_ERR_NVS_INVALID_HANDLE, nvs_get_used_entry_count(handle_1, &h2_count_entries)); + TEST_ASSERT_TRUE(h2_count_entries == 0); + h2_count_entries = temp; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, nvs_get_used_entry_count(handle_1, NULL)); + + nvs_handle_t handle_3; + // create namespace test_k3 + TEST_ESP_OK(nvs_open("test_k3", NVS_READWRITE, &handle_3)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + TEST_ASSERT_TRUE(stat2.free_entries + 1 == stat1.free_entries); + TEST_ASSERT_TRUE(stat2.namespace_count == 3); + TEST_ASSERT_TRUE(stat2.total_entries == stat1.total_entries); + TEST_ASSERT_TRUE(stat2.used_entries == 8); + + // create pair blobs + uint32_t blob[12]; + TEST_ESP_OK(nvs_set_blob(handle_3, "bl1", &blob, sizeof(blob))); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + TEST_ASSERT_TRUE(stat1.free_entries + 4 == stat2.free_entries); + TEST_ASSERT_TRUE(stat1.namespace_count == 3); + TEST_ASSERT_TRUE(stat1.total_entries == stat2.total_entries); + TEST_ASSERT_TRUE(stat1.used_entries == 12); + + // amount valid pair in namespace 2 + size_t h3_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_3, &h3_count_entries)); + TEST_ASSERT_TRUE(h3_count_entries == 4); + + TEST_ASSERT_TRUE(stat1.used_entries == (h1_count_entries + h2_count_entries + h3_count_entries + stat1.namespace_count)); + + nvs_close(handle_3); + + TEST_ESP_OK(nvs_flash_deinit()); + TEST_ESP_OK(nvs_flash_erase()); +} + +TEST_CASE("check for memory leaks in nvs_set_blob", "[nvs]") +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + TEST_ESP_OK( err ); + + for (int i = 0; i < 500; ++i) { + nvs_handle_t my_handle; + uint8_t key[20] = {0}; + + TEST_ESP_OK( nvs_open("leak_check_ns", NVS_READWRITE, &my_handle) ); + TEST_ESP_OK( nvs_set_blob(my_handle, "key", key, sizeof(key)) ); + TEST_ESP_OK( nvs_commit(my_handle) ); + nvs_close(my_handle); + printf("%d\n", esp_get_free_heap_size()); + } + + nvs_flash_deinit(); + printf("%d\n", esp_get_free_heap_size()); + /* heap leaks will be checked in unity_platform.c */ +} + +#ifdef CONFIG_NVS_ENCRYPTION +TEST_CASE("check underlying xts code for 32-byte size sector encryption", "[nvs]") +{ + uint8_t eky_hex[2 * NVS_KEY_SIZE] = { 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11, + /* Tweak key below*/ + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22 }; + + uint8_t ba_hex[16] = { 0x33,0x33,0x33,0x33,0x33,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; + + uint8_t ptxt_hex[32] = { 0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44, + 0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44, + 0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44, + 0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44 }; + + + uint8_t ctxt_hex[32] = { 0xe6,0x22,0x33,0x4f,0x18,0x4b,0xbc,0xe1, + 0x29,0xa2,0x5b,0x2a,0xc7,0x6b,0x3d,0x92, + 0xab,0xf9,0x8e,0x22,0xdf,0x5b,0xdd,0x15, + 0xaf,0x47,0x1f,0x3d,0xb8,0x94,0x6a,0x85 }; + + mbedtls_aes_xts_context ectx[1]; + mbedtls_aes_xts_context dctx[1]; + + mbedtls_aes_xts_init(ectx); + mbedtls_aes_xts_init(dctx); + + TEST_ASSERT_TRUE(!mbedtls_aes_xts_setkey_enc(ectx, eky_hex, 2 * NVS_KEY_SIZE * 8)); + TEST_ASSERT_TRUE(!mbedtls_aes_xts_setkey_enc(dctx, eky_hex, 2 * NVS_KEY_SIZE * 8)); + + TEST_ASSERT_TRUE(!mbedtls_aes_crypt_xts(ectx, MBEDTLS_AES_ENCRYPT, 32, ba_hex, ptxt_hex, ptxt_hex)); + + TEST_ASSERT_TRUE(!memcmp(ptxt_hex, ctxt_hex, 32)); +} + +TEST_CASE("Check nvs key partition APIs (read and generate keys)", "[nvs]") +{ + nvs_sec_cfg_t cfg, cfg2; + + const esp_partition_t* key_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + + if (!esp_flash_encryption_enabled()) { + TEST_IGNORE_MESSAGE("flash encryption disabled, skipping nvs_key partition related tests"); + } + + TEST_ESP_OK(esp_partition_erase_range(key_part, 0, key_part->size)); + TEST_ESP_ERR(ESP_ERR_NVS_KEYS_NOT_INITIALIZED, nvs_flash_read_security_cfg(key_part, &cfg)); + + TEST_ESP_OK(nvs_flash_generate_keys(key_part, &cfg)); + + TEST_ESP_OK(nvs_flash_read_security_cfg(key_part, &cfg2)); + + TEST_ASSERT_TRUE(!memcmp(&cfg, &cfg2, sizeof(nvs_sec_cfg_t))); +} + + +TEST_CASE("test nvs apis with encryption enabled", "[nvs]") +{ + if (!esp_flash_encryption_enabled()) { + TEST_IGNORE_MESSAGE("flash encryption disabled, skipping nvs_api tests with encryption enabled"); + } + const esp_partition_t* key_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + + assert(key_part && "partition table must have an NVS Key partition"); + + const esp_partition_t* nvs_partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + assert(nvs_partition && "partition table must have an NVS partition"); + + ESP_ERROR_CHECK( esp_partition_erase_range(key_part, 0, key_part->size) ); + + bool done = false; + + do { + ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) ); + + nvs_sec_cfg_t cfg; + esp_err_t err = nvs_flash_read_security_cfg(key_part, &cfg); + + if(err == ESP_ERR_NVS_KEYS_NOT_INITIALIZED) { + uint8_t value[4096] = {[0 ... 4095] = 0xff}; + TEST_ESP_OK(esp_partition_write(key_part, 0, value, sizeof(value))); + + TEST_ESP_ERR(ESP_ERR_NVS_KEYS_NOT_INITIALIZED, nvs_flash_read_security_cfg(key_part, &cfg)); + + TEST_ESP_OK(nvs_flash_generate_keys(key_part, &cfg)); + } else { + /* Second time key_partition exists already*/ + ESP_ERROR_CHECK(err); + done = true; + } + TEST_ESP_OK(nvs_flash_secure_init(&cfg)); + + nvs_handle_t handle_1; + + TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, nvs_open("namespace1", NVS_READONLY, &handle_1)); + + + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); + + TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); + TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789)); + + nvs_handle_t handle_2; + TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a)); + const char* str = "value 0123456789abcdef0123456789abcdef"; + TEST_ESP_OK(nvs_set_str(handle_2, "key", str)); + + int32_t v1; + TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1)); + TEST_ASSERT_TRUE(0x23456789 == v1); + + int32_t v2; + TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2)); + TEST_ASSERT_TRUE(0x3456789a == v2); + + char buf[strlen(str) + 1]; + size_t buf_len = sizeof(buf); + + size_t buf_len_needed; + TEST_ESP_OK(nvs_get_str(handle_2, "key", NULL, &buf_len_needed)); + TEST_ASSERT_TRUE(buf_len_needed == buf_len); + + size_t buf_len_short = buf_len - 1; + TEST_ESP_ERR(ESP_ERR_NVS_INVALID_LENGTH, nvs_get_str(handle_2, "key", buf, &buf_len_short)); + TEST_ASSERT_TRUE(buf_len_short == buf_len); + + size_t buf_len_long = buf_len + 1; + TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len_long)); + TEST_ASSERT_TRUE(buf_len_long == buf_len); + + TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); + + TEST_ASSERT_TRUE(0 == strcmp(buf, str)); + + nvs_close(handle_1); + nvs_close(handle_2); + + TEST_ESP_OK(nvs_flash_deinit()); + } while(!done); +} + +TEST_CASE("test nvs apis for nvs partition generator utility with encryption enabled", "[nvs_part_gen]") +{ + + if (!esp_flash_encryption_enabled()) { + TEST_IGNORE_MESSAGE("flash encryption disabled, skipping nvs_api tests with encryption enabled"); + } + + nvs_handle_t handle; + nvs_sec_cfg_t xts_cfg; + + extern const char nvs_key_start[] asm("_binary_encryption_keys_bin_start"); + extern const char nvs_key_end[] asm("_binary_encryption_keys_bin_end"); + + extern const char nvs_data_start[] asm("_binary_partition_encrypted_bin_start"); + + extern const char sample_bin_start[] asm("_binary_sample_bin_start"); + + const esp_partition_t* key_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + + const esp_partition_t* nvs_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + + assert(key_part && "partition table must have a KEY partition"); + TEST_ASSERT_TRUE((nvs_key_end - nvs_key_start - 1) == SPI_FLASH_SEC_SIZE); + + assert(nvs_part && "partition table must have an NVS partition"); + printf("\n nvs_part size:%d\n", nvs_part->size); + + ESP_ERROR_CHECK(esp_partition_erase_range(key_part, 0, key_part->size)); + ESP_ERROR_CHECK( esp_partition_erase_range(nvs_part, 0, nvs_part->size) ); + + for (int i = 0; i < key_part->size; i+= SPI_FLASH_SEC_SIZE) { + ESP_ERROR_CHECK( esp_partition_write(key_part, i, nvs_key_start + i, SPI_FLASH_SEC_SIZE) ); + } + + for (int i = 0; i < nvs_part->size; i+= SPI_FLASH_SEC_SIZE) { + ESP_ERROR_CHECK( esp_partition_write(nvs_part, i, nvs_data_start + i, SPI_FLASH_SEC_SIZE) ); + } + + esp_err_t err = nvs_flash_read_security_cfg(key_part, &xts_cfg); + ESP_ERROR_CHECK(err); + + TEST_ESP_OK(nvs_flash_secure_init(&xts_cfg)); + + TEST_ESP_OK(nvs_open("dummyNamespace", NVS_READONLY, &handle)); + uint8_t u8v; + TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v)); + TEST_ASSERT_TRUE(u8v == 127); + int8_t i8v; + TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v)); + TEST_ASSERT_TRUE(i8v == -128); + uint16_t u16v; + TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v)); + TEST_ASSERT_TRUE(u16v == 32768); + uint32_t u32v; + TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v)); + TEST_ASSERT_TRUE(u32v == 4294967295); + int32_t i32v; + TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v)); + TEST_ASSERT_TRUE(i32v == -2147483648); + + char buf[64] = {0}; + size_t buflen = 64; + TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", buf, &buflen)); + TEST_ASSERT_TRUE(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0); + + uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; + buflen = 64; + TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); + TEST_ASSERT_TRUE(memcmp(buf, hexdata, buflen) == 0); + + uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; + buflen = 64; + TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + TEST_ASSERT_TRUE(memcmp(buf, base64data, buflen) == 0); + + uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; + buflen = 64; + TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen)); + TEST_ASSERT_TRUE(memcmp(buf, hexfiledata, buflen) == 0); + + uint8_t base64filedata[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xab, 0xcd, 0xef}; + buflen = 64; + TEST_ESP_OK( nvs_get_blob(handle, "base64FileKey", buf, &buflen)); + TEST_ASSERT_TRUE(memcmp(buf, base64filedata, buflen) == 0); + + uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0"; + buflen = 64; + TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen)); + TEST_ASSERT_TRUE(memcmp(buf, strfiledata, buflen) == 0); + + char bin_data[5120]; + size_t bin_len = sizeof(bin_data); + TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); + TEST_ASSERT_TRUE(memcmp(bin_data, sample_bin_start, bin_len) == 0); + + nvs_close(handle); + TEST_ESP_OK(nvs_flash_deinit()); + +} +#endif diff --git a/components/nvs_flash/test_nvs_host/Makefile b/components/nvs_flash/test_nvs_host/Makefile index f1f5b52f2..5031de6a8 100644 --- a/components/nvs_flash/test_nvs_host/Makefile +++ b/components/nvs_flash/test_nvs_host/Makefile @@ -3,6 +3,7 @@ all: $(TEST_PROGRAM) SOURCE_FILES = \ esp_error_check_stub.cpp \ + ../mock/int/crc.cpp \ $(addprefix ../src/, \ nvs_types.cpp \ nvs_api.cpp \ @@ -10,37 +11,61 @@ SOURCE_FILES = \ nvs_pagemanager.cpp \ nvs_storage.cpp \ nvs_item_hash_list.cpp \ + nvs_handle_simple.cpp \ + nvs_handle_locked.cpp \ + nvs_partition_manager.cpp \ + nvs_partition.cpp \ + nvs_encrypted_partition.cpp \ + nvs_cxx_api.cpp \ ) \ spi_flash_emulation.cpp \ test_compressed_enum_table.cpp \ test_spi_flash_emulation.cpp \ test_intrusive_list.cpp \ test_nvs.cpp \ - crc.cpp \ + test_partition_manager.cpp \ + test_nvs_handle.cpp \ + test_nvs_storage.cpp \ + test_nvs_partition.cpp \ + test_nvs_cxx_api.cpp \ + test_nvs_initialization.cpp \ main.cpp -CPPFLAGS += -I../include -I../src -I./ -I../../esp32/include -I ../../spi_flash/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -CFLAGS += -fprofile-arcs -ftest-coverage -CXXFLAGS += -std=c++11 -Wall -Werror +ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1) +COMPILER := clang +else +COMPILER := gcc +endif + +CPPFLAGS += -I../include -I../src -I../mock/int -I./ -I../../esp_common/include -I../../esp32/include -I ../../mbedtls/mbedtls/include -I ../../spi_flash/include -I ../../hal/include -I ../../xtensa/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -g2 -ggdb +CFLAGS += -fprofile-arcs -ftest-coverage -DLINUX_TARGET +CXXFLAGS += -std=c++11 -Wall -Werror -DLINUX_TARGET LDFLAGS += -lstdc++ -Wall -fprofile-arcs -ftest-coverage +ifeq ($(COMPILER),clang) +CFLAGS += -fsanitize=address +CXXFLAGS += -fsanitize=address +LDFLAGS += -fsanitize=address +endif + OBJ_FILES = $(SOURCE_FILES:.cpp=.o) COVERAGE_FILES = $(OBJ_FILES:.o=.gc*) $(OBJ_FILES): %.o: %.cpp -$(TEST_PROGRAM): $(OBJ_FILES) - g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES) +$(TEST_PROGRAM): clean-coverage $(OBJ_FILES) + $(MAKE) -C ../../mbedtls/mbedtls/ lib + g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES) ../../mbedtls/mbedtls/library/libmbedcrypto.a $(OUTPUT_DIR): mkdir -p $(OUTPUT_DIR) test: $(TEST_PROGRAM) - ./$(TEST_PROGRAM) + ./$(TEST_PROGRAM) -d yes exclude:[long] long-test: $(TEST_PROGRAM) - ./$(TEST_PROGRAM) [list],[enumtable],[spi_flash_emu],[nvs],[long] + ./$(TEST_PROGRAM) -d yes $(COVERAGE_FILES): $(TEST_PROGRAM) long-test @@ -52,10 +77,26 @@ coverage_report: coverage.info genhtml coverage.info --output-directory coverage_report @echo "Coverage report is in coverage_report/index.html" -clean: - rm -f $(OBJ_FILES) $(TEST_PROGRAM) +clean-coverage: rm -f $(COVERAGE_FILES) *.gcov rm -rf coverage_report/ rm -f coverage.info -.PHONY: clean all test +clean: clean-coverage + $(MAKE) -C ../../mbedtls/mbedtls/ clean + rm -f $(OBJ_FILES) $(TEST_PROGRAM) + rm -f ../nvs_partition_generator/partition_single_page.bin + rm -f ../nvs_partition_generator/partition_multipage_blob.bin + rm -f ../nvs_partition_generator/partition_encrypted.bin + rm -f ../nvs_partition_generator/partition_encrypted_using_keygen.bin + rm -f ../nvs_partition_generator/partition_encrypted_using_keyfile.bin + rm -f ../nvs_partition_generator/partition_decrypted.bin + rm -f ../nvs_partition_generator/partition_encoded.bin + rm -f ../nvs_partition_generator/Test-1-partition-encrypted.bin + rm -f ../nvs_partition_generator/Test-1-partition.bin + rm -f ../../../tools/mass_mfg/samples/sample_values_multipage_blob_created.csv + rm -f ../../../tools/mass_mfg/samples/sample_values_singlepage_blob_created.csv + + + +.PHONY: clean clean-coverage all test long-test diff --git a/components/nvs_flash/test_nvs_host/README.md b/components/nvs_flash/test_nvs_host/README.md new file mode 100644 index 000000000..bad5b08cd --- /dev/null +++ b/components/nvs_flash/test_nvs_host/README.md @@ -0,0 +1,22 @@ +# Build + +```bash +make -j 6 +``` + +# Run +* Run particular test case: +```bash +./test_nvs "" + +``` +* Run all quick tests: +```bash +./test_nvs -d yes exclude:[long] +``` + +* Run all tests (takes several hours) +```bash +./test_nvs -d yes +``` + diff --git a/components/nvs_flash/test_nvs_host/esp_error_check_stub.cpp b/components/nvs_flash/test_nvs_host/esp_error_check_stub.cpp index 9cff4af31..34d8704e7 100644 --- a/components/nvs_flash/test_nvs_host/esp_error_check_stub.cpp +++ b/components/nvs_flash/test_nvs_host/esp_error_check_stub.cpp @@ -1,9 +1,14 @@ #include "catch.hpp" #include "esp_err.h" +#include "sdkconfig.h" void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) { - printf("ESP_ERROR_CHECK failed: esp_err_t 0x%x at %p\n", rc, __builtin_return_address(0)); + printf("ESP_ERROR_CHECK failed: esp_err_t 0x%x", rc); +#ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP + printf(" (%s)", esp_err_to_name(rc)); +#endif //CONFIG_ESP_ERR_TO_NAME_LOOKUP + printf(" at %p\n", __builtin_return_address(0)); printf("file: \"%s\" line %d\nfunc: %s\nexpression: %s\n", file, line, function, expression); abort(); } diff --git a/components/nvs_flash/test_nvs_host/sdkconfig.h b/components/nvs_flash/test_nvs_host/sdkconfig.h index e69de29bb..a38e0a10d 100644 --- a/components/nvs_flash/test_nvs_host/sdkconfig.h +++ b/components/nvs_flash/test_nvs_host/sdkconfig.h @@ -0,0 +1,3 @@ +#define CONFIG_NVS_ENCRYPTION 1 +//currently use the legacy implementation, since the stubs for new HAL are not done yet +#define CONFIG_SPI_FLASH_USE_LEGACY_IMPL 1 diff --git a/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp b/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp index 914efc145..802256c1a 100644 --- a/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp +++ b/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#include "esp_spi_flash.h" +#include "esp_partition.h" #include "spi_flash_emulation.h" @@ -22,39 +22,82 @@ void spi_flash_emulator_set(SpiFlashEmulator* e) s_emulator = e; } -esp_err_t spi_flash_erase_sector(size_t sec) +esp_err_t esp_partition_erase_range(const esp_partition_t* partition, + size_t offset, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; } - if (!s_emulator->erase(sec)) { + if (size % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_SIZE; + } + + if (offset % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + + size_t start_sector = offset / SPI_FLASH_SEC_SIZE; + size_t num_sectors = size / SPI_FLASH_SEC_SIZE; + for (size_t sector = start_sector; sector < (start_sector + num_sectors); sector++) { + if (!s_emulator->erase(sector)) { + return ESP_ERR_FLASH_OP_FAIL; + } + } + + return ESP_OK; +} + +esp_err_t esp_partition_read(const esp_partition_t* partition, + size_t src_offset, void* dst, size_t size) +{ + if (!s_emulator) { + return ESP_ERR_FLASH_OP_TIMEOUT; + } + + if (!s_emulator->read(reinterpret_cast(dst), src_offset, size)) { return ESP_ERR_FLASH_OP_FAIL; } return ESP_OK; } -esp_err_t spi_flash_write(size_t des_addr, const void *src_addr, size_t size) +esp_err_t esp_partition_read_raw(const esp_partition_t* partition, + size_t src_offset, void* dst, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; } - if (!s_emulator->write(des_addr, reinterpret_cast(src_addr), size)) { + if (!s_emulator->read(reinterpret_cast(dst), src_offset, size)) { return ESP_ERR_FLASH_OP_FAIL; } return ESP_OK; } -esp_err_t spi_flash_read(size_t src_addr, void *des_addr, size_t size) +esp_err_t esp_partition_write(const esp_partition_t* partition, + size_t dst_offset, const void* src, size_t size) { if (!s_emulator) { return ESP_ERR_FLASH_OP_TIMEOUT; } - if (!s_emulator->read(reinterpret_cast(des_addr), src_addr, size)) { + if (!s_emulator->write(dst_offset, reinterpret_cast(src), size)) { + return ESP_ERR_FLASH_OP_FAIL; + } + + return ESP_OK; +} + +esp_err_t esp_partition_write_raw(const esp_partition_t* partition, + size_t dst_offset, const void* src, size_t size) +{ + if (!s_emulator) { + return ESP_ERR_FLASH_OP_TIMEOUT; + } + + if (!s_emulator->write(dst_offset, reinterpret_cast(src), size)) { return ESP_ERR_FLASH_OP_FAIL; } @@ -71,10 +114,11 @@ static size_t blockEraseTime = 37142; static size_t timeInterp(uint32_t bytes, size_t* lut) { + const int lut_size = sizeof(readTimes)/sizeof(readTimes[0]); int lz = __builtin_clz(bytes / 4); int log_size = 32 - lz; size_t x2 = 1 << (log_size + 2); - size_t y2 = lut[log_size]; + size_t y2 = lut[std::min(log_size, lut_size - 1)]; size_t x1 = 1 << (log_size + 1); size_t y1 = lut[log_size - 1]; return (bytes - x1) * (y2 - y1) / (x2 - x1) + y1; @@ -94,4 +138,3 @@ size_t SpiFlashEmulator::getEraseOpTime() { return blockEraseTime; } - diff --git a/components/nvs_flash/test_nvs_host/spi_flash_emulation.h b/components/nvs_flash/test_nvs_host/spi_flash_emulation.h index d8099322d..f2aef6a01 100644 --- a/components/nvs_flash/test_nvs_host/spi_flash_emulation.h +++ b/components/nvs_flash/test_nvs_host/spi_flash_emulation.h @@ -36,6 +36,16 @@ class SpiFlashEmulator SpiFlashEmulator(size_t sectorCount) : mUpperSectorBound(sectorCount) { mData.resize(sectorCount * SPI_FLASH_SEC_SIZE / 4, 0xffffffff); + mEraseCnt.resize(sectorCount); + spi_flash_emulator_set(this); + } + + SpiFlashEmulator(const char *filename) + { + load(filename); + // Atleast one page should be free, hence we create mData of size of 2 sectors. + mData.resize(mData.size() + SPI_FLASH_SEC_SIZE / 4, 0xffffffff); + mUpperSectorBound = mData.size() * 4 / SPI_FLASH_SEC_SIZE; spi_flash_emulator_set(this); } @@ -67,13 +77,13 @@ class SpiFlashEmulator WARN("invalid flash operation detected: erase sector=" << sectorNumber); return false; } - + if (dstAddr % 4 != 0 || size % 4 != 0 || dstAddr + size > mData.size() * 4) { return false; } - + for (size_t i = 0; i < size / 4; ++i) { if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) { return false; @@ -102,12 +112,12 @@ class SpiFlashEmulator if (offset > mData.size()) { return false; } - + if (sectorNumber < mLowerSectorBound || sectorNumber >= mUpperSectorBound) { WARN("invalid flash operation detected: erase sector=" << sectorNumber); return false; } - + if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) { return false; } @@ -115,10 +125,11 @@ class SpiFlashEmulator std::fill_n(begin(mData) + offset, SPI_FLASH_SEC_SIZE / 4, 0xffffffff); ++mEraseOps; + mEraseCnt[sectorNumber]++; mTotalTime += getEraseOpTime(); return true; } - + void randomize(uint32_t seed) { std::random_device rd; @@ -141,7 +152,7 @@ class SpiFlashEmulator { return reinterpret_cast(mData.data()); } - + void load(const char* filename) { FILE* f = fopen(filename, "rb"); @@ -154,7 +165,7 @@ class SpiFlashEmulator assert(s == static_cast(size / SPI_FLASH_SEC_SIZE)); fclose(f); } - + void save(const char* filename) { FILE* f = fopen(filename, "wb"); @@ -198,16 +209,20 @@ class SpiFlashEmulator { return mTotalTime; } - + void setBounds(uint32_t lowerSector, uint32_t upperSector) { mLowerSectorBound = lowerSector; mUpperSectorBound = upperSector; } - + void failAfter(uint32_t count) { mFailCountdown = count; } + size_t getSectorEraseCount(uint32_t sector) const { + return mEraseCnt[sector]; + } + protected: static size_t getReadOpTime(uint32_t bytes); static size_t getWriteOpTime(uint32_t bytes); @@ -215,6 +230,7 @@ class SpiFlashEmulator std::vector mData; + std::vector mEraseCnt; mutable size_t mReadOps = 0; mutable size_t mWriteOps = 0; @@ -224,7 +240,7 @@ class SpiFlashEmulator mutable size_t mTotalTime = 0; size_t mLowerSectorBound = 0; size_t mUpperSectorBound = 0; - + size_t mFailCountdown = SIZE_MAX; }; diff --git a/components/nvs_flash/test_nvs_host/test_compressed_enum_table.cpp b/components/nvs_flash/test_nvs_host/test_compressed_enum_table.cpp index 225a11c27..a404545c7 100644 --- a/components/nvs_flash/test_nvs_host/test_compressed_enum_table.cpp +++ b/components/nvs_flash/test_nvs_host/test_compressed_enum_table.cpp @@ -53,4 +53,4 @@ TEST_CASE("test if CompressedEnumTable works as expected", "[enumtable]") CHECK(table.data()[0] == 0x93909249); -} \ No newline at end of file +} diff --git a/components/nvs_flash/test_nvs_host/test_fixtures.hpp b/components/nvs_flash/test_nvs_host/test_fixtures.hpp new file mode 100644 index 000000000..473f1da97 --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_fixtures.hpp @@ -0,0 +1,149 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "nvs_partition.hpp" +#include "nvs_encrypted_partition.hpp" +#include "spi_flash_emulation.h" +#include "nvs.h" + +class PartitionEmulation : public nvs::Partition { +public: + PartitionEmulation(SpiFlashEmulator *spi_flash_emulator, + uint32_t address, + uint32_t size, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : partition_name(partition_name), flash_emu(spi_flash_emulator), address(address), size(size) + { + assert(partition_name); + assert(flash_emu); + assert(size); + } + + const char *get_partition_name() override + { + return partition_name; + } + + esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override + { + if (!flash_emu->read(reinterpret_cast(dst), src_offset, size)) { + return ESP_ERR_FLASH_OP_FAIL; + } + + return ESP_OK; + } + + esp_err_t read(size_t src_offset, void* dst, size_t size) override + { + if (!flash_emu->read(reinterpret_cast(dst), src_offset, size)) { + return ESP_ERR_FLASH_OP_FAIL; + } + + return ESP_OK; + } + + esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override + { + if (!flash_emu->write(dst_offset, reinterpret_cast(src), size)) { + return ESP_ERR_FLASH_OP_FAIL; + } + + return ESP_OK; + } + + esp_err_t write(size_t dst_offset, const void* src, size_t size) override + { + if (!flash_emu->write(dst_offset, reinterpret_cast(src), size)) { + return ESP_ERR_FLASH_OP_FAIL; + } + + return ESP_OK; + } + + esp_err_t erase_range(size_t dst_offset, size_t size) override + { + if (size % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_SIZE; + } + + if (dst_offset % SPI_FLASH_SEC_SIZE != 0) { + return ESP_ERR_INVALID_ARG; + } + + size_t start_sector = dst_offset / SPI_FLASH_SEC_SIZE; + size_t num_sectors = size / SPI_FLASH_SEC_SIZE; + for (size_t sector = start_sector; sector < (start_sector + num_sectors); sector++) { + if (!flash_emu->erase(sector)) { + return ESP_ERR_FLASH_OP_FAIL; + } + } + + return ESP_OK; + } + + uint32_t get_address() override + { + return address; + } + + uint32_t get_size() override + { + return size; + } + +private: + const char *partition_name; + + SpiFlashEmulator *flash_emu; + + uint32_t address; + + uint32_t size; +}; + +struct PartitionEmulationFixture { + PartitionEmulationFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : emu(start_sector + sector_size), + part(&emu, start_sector * SPI_FLASH_SEC_SIZE, sector_size * SPI_FLASH_SEC_SIZE, partition_name) { + } + + ~PartitionEmulationFixture() { } + + SpiFlashEmulator emu; + + PartitionEmulation part; +}; + +struct EncryptedPartitionFixture { + EncryptedPartitionFixture(nvs_sec_cfg_t *cfg, + uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : esp_partition(), emu(start_sector + sector_size), + part(&esp_partition) { + esp_partition.address = start_sector * SPI_FLASH_SEC_SIZE; + esp_partition.size = sector_size * SPI_FLASH_SEC_SIZE; + strncpy(esp_partition.label, partition_name, PART_NAME_MAX_SIZE); + assert(part.init(cfg) == ESP_OK); + } + + ~EncryptedPartitionFixture() { } + + esp_partition_t esp_partition; + + SpiFlashEmulator emu; + + nvs::NVSEncryptedPartition part; +}; diff --git a/components/nvs_flash/test_nvs_host/test_intrusive_list.cpp b/components/nvs_flash/test_nvs_host/test_intrusive_list.cpp index 979438a0b..dea63f3c3 100644 --- a/components/nvs_flash/test_nvs_host/test_intrusive_list.cpp +++ b/components/nvs_flash/test_nvs_host/test_intrusive_list.cpp @@ -197,8 +197,8 @@ TEST_CASE("can erase all items in the list using clear method", "[list]") TestNode n4("four", 4); TestNode n5("five", 5); TestNode n6("six", 6); - - + + list.push_back(&n1); list.push_back(&n2); list.insert(++list.begin(), &n3); @@ -207,7 +207,6 @@ TEST_CASE("can erase all items in the list using clear method", "[list]") list.insert(list.begin(), &n6); list.clear(); - + REQUIRE(std::begin(list) == std::end(list)); } - diff --git a/components/nvs_flash/test_nvs_host/test_nvs.cpp b/components/nvs_flash/test_nvs_host/test_nvs.cpp index c23f5d661..d8467dfe5 100644 --- a/components/nvs_flash/test_nvs_host/test_nvs.cpp +++ b/components/nvs_flash/test_nvs_host/test_nvs.cpp @@ -14,9 +14,21 @@ #include "catch.hpp" #include "nvs.hpp" #include "nvs_test_api.h" +#include "sdkconfig.h" #include "spi_flash_emulation.h" +#include "nvs_partition_manager.hpp" +#include "nvs_partition.hpp" +#include "mbedtls/aes.h" #include #include +#include +#include +#include +#include +#include +#include + +#include "test_fixtures.hpp" #define TEST_ESP_ERR(rc, res) CHECK((rc) == (res)) #define TEST_ESP_OK(rc) CHECK((rc) == ESP_OK) @@ -45,7 +57,7 @@ TEST_CASE("crc32 behaves as expected", "[nvs]") item1.datatype = ItemType::I32; item1.nsIndex = 1; item1.crc32 = 0; - item1.reserved = 0xff; + item1.chunkIndex = 0xff; fill_n(item1.key, sizeof(item1.key), 0xbb); fill_n(item1.data, sizeof(item1.data), 0xaa); @@ -69,20 +81,20 @@ TEST_CASE("crc32 behaves as expected", "[nvs]") CHECK(crc32_1 != item2.calculateCrc32()); } -TEST_CASE("starting with empty flash, page is in uninitialized state", "[nvs]") +TEST_CASE("Page starting with empty flash is in uninitialized state", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; CHECK(page.state() == Page::PageState::INVALID); - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); CHECK(page.state() == Page::PageState::UNINITIALIZED); } -TEST_CASE("can distinguish namespaces", "[nvs]") +TEST_CASE("Page can distinguish namespaces", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); int32_t val1 = 0x12345678; CHECK(page.writeItem(1, ItemType::I32, "intval1", &val1, sizeof(val1)) == ESP_OK); int32_t val2 = 0x23456789; @@ -94,32 +106,32 @@ TEST_CASE("can distinguish namespaces", "[nvs]") } -TEST_CASE("reading with different type causes type mismatch error", "[nvs]") +TEST_CASE("Page reading with different type causes type mismatch error", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); int32_t val = 0x12345678; CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK); CHECK(page.readItem(1, ItemType::U32, "intval1", &val, sizeof(val)) == ESP_ERR_NVS_TYPE_MISMATCH); } -TEST_CASE("when page is erased, it's state becomes UNITIALIZED", "[nvs]") +TEST_CASE("Page when erased, it's state becomes UNITIALIZED", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); int32_t val = 0x12345678; CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK); CHECK(page.erase() == ESP_OK); CHECK(page.state() == Page::PageState::UNINITIALIZED); } -TEST_CASE("when writing and erasing, used/erased counts are updated correctly", "[nvs]") +TEST_CASE("Page when writing and erasing, used/erased counts are updated correctly", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); CHECK(page.getUsedEntryCount() == 0); CHECK(page.getErasedEntryCount() == 0); uint32_t foo1 = 0; @@ -146,11 +158,11 @@ TEST_CASE("when writing and erasing, used/erased counts are updated correctly", CHECK(page.getErasedEntryCount() == Page::ENTRY_COUNT - 1); } -TEST_CASE("when page is full, adding an element fails", "[nvs]") +TEST_CASE("Page when page is full, adding an element fails", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) { char name[16]; snprintf(name, sizeof(name), "i%ld", (long int)i); @@ -159,30 +171,30 @@ TEST_CASE("when page is full, adding an element fails", "[nvs]") CHECK(page.writeItem(1, "foo", 64UL) == ESP_ERR_NVS_PAGE_FULL); } -TEST_CASE("page maintains its seq number") +TEST_CASE("Page maintains its seq number") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; { Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); CHECK(page.setSeqNumber(123) == ESP_OK); int32_t val = 42; CHECK(page.writeItem(1, ItemType::I32, "dummy", &val, sizeof(val)) == ESP_OK); } { Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); uint32_t seqno; CHECK(page.getSeqNumber(seqno) == ESP_OK); CHECK(seqno == 123); } } -TEST_CASE("can write and read variable length data", "[nvs]") +TEST_CASE("Page can write and read variable length data", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - CHECK(page.load(0) == ESP_OK); + CHECK(page.load(&f.part, 0) == ESP_OK); const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; size_t len = strlen(str); CHECK(page.writeItem(1, "stuff1", 42) == ESP_OK); @@ -212,11 +224,11 @@ TEST_CASE("can write and read variable length data", "[nvs]") CHECK(memcmp(buf, str, strlen(str)) == 0); } -TEST_CASE("different key names are distinguished even if the pointer is the same", "[nvs]") +TEST_CASE("Page different key names are distinguished even if the pointer is the same", "[nvs]") { - SpiFlashEmulator emu(1); + PartitionEmulationFixture f; Page page; - TEST_ESP_OK(page.load(0)); + TEST_ESP_OK(page.load(&f.part, 0)); TEST_ESP_OK(page.writeItem(1, "i1", 1)); TEST_ESP_OK(page.writeItem(1, "i2", 2)); int32_t value; @@ -233,9 +245,9 @@ TEST_CASE("different key names are distinguished even if the pointer is the same TEST_CASE("Page validates key size", "[nvs]") { - SpiFlashEmulator emu(4); + PartitionEmulationFixture f(0, 4); Page page; - TEST_ESP_OK(page.load(0)); + TEST_ESP_OK(page.load(&f.part, 0)); // 16-character key fails TEST_ESP_ERR(page.writeItem(1, "0123456789123456", 1), ESP_ERR_NVS_KEY_TOO_LONG); // 15-character key is okay @@ -244,56 +256,97 @@ TEST_CASE("Page validates key size", "[nvs]") TEST_CASE("Page validates blob size", "[nvs]") { - SpiFlashEmulator emu(4); + PartitionEmulationFixture f(0, 4); Page page; - TEST_ESP_OK(page.load(0)); + TEST_ESP_OK(page.load(&f.part, 0)); - char buf[2048] = { 0 }; + char buf[4096] = { 0 }; // There are two potential errors here: // - not enough space in the page (because one value has been written already) // - value is too long // Check that the second one is actually returned. TEST_ESP_ERR(page.writeItem(1, ItemType::BLOB, "2", buf, Page::ENTRY_COUNT * Page::ENTRY_SIZE), ESP_ERR_NVS_VALUE_TOO_LONG); // Should fail as well - TEST_ESP_ERR(page.writeItem(1, ItemType::BLOB, "2", buf, Page::BLOB_MAX_SIZE + 1), ESP_ERR_NVS_VALUE_TOO_LONG); - TEST_ESP_OK(page.writeItem(1, ItemType::BLOB, "2", buf, Page::BLOB_MAX_SIZE)); + TEST_ESP_ERR(page.writeItem(1, ItemType::BLOB, "2", buf, Page::CHUNK_MAX_SIZE + 1), ESP_ERR_NVS_VALUE_TOO_LONG); + TEST_ESP_OK(page.writeItem(1, ItemType::BLOB, "2", buf, Page::CHUNK_MAX_SIZE)); } TEST_CASE("Page handles invalid CRC of variable length items", "[nvs][cur]") { - SpiFlashEmulator emu(4); + PartitionEmulationFixture f(0, 4); { Page page; - TEST_ESP_OK(page.load(0)); + TEST_ESP_OK(page.load(&f.part, 0)); char buf[128] = {0}; TEST_ESP_OK(page.writeItem(1, ItemType::BLOB, "1", buf, sizeof(buf))); } // corrupt header of the item (64 is the offset of the first item in page) uint32_t overwrite_buf = 0; - emu.write(64, &overwrite_buf, 4); + f.emu.write(64, &overwrite_buf, 4); // load page again { Page page; - TEST_ESP_OK(page.load(0)); + TEST_ESP_OK(page.load(&f.part, 0)); + } +} + +class HashListTestHelper : public HashList +{ + public: + size_t getBlockCount() + { + return mBlockList.size(); + } +}; + +TEST_CASE("HashList is cleaned up as soon as items are erased", "[nvs]") +{ + HashListTestHelper hashlist; + // Add items + const size_t count = 128; + for (size_t i = 0; i < count; ++i) { + char key[16]; + snprintf(key, sizeof(key), "i%ld", (long int)i); + Item item(1, ItemType::U32, 1, key); + hashlist.insert(item, i); + } + INFO("Added " << count << " items, " << hashlist.getBlockCount() << " blocks"); + // Remove them in reverse order + for (size_t i = count; i > 0; --i) { + hashlist.erase(i - 1, true); + } + CHECK(hashlist.getBlockCount() == 0); + // Add again + for (size_t i = 0; i < count; ++i) { + char key[16]; + snprintf(key, sizeof(key), "i%ld", (long int)i); + Item item(1, ItemType::U32, 1, key); + hashlist.insert(item, i); + } + INFO("Added " << count << " items, " << hashlist.getBlockCount() << " blocks"); + // Remove them in the same order + for (size_t i = 0; i < count; ++i) { + hashlist.erase(i, true); } + CHECK(hashlist.getBlockCount() == 0); } TEST_CASE("can init PageManager in empty flash", "[nvs]") { - SpiFlashEmulator emu(4); + PartitionEmulationFixture f(0, 4); PageManager pm; - CHECK(pm.load(0, 4) == ESP_OK); + CHECK(pm.load(&f.part, 0, 4) == ESP_OK); } TEST_CASE("PageManager adds page in the correct order", "[nvs]") { const size_t pageCount = 8; - SpiFlashEmulator emu(pageCount); + PartitionEmulationFixture f(0, pageCount); uint32_t pageNo[pageCount] = { -1U, 50, 11, -1U, 23, 22, 24, 49}; for (uint32_t i = 0; i < pageCount; ++i) { Page p; - p.load(i); + p.load(&f.part, i); if (pageNo[i] != -1U) { p.setSeqNumber(pageNo[i]); p.writeItem(1, "foo", 10U); @@ -301,7 +354,7 @@ TEST_CASE("PageManager adds page in the correct order", "[nvs]") } PageManager pageManager; - CHECK(pageManager.load(0, pageCount) == ESP_OK); + CHECK(pageManager.load(&f.part, 0, pageCount) == ESP_OK); uint32_t lastSeqNo = 0; for (auto it = std::begin(pageManager); it != std::end(pageManager); ++it) { @@ -313,76 +366,77 @@ TEST_CASE("PageManager adds page in the correct order", "[nvs]") TEST_CASE("can init storage in empty flash", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); + cout << "before check" << endl; CHECK(storage.init(4, 4) == ESP_OK); - s_perf << "Time to init empty storage (4 sectors): " << emu.getTotalTime() << " us" << std::endl; + s_perf << "Time to init empty storage (4 sectors): " << f.emu.getTotalTime() << " us" << std::endl; } TEST_CASE("storage doesn't add duplicates within one page", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); int bar = 0; - CHECK(storage.writeItem(1, "bar", bar) == ESP_OK); - CHECK(storage.writeItem(1, "bar", bar) == ESP_OK); + CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK); + CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK); Page page; - page.load(4); + page.load(&f.part, 4); CHECK(page.getUsedEntryCount() == 1); CHECK(page.getErasedEntryCount() == 1); } TEST_CASE("can write one item a thousand times", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT * 4 * 2; ++i) { REQUIRE(storage.writeItem(1, "i", static_cast(i)) == ESP_OK); } - s_perf << "Time to write one item a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl; + s_perf << "Time to write one item a thousand times: " << f.emu.getTotalTime() << " us (" << f.emu.getEraseOps() << " " << f.emu.getWriteOps() << " " << f.emu.getReadOps() << " " << f.emu.getWriteBytes() << " " << f.emu.getReadBytes() << ")" << std::endl; } TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); int bar = 0; - CHECK(storage.writeItem(1, "bar", bar) == ESP_OK); + CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) { - CHECK(storage.writeItem(1, "foo", static_cast(bar)) == ESP_OK); + CHECK(storage.writeItem(1, "foo", static_cast(++bar)) == ESP_OK); } - CHECK(storage.writeItem(1, "bar", bar) == ESP_OK); + CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK); Page page; - page.load(4); + page.load(&f.part, 4); CHECK(page.findItem(1, itemTypeOf(), "bar") == ESP_ERR_NVS_NOT_FOUND); - page.load(5); + page.load(&f.part, 5); CHECK(page.findItem(1, itemTypeOf(), "bar") == ESP_OK); } TEST_CASE("storage can find items on second page if first is not fully written and has cached search data", "[nvs]") { - SpiFlashEmulator emu(3); - Storage storage; + PartitionEmulationFixture f(0, 3); + Storage storage(&f.part); CHECK(storage.init(0, 3) == ESP_OK); int bar = 0; - uint8_t bigdata[Page::BLOB_MAX_SIZE] = {0}; + uint8_t bigdata[(Page::CHUNK_MAX_SIZE - Page::ENTRY_SIZE)/2] = {0}; // write one big chunk of data ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "1", bigdata, sizeof(bigdata))); // write another big chunk of data ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "2", bigdata, sizeof(bigdata))); - + // write third one; it will not fit into the first page ESP_ERROR_CHECK(storage.writeItem(0, ItemType::BLOB, "3", bigdata, sizeof(bigdata))); - + size_t size; ESP_ERROR_CHECK(storage.getItemDataSize(0, ItemType::BLOB, "1", size)); CHECK(size == sizeof(bigdata)); @@ -393,9 +447,9 @@ TEST_CASE("storage can find items on second page if first is not fully written a TEST_CASE("can write and read variable length data lots of times", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; char buf[sizeof(str) + 16]; @@ -413,16 +467,16 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]") CHECK(storage.readItem(1, ItemType::SZ, "foobaar", buf, sizeof(buf)) == ESP_OK); CHECK(memcmp(buf, str, strlen(str) + 1) == 0); } - s_perf << "Time to write one string and one integer a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl; + s_perf << "Time to write one string and one integer a thousand times: " << f.emu.getTotalTime() << " us (" << f.emu.getEraseOps() << " " << f.emu.getWriteOps() << " " << f.emu.getReadOps() << " " << f.emu.getWriteBytes() << " " << f.emu.getReadBytes() << ")" << std::endl; } TEST_CASE("can get length of variable length data", "[nvs]") { - SpiFlashEmulator emu(8); - emu.randomize(200); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + f.emu.randomize(200); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; size_t len = strlen(str); @@ -439,24 +493,24 @@ TEST_CASE("can get length of variable length data", "[nvs]") TEST_CASE("can create namespaces", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); uint8_t nsi; CHECK(storage.createOrOpenNamespace("wifi", false, nsi) == ESP_ERR_NVS_NOT_FOUND); CHECK(storage.createOrOpenNamespace("wifi", true, nsi) == ESP_OK); Page page; - page.load(4); + page.load(&f.part, 4); CHECK(page.findItem(Page::NS_INDEX, ItemType::U8, "wifi") == ESP_OK); } TEST_CASE("storage may become full", "[nvs]") { - SpiFlashEmulator emu(8); - Storage storage; - emu.setBounds(4, 8); + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); + f.emu.setBounds(4, 8); CHECK(storage.init(4, 4) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT * 3; ++i) { char name[Item::MAX_KEY_LENGTH + 1]; @@ -468,19 +522,48 @@ TEST_CASE("storage may become full", "[nvs]") TEST_CASE("can modify an item on a page which will be erased", "[nvs]") { - SpiFlashEmulator emu(2); - Storage storage; + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); CHECK(storage.init(0, 2) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT * 3 + 1; ++i) { REQUIRE(storage.writeItem(1, "foo", 42U) == ESP_OK); } } +TEST_CASE("erase operations are distributed among sectors", "[nvs]") +{ + const size_t sectors = 6; + PartitionEmulationFixture f(0, sectors); + Storage storage(&f.part); + CHECK(storage.init(0, sectors) == ESP_OK); + + /* Fill some part of storage with static values */ + const size_t static_sectors = 2; + for (size_t i = 0; i < static_sectors * Page::ENTRY_COUNT; ++i) { + char name[Item::MAX_KEY_LENGTH]; + snprintf(name, sizeof(name), "static%d", (int) i); + REQUIRE(storage.writeItem(1, name, i) == ESP_OK); + } + + /* Now perform many write operations */ + const size_t write_ops = 2000; + for (size_t i = 0; i < write_ops; ++i) { + REQUIRE(storage.writeItem(1, "value", i) == ESP_OK); + } + + /* Check that erase counts are distributed between the remaining sectors */ + const size_t max_erase_cnt = write_ops / Page::ENTRY_COUNT / (sectors - static_sectors) + 1; + for (size_t i = 0; i < sectors; ++i) { + auto erase_cnt = f.emu.getSectorEraseCount(i); + INFO("Sector " << i << " erased " << erase_cnt); + CHECK(erase_cnt <= max_erase_cnt); + } +} TEST_CASE("can erase items", "[nvs]") { - SpiFlashEmulator emu(3); - Storage storage; + PartitionEmulationFixture f(0, 8); + Storage storage(&f.part); CHECK(storage.init(0, 3) == ESP_OK); for (size_t i = 0; i < Page::ENTRY_COUNT * 2 - 3; ++i) { char name[Item::MAX_KEY_LENGTH + 1]; @@ -498,22 +581,82 @@ TEST_CASE("can erase items", "[nvs]") CHECK(storage.readItem(3, "key00222", val) == ESP_ERR_NVS_NOT_FOUND); } +TEST_CASE("namespace name is deep copy", "[nvs]") +{ + char ns_name[16]; + strcpy(ns_name, "const_name"); + + nvs_handle_t handle_1; + nvs_handle_t handle_2; + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN); + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + TEST_ESP_OK(nvs_open("const_name", NVS_READWRITE, &handle_1)); + strcpy(ns_name, "just_kidding"); + + CHECK(nvs_open("just_kidding", NVS_READONLY, &handle_2) == ESP_ERR_NVS_NOT_FOUND); + + nvs_close(handle_1); + nvs_close(handle_2); + + nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); +} + +TEST_CASE("readonly handle fails on writing", "[nvs]") +{ + PartitionEmulationFixture f(0, 10); + const char* str = "value 0123456789abcdef0123456789abcdef"; + const uint8_t blob[8] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; + + nvs_handle_t handle_1; + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + // first, creating namespace... + TEST_ESP_OK(nvs_open("ro_ns", NVS_READWRITE, &handle_1)); + nvs_close(handle_1); + + TEST_ESP_OK(nvs_open("ro_ns", NVS_READONLY, &handle_1)); + TEST_ESP_ERR(nvs_set_i32(handle_1, "key", 47), ESP_ERR_NVS_READ_ONLY); + TEST_ESP_ERR(nvs_set_str(handle_1, "key", str), ESP_ERR_NVS_READ_ONLY); + TEST_ESP_ERR(nvs_set_blob(handle_1, "key", blob, 8), ESP_ERR_NVS_READ_ONLY); + + nvs_close(handle_1); + + // without deinit it affects "nvs api tests" + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); +} + TEST_CASE("nvs api tests", "[nvs]") { - SpiFlashEmulator emu(10); - emu.randomize(100); - - nvs_handle handle_1; + PartitionEmulationFixture f(0, 10); + f.emu.randomize(100); + + nvs_handle_t handle_1; const uint32_t NVS_FLASH_SECTOR = 6; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); for (uint16_t i = NVS_FLASH_SECTOR; i init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); @@ -524,7 +667,7 @@ TEST_CASE("nvs api tests", "[nvs]") TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789)); - nvs_handle handle_2; + nvs_handle_t handle_2; TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a)); const char* str = "value 0123456789abcdef0123456789abcdef"; @@ -544,11 +687,11 @@ TEST_CASE("nvs api tests", "[nvs]") size_t buf_len_needed; TEST_ESP_OK(nvs_get_str(handle_2, "key", NULL, &buf_len_needed)); CHECK(buf_len_needed == buf_len); - + size_t buf_len_short = buf_len - 1; TEST_ESP_ERR(ESP_ERR_NVS_INVALID_LENGTH, nvs_get_str(handle_2, "key", buf, &buf_len_short)); CHECK(buf_len_short == buf_len); - + size_t buf_len_long = buf_len + 1; TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len_long)); CHECK(buf_len_long == buf_len); @@ -556,51 +699,294 @@ TEST_CASE("nvs api tests", "[nvs]") TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); CHECK(0 == strcmp(buf, str)); + nvs_close(handle_1); + nvs_close(handle_2); + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); +} + +TEST_CASE("deinit partition doesn't affect other partition's open handles", "[nvs]") +{ + const char *OTHER_PARTITION_NAME = "other_part"; + PartitionEmulationFixture f(0, 10); + PartitionEmulationFixture f_other(0, 10, OTHER_PARTITION_NAME); + const char* str = "value 0123456789abcdef0123456789abcdef"; + const uint8_t blob[8] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; + + nvs_handle_t handle_1; + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + f_other.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f_other.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + TEST_ESP_OK(nvs_open_from_partition(OTHER_PARTITION_NAME, "ns", NVS_READWRITE, &handle_1)); + + // Deinitializing must not interfere with the open handle from the other partition. + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + + TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x3456789a)); + nvs_close(handle_1); + + TEST_ESP_OK(nvs_flash_deinit_partition(OTHER_PARTITION_NAME)); +} + +TEST_CASE("nvs iterators tests", "[nvs]") +{ + PartitionEmulationFixture f(0, 5); + + const uint32_t NVS_FLASH_SECTOR = 0; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 5; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { + f.emu.erase(i); + } + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_iterator_t it; + nvs_entry_info_t info; + nvs_handle handle_1; + nvs_handle handle_2; + const uint32_t blob = 0x11223344; + const char *name_1 = "namespace1"; + const char *name_2 = "namespace2"; + TEST_ESP_OK(nvs_open(name_1, NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open(name_2, NVS_READWRITE, &handle_2)); + + TEST_ESP_OK(nvs_set_i8(handle_1, "value1", -11)); + TEST_ESP_OK(nvs_set_u8(handle_1, "value2", 11)); + TEST_ESP_OK(nvs_set_i16(handle_1, "value3", 1234)); + TEST_ESP_OK(nvs_set_u16(handle_1, "value4", -1234)); + TEST_ESP_OK(nvs_set_i32(handle_1, "value5", -222)); + TEST_ESP_OK(nvs_set_i32(handle_1, "value6", -222)); + TEST_ESP_OK(nvs_set_i32(handle_1, "value7", -222)); + TEST_ESP_OK(nvs_set_u32(handle_1, "value8", 222)); + TEST_ESP_OK(nvs_set_u32(handle_1, "value9", 222)); + TEST_ESP_OK(nvs_set_str(handle_1, "value10", "foo")); + TEST_ESP_OK(nvs_set_blob(handle_1, "value11", &blob, sizeof(blob))); + TEST_ESP_OK(nvs_set_i32(handle_2, "value1", -111)); + TEST_ESP_OK(nvs_set_i32(handle_2, "value2", -111)); + TEST_ESP_OK(nvs_set_i64(handle_2, "value3", -555)); + TEST_ESP_OK(nvs_set_u64(handle_2, "value4", 555)); + + auto entry_count = [](const char *part, const char *name, nvs_type_t type)-> int { + int count; + nvs_iterator_t it = nvs_entry_find(part, name, type); + for (count = 0; it != nullptr; count++) { + it = nvs_entry_next(it); + } + return count; + }; + + SECTION("Number of entries found for specified namespace and type is correct") + { + CHECK(nvs_entry_find("", NULL, NVS_TYPE_ANY) == NULL); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_ANY) == 15); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY) == 11); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I32) == 3); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_I32) == 5); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_U64) == 1); + } + + SECTION("New entry is not created when existing key-value pair is set") + { + CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_2, NVS_TYPE_ANY) == 4); + TEST_ESP_OK(nvs_set_i32(handle_2, "value1", -222)); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_2, NVS_TYPE_ANY) == 4); + } + + SECTION("Number of entries found decrease when entry is erased") + { + CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_U64) == 1); + TEST_ESP_OK(nvs_erase_key(handle_2, "value4")); + CHECK(entry_count(NVS_DEFAULT_PART_NAME, "", NVS_TYPE_U64) == 0); + } + + SECTION("All fields of nvs_entry_info_t structure are correct") + { + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I32); + CHECK(it != nullptr); + string key = "value5"; + do { + nvs_entry_info(it, &info); + + CHECK(string(name_1) == info.namespace_name); + CHECK(key == info.key); + CHECK(info.type == NVS_TYPE_I32); + + it = nvs_entry_next(it); + key[5]++; + } while (it != NULL); + nvs_release_iterator(it); + } + + SECTION("Entry info is not affected by subsequent erase") + { + nvs_entry_info_t info_after_erase; + + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY); + nvs_entry_info(it, &info); + TEST_ESP_OK(nvs_erase_key(handle_1, "value1")); + nvs_entry_info(it, &info_after_erase); + CHECK(memcmp(&info, &info_after_erase, sizeof(info)) == 0); + nvs_release_iterator(it); + } + + SECTION("Entry info is not affected by subsequent set") + { + nvs_entry_info_t info_after_set; + + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY); + nvs_entry_info(it, &info); + TEST_ESP_OK(nvs_set_u8(handle_1, info.key, 44)); + nvs_entry_info(it, &info_after_set); + CHECK(memcmp(&info, &info_after_set, sizeof(info)) == 0); + nvs_release_iterator(it); + } + + + SECTION("Iterating over multiple pages works correctly") + { + nvs_handle handle_3; + const char *name_3 = "namespace3"; + const int entries_created = 250; + + TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3)); + for (size_t i = 0; i < entries_created; i++) { + TEST_ESP_OK(nvs_set_u8(handle_3, to_string(i).c_str(), 123)); + } + + int entries_found = 0; + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_3, NVS_TYPE_ANY); + while(it != nullptr) { + entries_found++; + it = nvs_entry_next(it); + } + CHECK(entries_created == entries_found); + + nvs_release_iterator(it); + nvs_close(handle_3); + } + + SECTION("Iterating over multi-page blob works correctly") + { + nvs_handle handle_3; + const char *name_3 = "namespace3"; + const uint8_t multipage_blob[4096 * 2] = { 0 }; + const int NUMBER_OF_ENTRIES_PER_PAGE = 125; + size_t occupied_entries; + + TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3)); + nvs_set_blob(handle_3, "blob", multipage_blob, sizeof(multipage_blob)); + TEST_ESP_OK(nvs_get_used_entry_count(handle_3, &occupied_entries)); + CHECK(occupied_entries > NUMBER_OF_ENTRIES_PER_PAGE * 2); + + CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_3, NVS_TYPE_BLOB) == 1); + + nvs_close(handle_3); + } + + nvs_close(handle_1); + nvs_close(handle_2); + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); +} + +TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]") +{ + PartitionEmulationFixture f(0, 5); + nvs_iterator_t it; + nvs_handle_t my_handle; + const char* NAMESPACE = "test_ns_4"; + + const uint32_t NVS_FLASH_SECTOR = 0; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 5; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { + f.emu.erase(i); + } + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + // writing string to namespace (a type which spans multiple entries) + TEST_ESP_OK(nvs_open(NAMESPACE, NVS_READWRITE, &my_handle)); + TEST_ESP_OK(nvs_set_str(my_handle, "test-string", "InitString0")); + TEST_ESP_OK(nvs_commit(my_handle)); + nvs_close(my_handle); + + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_I32); + CHECK(it == NULL); + + // re-init to trigger cleaning up of broken items -> a corrupted string will be erased + nvs_flash_deinit(); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + it = nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_STR); + CHECK(it != NULL); + nvs_release_iterator(it); + + // without deinit it affects "nvs api tests" + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } TEST_CASE("wifi test", "[nvs]") { - SpiFlashEmulator emu(10); - emu.randomize(10); - - + PartitionEmulationFixture f(0, 10); + f.emu.randomize(10); + + const uint32_t NVS_FLASH_SECTOR = 5; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); - - nvs_handle misc_handle; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_handle_t misc_handle; TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &misc_handle)); char log[33]; size_t log_size = sizeof(log); TEST_ESP_ERR(nvs_get_str(misc_handle, "log", log, &log_size), ESP_ERR_NVS_NOT_FOUND); strcpy(log, "foobarbazfizzz"); TEST_ESP_OK(nvs_set_str(misc_handle, "log", log)); - - nvs_handle net80211_handle; + + nvs_handle_t net80211_handle; TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &net80211_handle)); - + uint8_t opmode = 2; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "wifi.opmode", &opmode), ESP_ERR_NVS_NOT_FOUND); - + TEST_ESP_OK(nvs_set_u8(net80211_handle, "wifi.opmode", opmode)); - + uint8_t country = 0; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "wifi.country", &opmode), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "wifi.country", opmode)); - + char ssid[36]; size_t size = sizeof(ssid); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.ssid", ssid, &size), ESP_ERR_NVS_NOT_FOUND); strcpy(ssid, "my android AP"); TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.ssid", ssid, size)); - + char mac[6]; size = sizeof(mac); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.mac", mac, &size), ESP_ERR_NVS_NOT_FOUND); memset(mac, 0xab, 6); TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.mac", mac, size)); - + uint8_t authmode = 1; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.authmode", &authmode), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.authmode", authmode)); @@ -616,11 +1002,11 @@ TEST_CASE("wifi test", "[nvs]") TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.pmk", pmk, &size), ESP_ERR_NVS_NOT_FOUND); memset(pmk, 1, size); TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.pmk", pmk, size)); - + uint8_t chan = 1; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.chan", &chan), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.chan", chan)); - + uint8_t autoconn = 1; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "auto.conn", &autoconn), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "auto.conn", autoconn)); @@ -638,43 +1024,43 @@ TEST_CASE("wifi test", "[nvs]") uint8_t phym = 3; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.phym", &phym), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.phym", phym)); - + uint8_t phybw = 2; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "sta.phybw", &phybw), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "sta.phybw", phybw)); - + char apsw[2]; size = sizeof(apsw); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.apsw", apsw, &size), ESP_ERR_NVS_NOT_FOUND); memset(apsw, 0x2, size); TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.apsw", apsw, size)); - + char apinfo[700]; size = sizeof(apinfo); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "sta.apinfo", apinfo, &size), ESP_ERR_NVS_NOT_FOUND); memset(apinfo, 0, size); TEST_ESP_OK(nvs_set_blob(net80211_handle, "sta.apinfo", apinfo, size)); - + size = sizeof(ssid); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.ssid", ssid, &size), ESP_ERR_NVS_NOT_FOUND); strcpy(ssid, "ESP_A2F340"); TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.ssid", ssid, size)); - + size = sizeof(mac); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.mac", mac, &size), ESP_ERR_NVS_NOT_FOUND); memset(mac, 0xac, 6); TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.mac", mac, size)); - + size = sizeof(pswd); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.passwd", pswd, &size), ESP_ERR_NVS_NOT_FOUND); strcpy(pswd, ""); TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.passwd", pswd, size)); - + size = sizeof(pmk); TEST_ESP_ERR(nvs_get_blob(net80211_handle, "ap.pmk", pmk, &size), ESP_ERR_NVS_NOT_FOUND); memset(pmk, 1, size); TEST_ESP_OK(nvs_set_blob(net80211_handle, "ap.pmk", pmk, size)); - + chan = 6; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.chan", &chan), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.chan", chan)); @@ -682,63 +1068,133 @@ TEST_CASE("wifi test", "[nvs]") authmode = 0; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.authmode", &authmode), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.authmode", authmode)); - + uint8_t hidden = 0; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.hidden", &hidden), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.hidden", hidden)); - + uint8_t max_conn = 4; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "ap.max.conn", &max_conn), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "ap.max.conn", max_conn)); - + uint8_t bcn_interval = 2; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "bcn_interval", &bcn_interval), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "bcn_interval", bcn_interval)); - - s_perf << "Time to simulate nvs init with wifi libs: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << "E " << emu.getWriteOps() << "W " << emu.getReadOps() << "R " << emu.getWriteBytes() << "Wb " << emu.getReadBytes() << "Rb)" << std::endl; + s_perf << "Time to simulate nvs init with wifi libs: " << f.emu.getTotalTime() << " us (" << f.emu.getEraseOps() << "E " << f.emu.getWriteOps() << "W " << f.emu.getReadOps() << "R " << f.emu.getWriteBytes() << "Wb " << f.emu.getReadBytes() << "Rb)" << std::endl; + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } +TEST_CASE("writing the identical content does not write or erase", "[nvs]") +{ + PartitionEmulationFixture f(0, 20); + + const uint32_t NVS_FLASH_SECTOR = 5; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 10; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_handle misc_handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &misc_handle)); + + // Test writing a u8 twice, then changing it + nvs_set_u8(misc_handle, "test_u8", 8); + f.emu.clearStats(); + nvs_set_u8(misc_handle, "test_u8", 8); + CHECK(f.emu.getWriteOps() == 0); + CHECK(f.emu.getEraseOps() == 0); + CHECK(f.emu.getReadOps() != 0); + f.emu.clearStats(); + nvs_set_u8(misc_handle, "test_u8", 9); + CHECK(f.emu.getWriteOps() != 0); + CHECK(f.emu.getReadOps() != 0); + + // Test writing a string twice, then changing it + static const char *test[2] = {"Hello world.", "Hello world!"}; + nvs_set_str(misc_handle, "test_str", test[0]); + f.emu.clearStats(); + nvs_set_str(misc_handle, "test_str", test[0]); + CHECK(f.emu.getWriteOps() == 0); + CHECK(f.emu.getEraseOps() == 0); + CHECK(f.emu.getReadOps() != 0); + f.emu.clearStats(); + nvs_set_str(misc_handle, "test_str", test[1]); + CHECK(f.emu.getWriteOps() != 0); + CHECK(f.emu.getReadOps() != 0); + + // Test writing a multi-page blob, then changing it + uint8_t blob[Page::CHUNK_MAX_SIZE * 3] = {0}; + memset(blob, 1, sizeof(blob)); + nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); + f.emu.clearStats(); + nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); + CHECK(f.emu.getWriteOps() == 0); + CHECK(f.emu.getEraseOps() == 0); + CHECK(f.emu.getReadOps() != 0); + blob[sizeof(blob) - 1]++; + f.emu.clearStats(); + nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); + CHECK(f.emu.getWriteOps() != 0); + CHECK(f.emu.getReadOps() != 0); + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); +} TEST_CASE("can init storage from flash with random contents", "[nvs]") { - SpiFlashEmulator emu(10); - emu.randomize(42); - - nvs_handle handle; + PartitionEmulationFixture f(0, 10); + f.emu.randomize(42); + + nvs_handle_t handle; const uint32_t NVS_FLASH_SECTOR = 5; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); - + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle)); - + uint8_t opmode = 2; if (nvs_get_u8(handle, "wifi.opmode", &opmode) != ESP_OK) { TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode)); } + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } -TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][.][long]") +TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long]") { - for (size_t count = 0; count < 10000; ++count) { - SpiFlashEmulator emu(10); - emu.randomize(static_cast(count)); - + const size_t testIters = 3000; + int lastPercent = -1; + for (size_t count = 0; count < testIters; ++count) { + int percentDone = (int) (count * 100 / testIters); + if (percentDone != lastPercent) { + lastPercent = percentDone; + printf("%d%%\n", percentDone); + } + PartitionEmulationFixture f(0, 10); + f.emu.randomize(static_cast(count)); + const uint32_t NVS_FLASH_SECTOR = 6; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); - - nvs_handle handle_1; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_handle_t handle_1; TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); - + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); for (size_t i = 0; i < 500; ++i) { - nvs_handle handle_2; + nvs_handle_t handle_2; TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789 % (i + 1))); TEST_ESP_OK(nvs_set_i32(handle_2, "foo", static_cast(i))); @@ -746,38 +1202,42 @@ TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][.][long]") char str_buf[128]; snprintf(str_buf, sizeof(str_buf), str, i + count * 1024); TEST_ESP_OK(nvs_set_str(handle_2, "key", str_buf)); - + int32_t v1; TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1)); CHECK(0x23456789 % (i + 1) == v1); - + int32_t v2; TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2)); CHECK(static_cast(i) == v2); - + char buf[128]; size_t buf_len = sizeof(buf); - + TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); - + CHECK(0 == strcmp(buf, str_buf)); nvs_close(handle_2); } nvs_close(handle_1); } -} + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); +} extern "C" void nvs_dump(const char *partName); class RandomTest { - - static const size_t nKeys = 9; + + static const size_t nKeys = 11; int32_t v1 = 0, v2 = 0; uint64_t v3 = 0, v4 = 0; static const size_t strBufLen = 1024; + static const size_t smallBlobLen = Page::CHUNK_MAX_SIZE / 3; + static const size_t largeBlobLen = Page::CHUNK_MAX_SIZE * 3; char v5[strBufLen], v6[strBufLen], v7[strBufLen], v8[strBufLen], v9[strBufLen]; + uint8_t v10[smallBlobLen], v11[largeBlobLen]; bool written[nKeys]; - + public: RandomTest() { @@ -785,17 +1245,17 @@ class RandomTest { } template - esp_err_t doRandomThings(nvs_handle handle, TGen gen, size_t& count) { - - const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5"}; - const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ}; - - void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9}; - + esp_err_t doRandomThings(nvs_handle_t handle, TGen gen, size_t& count) { + + const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5", "singlepage", "multipage"}; + const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::BLOB, ItemType::BLOB}; + + void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9, &v10, &v11}; + const size_t nKeys = sizeof(keys) / sizeof(keys[0]); static_assert(nKeys == sizeof(types) / sizeof(types[0]), ""); static_assert(nKeys == sizeof(values) / sizeof(values[0]), ""); - + auto randomRead = [&](size_t index) -> esp_err_t { switch (types[index]) { case ItemType::I32: @@ -814,7 +1274,7 @@ class RandomTest { } break; } - + case ItemType::U64: { uint64_t val; @@ -831,7 +1291,7 @@ class RandomTest { } break; } - + case ItemType::SZ: { char buf[strBufLen]; @@ -849,19 +1309,47 @@ class RandomTest { } break; } - + + case ItemType::BLOB: + { + uint32_t blobBufLen = 0; + if(strncmp(keys[index],"singlepage", sizeof("singlepage")) == 0) { + blobBufLen = smallBlobLen ; + } else { + blobBufLen = largeBlobLen ; + + } + uint8_t buf[blobBufLen]; + memset(buf, 0, blobBufLen); + + size_t len = blobBufLen; + auto err = nvs_get_blob(handle, keys[index], buf, &len); + if (err == ESP_ERR_FLASH_OP_FAIL) { + return err; + } + if (!written[index]) { + REQUIRE(err == ESP_ERR_NVS_NOT_FOUND); + } + else { + REQUIRE(err == ESP_OK); + REQUIRE(memcmp(buf, reinterpret_cast(values[index]), blobBufLen) == 0); + } + break; + } + + default: assert(0); } return ESP_OK; }; - + auto randomWrite = [&](size_t index) -> esp_err_t { switch (types[index]) { case ItemType::I32: { int32_t val = static_cast(gen()); - + auto err = nvs_set_i32(handle, keys[index], val); if (err == ESP_ERR_FLASH_OP_FAIL) { return err; @@ -876,11 +1364,11 @@ class RandomTest { *reinterpret_cast(values[index]) = val; break; } - + case ItemType::U64: { uint64_t val = static_cast(gen()); - + auto err = nvs_set_u64(handle, keys[index], val); if (err == ESP_ERR_FLASH_OP_FAIL) { return err; @@ -895,19 +1383,19 @@ class RandomTest { *reinterpret_cast(values[index]) = val; break; } - + case ItemType::SZ: { char buf[strBufLen]; size_t len = strBufLen; - + size_t strLen = gen() % (strBufLen - 1); std::generate_n(buf, strLen, [&]() -> char { const char c = static_cast(gen() % 127); return (c < 32) ? 32 : c; }); buf[strLen] = 0; - + auto err = nvs_set_str(handle, keys[index], buf); if (err == ESP_ERR_FLASH_OP_FAIL) { return err; @@ -922,23 +1410,53 @@ class RandomTest { strncpy(reinterpret_cast(values[index]), buf, strBufLen); break; } - + + case ItemType::BLOB: + { + uint32_t blobBufLen = 0; + if(strncmp(keys[index],"singlepage", sizeof("singlepage")) == 0) { + blobBufLen = smallBlobLen ; + } else { + blobBufLen = largeBlobLen ; + } + uint8_t buf[blobBufLen]; + memset(buf, 0, blobBufLen); + size_t blobLen = gen() % blobBufLen; + std::generate_n(buf, blobLen, [&]() -> uint8_t { + return static_cast(gen() % 256); + }); + + auto err = nvs_set_blob(handle, keys[index], buf, blobLen); + if (err == ESP_ERR_FLASH_OP_FAIL) { + return err; + } + if (err == ESP_ERR_NVS_REMOVE_FAILED) { + written[index] = true; + memcpy(reinterpret_cast(values[index]), buf, blobBufLen); + return ESP_ERR_FLASH_OP_FAIL; + } + REQUIRE(err == ESP_OK); + written[index] = true; + memcpy(reinterpret_cast(values[index]), buf, blobBufLen); + break; + } + default: assert(0); } return ESP_OK; }; - - + + for (; count != 0; --count) { - size_t index = gen() % nKeys; + size_t index = gen() % (nKeys); switch (gen() % 3) { case 0: // read, 1/3 if (randomRead(index) == ESP_ERR_FLASH_OP_FAIL) { return ESP_ERR_FLASH_OP_FAIL; } break; - + default: // write, 2/3 if (randomWrite(index) == ESP_ERR_FLASH_OP_FAIL) { return ESP_ERR_FLASH_OP_FAIL; @@ -948,8 +1466,20 @@ class RandomTest { } return ESP_OK; } -}; + esp_err_t handleExternalWriteAtIndex(uint8_t index, const void* value, const size_t len ) { + if(index == 9) { /* This is only done for small-page blobs for now*/ + if(len > smallBlobLen) { + return ESP_FAIL; + } + memcpy(v10, value, len); + written[index] = true; + return ESP_OK; + } else { + return ESP_FAIL; + } + } +}; TEST_CASE("monkey test", "[nvs][monkey]") { @@ -957,49 +1487,54 @@ TEST_CASE("monkey test", "[nvs][monkey]") std::mt19937 gen(rd()); uint32_t seed = 3; gen.seed(seed); - - SpiFlashEmulator emu(10); - emu.randomize(seed); - emu.clearStats(); - - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); - - nvs_handle handle; + + PartitionEmulationFixture f(0, 10); + f.emu.randomize(seed); + f.emu.clearStats(); + + const uint32_t NVS_FLASH_SECTOR = 2; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_handle_t handle; TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); RandomTest test; size_t count = 1000; CHECK(test.doRandomThings(handle, gen, count) == ESP_OK); - - s_perf << "Monkey test: nErase=" << emu.getEraseOps() << " nWrite=" << emu.getWriteOps() << std::endl; + + s_perf << "Monkey test: nErase=" << f.emu.getEraseOps() << " nWrite=" << f.emu.getWriteOps() << std::endl; + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } -TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey]") +TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey]") { std::random_device rd; std::mt19937 gen(rd()); uint32_t seed = 3; gen.seed(seed); const size_t iter_count = 2000; - - SpiFlashEmulator emu(10); - - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 2; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; + + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + size_t totalOps = 0; int lastPercent = -1; for (uint32_t errDelay = 0; ; ++errDelay) { INFO(errDelay); - emu.randomize(seed); - emu.clearStats(); - emu.failAfter(errDelay); + f.emu.randomize(seed); + f.emu.clearStats(); + f.emu.failAfter(errDelay); RandomTest test; - + if (totalOps != 0) { int percent = errDelay * 100 / totalOps; if (percent > lastPercent) { @@ -1007,12 +1542,14 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey lastPercent = percent; } } - - nvs_handle handle; + + nvs_handle_t handle; size_t count = iter_count; - if (nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { + if (NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { if (nvs_open("namespace1", NVS_READWRITE, &handle) == ESP_OK) { if(test.doRandomThings(handle, gen, count) != ESP_ERR_FLASH_OP_FAIL) { nvs_close(handle); @@ -1020,9 +1557,12 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey } nvs_close(handle); } + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } - - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); auto res = test.doRandomThings(handle, gen, count); if (res != ESP_OK) { @@ -1030,35 +1570,40 @@ TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey CHECK(0); } nvs_close(handle); - totalOps = emu.getEraseOps() + emu.getWriteBytes() / 4; + totalOps = f.emu.getEraseOps() + f.emu.getWriteBytes() / 4; + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } } - TEST_CASE("test for memory leaks in open/set", "[leaks]") { - SpiFlashEmulator emu(10); + PartitionEmulationFixture f(0, 10); const uint32_t NVS_FLASH_SECTOR = 6; const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); - TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); - + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + for (int i = 0; i < 100000; ++i) { - nvs_handle light_handle = 0; + nvs_handle_t light_handle = 0; char lightbulb[1024] = {12, 13, 14, 15, 16}; TEST_ESP_OK(nvs_open("light", NVS_READWRITE, &light_handle)); TEST_ESP_OK(nvs_set_blob(light_handle, "key", lightbulb, sizeof(lightbulb))); TEST_ESP_OK(nvs_commit(light_handle)); nvs_close(light_handle); } + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } TEST_CASE("duplicate items are removed", "[nvs][dupes]") { - SpiFlashEmulator emu(3); + PartitionEmulationFixture f(0, 3); { // create one item nvs::Page p; - p.load(0); + p.load(&f.part, 0); p.writeItem(1, "opmode", 3); } { @@ -1066,14 +1611,14 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") nvs::Item item(1, ItemType::U8, 1, "opmode"); item.data[0] = 2; item.crc32 = item.calculateCrc32(); - emu.write(3 * 32, reinterpret_cast(&item), sizeof(item)); - emu.write(4 * 32, reinterpret_cast(&item), sizeof(item)); + f.emu.write(3 * 32, reinterpret_cast(&item), sizeof(item)); + f.emu.write(4 * 32, reinterpret_cast(&item), sizeof(item)); uint32_t mask = 0xFFFFFFEA; - emu.write(32, &mask, 4); + f.emu.write(32, &mask, 4); } { // load page and check that second item persists - nvs::Storage s; + nvs::Storage s(&f.part); s.init(0, 3); uint8_t val; ESP_ERROR_CHECK(s.readItem(1, "opmode", val)); @@ -1081,7 +1626,7 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") } { Page p; - p.load(0); + p.load(&f.part, 0); CHECK(p.getErasedEntryCount() == 2); CHECK(p.getUsedEntryCount() == 1); } @@ -1089,30 +1634,30 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") TEST_CASE("recovery after failure to write data", "[nvs]") { - SpiFlashEmulator emu(3); + PartitionEmulationFixture f(0, 3); const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678"; // make flash write fail exactly in Page::writeEntryData - emu.failAfter(17); + f.emu.failAfter(17); { - Storage storage; + Storage storage(&f.part); TEST_ESP_OK(storage.init(0, 3)); - + TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_FLASH_OP_FAIL); - + // check that repeated operations cause an error TEST_ESP_ERR(storage.writeItem(1, ItemType::SZ, "key", str, strlen(str)), ESP_ERR_NVS_INVALID_STATE); - + uint8_t val; TEST_ESP_ERR(storage.readItem(1, ItemType::U8, "key", &val, sizeof(val)), ESP_ERR_NVS_NOT_FOUND); } { // load page and check that data was erased Page p; - p.load(0); + p.load(&f.part, 0); CHECK(p.getErasedEntryCount() == 3); CHECK(p.getUsedEntryCount() == 0); - + // try to write again TEST_ESP_OK(p.writeItem(1, ItemType::SZ, "key", str, strlen(str))); } @@ -1120,25 +1665,25 @@ TEST_CASE("recovery after failure to write data", "[nvs]") TEST_CASE("crc errors in item header are handled", "[nvs]") { - SpiFlashEmulator emu(3); - Storage storage; + PartitionEmulationFixture f(0, 3); + Storage storage(&f.part); // prepare some data TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); TEST_ESP_OK(storage.writeItem(1, "value1", static_cast(1))); TEST_ESP_OK(storage.writeItem(1, "value2", static_cast(2))); - + // corrupt item header uint32_t val = 0; - emu.write(32 * 3, &val, 4); - + f.emu.write(32 * 3, &val, 4); + // check that storage can recover TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.readItem(1, "value2", val)); CHECK(val == 2); // check that the corrupted item is no longer present TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "value1", val)); - + // add more items to make the page full for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) { char item_name[Item::MAX_KEY_LENGTH + 1]; @@ -1148,8 +1693,8 @@ TEST_CASE("crc errors in item header are handled", "[nvs]") // corrupt another item on the full page val = 0; - emu.write(32 * 4, &val, 4); - + f.emu.write(32 * 4, &val, 4); + // check that storage can recover TEST_ESP_OK(storage.init(0, 3)); // check that the corrupted item is no longer present @@ -1158,13 +1703,13 @@ TEST_CASE("crc errors in item header are handled", "[nvs]") TEST_CASE("crc error in variable length item is handled", "[nvs]") { - SpiFlashEmulator emu(3); + PartitionEmulationFixture f(0, 3); const uint64_t before_val = 0xbef04e; const uint64_t after_val = 0xaf7e4; // write some data { Page p; - p.load(0); + p.load(&f.part, 0); TEST_ESP_OK(p.writeItem(0, "before", before_val)); const char* str = "foobar"; TEST_ESP_OK(p.writeItem(0, ItemType::SZ, "key", str, strlen(str))); @@ -1172,13 +1717,13 @@ TEST_CASE("crc error in variable length item is handled", "[nvs]") } // corrupt some data uint32_t w; - CHECK(emu.read(&w, 32 * 3 + 8, sizeof(w))); + CHECK(f.emu.read(&w, 32 * 3 + 8, sizeof(w))); w &= 0xf000000f; - CHECK(emu.write(32 * 3 + 8, &w, sizeof(w))); + CHECK(f.emu.write(32 * 3 + 8, &w, sizeof(w))); // load and check { Page p; - p.load(0); + p.load(&f.part, 0); CHECK(p.getUsedEntryCount() == 2); CHECK(p.getErasedEntryCount() == 2); @@ -1194,16 +1739,16 @@ TEST_CASE("crc error in variable length item is handled", "[nvs]") TEST_CASE("read/write failure (TW8406)", "[nvs]") { - SpiFlashEmulator emu(3); - nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3); + PartitionEmulationFixture f(0, 3); + NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3); for (int attempts = 0; attempts < 3; ++attempts) { int i = 0; - nvs_handle light_handle = 0; + nvs_handle_t light_handle = 0; char key[15] = {0}; char data[76] = {12, 13, 14, 15, 16}; uint8_t number = 20; size_t data_len = sizeof(data); - + ESP_ERROR_CHECK(nvs_open("LIGHT", NVS_READWRITE, &light_handle)); ESP_ERROR_CHECK(nvs_set_u8(light_handle, "RecordNum", number)); for (i = 0; i < number; ++i) { @@ -1211,7 +1756,7 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]") ESP_ERROR_CHECK(nvs_set_blob(light_handle, key, data, sizeof(data))); } nvs_commit(light_handle); - + uint8_t get_number = 0; ESP_ERROR_CHECK(nvs_get_u8(light_handle, "RecordNum", &get_number)); REQUIRE(number == get_number); @@ -1222,38 +1767,44 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]") } nvs_close(light_handle); } + + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); } TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]") { - const size_t blob_size = Page::BLOB_MAX_SIZE; + const size_t blob_size = Page::CHUNK_MAX_SIZE; uint8_t blob[blob_size] = {0}; - SpiFlashEmulator emu(5); - TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 5) ); - nvs_handle handle; + PartitionEmulationFixture f(0, 8); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5) ); + nvs_handle_t handle; TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); // Fill first page TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) ); // Fill second page TEST_ESP_OK( nvs_set_blob(handle, "2a", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "2b", blob, blob_size) ); // Fill third page TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, blob_size) ); - TEST_ESP_OK( nvs_set_blob(handle, "3b", blob, blob_size) ); TEST_ESP_OK( nvs_commit(handle) ); nvs_close(handle); + TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); // first two pages are now full, third one is writable, last two are empty // init should fail - TEST_ESP_ERR( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3), ESP_ERR_NVS_NO_FREE_PAGES ); + TEST_ESP_ERR( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3), + ESP_ERR_NVS_NO_FREE_PAGES ); + + // in case this test fails, to not affect other tests + nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); } TEST_CASE("multiple partitions access check", "[nvs]") { SpiFlashEmulator emu(10); - TEST_ESP_OK( nvs_flash_init_custom("nvs1", 0, 5) ); - TEST_ESP_OK( nvs_flash_init_custom("nvs2", 5, 5) ); - nvs_handle handle1, handle2; + PartitionEmulation p0(&emu, 0 * SPI_FLASH_SEC_SIZE, 5 * SPI_FLASH_SEC_SIZE, "nvs1"); + PartitionEmulation p1(&emu, 5 * SPI_FLASH_SEC_SIZE, 5 * SPI_FLASH_SEC_SIZE, "nvs2"); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&p0, 0, 5) ); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&p1, 5, 5) ); + nvs_handle_t handle1, handle2; TEST_ESP_OK( nvs_open_from_partition("nvs1", "test", NVS_READWRITE, &handle1) ); TEST_ESP_OK( nvs_open_from_partition("nvs2", "test", NVS_READWRITE, &handle2) ); TEST_ESP_OK( nvs_set_i32(handle1, "foo", 0xdeadbeef)); @@ -1263,15 +1814,18 @@ TEST_CASE("multiple partitions access check", "[nvs]") TEST_ESP_OK( nvs_get_i32(handle2, "foo", &v2)); CHECK(v1 == 0xdeadbeef); CHECK(v2 == 0xcafebabe); + + TEST_ESP_OK(nvs_flash_deinit_partition(p0.get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(p1.get_partition_name())); } TEST_CASE("nvs page selection takes into account free entries also not just erased entries", "[nvs]") { - const size_t blob_size = Page::BLOB_MAX_SIZE; + const size_t blob_size = Page::CHUNK_MAX_SIZE/2; uint8_t blob[blob_size] = {0}; - SpiFlashEmulator emu(3); - TEST_ESP_OK( nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, 0, 3) ); - nvs_handle handle; + PartitionEmulationFixture f(0, 3); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3) ); + nvs_handle_t handle; TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); // Fill first page TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size/3) ); @@ -1284,8 +1838,1689 @@ TEST_CASE("nvs page selection takes into account free entries also not just eras TEST_ESP_OK( nvs_set_blob(handle, "3a", blob, 4) ); TEST_ESP_OK( nvs_commit(handle) ); nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("calculate used and free space", "[nvs]") +{ + PartitionEmulationFixture f(0, 6); + nvs_flash_deinit(); + TEST_ESP_ERR(nvs_get_stats(NULL, NULL), ESP_ERR_INVALID_ARG); + nvs_stats_t stat1; + nvs_stats_t stat2; + TEST_ESP_ERR(nvs_get_stats(NULL, &stat1), ESP_ERR_NVS_NOT_INITIALIZED); + CHECK(stat1.free_entries == 0); + CHECK(stat1.namespace_count == 0); + CHECK(stat1.total_entries == 0); + CHECK(stat1.used_entries == 0); + + nvs_handle_t handle = 0; + size_t h_count_entries; + TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE); + CHECK(h_count_entries == 0); + + // init nvs + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 6)); + + TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE); + CHECK(h_count_entries == 0); + + Page p; + // after erase. empty partition + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + CHECK(stat1.free_entries != 0); + CHECK(stat1.namespace_count == 0); + CHECK(stat1.total_entries == 6 * p.ENTRY_COUNT); + CHECK(stat1.used_entries == 0); + + // create namespace test_k1 + nvs_handle_t handle_1; + TEST_ESP_OK(nvs_open("test_k1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + CHECK(stat2.free_entries + 1 == stat1.free_entries); + CHECK(stat2.namespace_count == 1); + CHECK(stat2.total_entries == stat1.total_entries); + CHECK(stat2.used_entries == 1); + + // create pair key-value com + TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x12345678)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + CHECK(stat1.free_entries + 1 == stat2.free_entries); + CHECK(stat1.namespace_count == 1); + CHECK(stat1.total_entries == stat2.total_entries); + CHECK(stat1.used_entries == 2); + + // change value in com + TEST_ESP_OK(nvs_set_i32(handle_1, "com", 0x01234567)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + CHECK(stat2.free_entries == stat1.free_entries); + CHECK(stat2.namespace_count == 1); + CHECK(stat2.total_entries != 0); + CHECK(stat2.used_entries == 2); + + // create pair key-value ru + TEST_ESP_OK(nvs_set_i32(handle_1, "ru", 0x00FF00FF)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + CHECK(stat1.free_entries + 1 == stat2.free_entries); + CHECK(stat1.namespace_count == 1); + CHECK(stat1.total_entries != 0); + CHECK(stat1.used_entries == 3); + + // amount valid pair in namespace 1 + size_t h1_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_1, &h1_count_entries)); + CHECK(h1_count_entries == 2); + + nvs_handle_t handle_2; + // create namespace test_k2 + TEST_ESP_OK(nvs_open("test_k2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + CHECK(stat2.free_entries + 1 == stat1.free_entries); + CHECK(stat2.namespace_count == 2); + CHECK(stat2.total_entries == stat1.total_entries); + CHECK(stat2.used_entries == 4); + + // create pair key-value + TEST_ESP_OK(nvs_set_i32(handle_2, "su1", 0x00000001)); + TEST_ESP_OK(nvs_set_i32(handle_2, "su2", 0x00000002)); + TEST_ESP_OK(nvs_set_i32(handle_2, "sus", 0x00000003)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + CHECK(stat1.free_entries + 3 == stat2.free_entries); + CHECK(stat1.namespace_count == 2); + CHECK(stat1.total_entries == stat2.total_entries); + CHECK(stat1.used_entries == 7); + + CHECK(stat1.total_entries == (stat1.used_entries + stat1.free_entries)); + + // amount valid pair in namespace 2 + size_t h2_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_2, &h2_count_entries)); + CHECK(h2_count_entries == 3); + + CHECK(stat1.used_entries == (h1_count_entries + h2_count_entries + stat1.namespace_count)); + + nvs_close(handle_1); + nvs_close(handle_2); + + size_t temp = h2_count_entries; + TEST_ESP_ERR(nvs_get_used_entry_count(handle_1, &h2_count_entries), ESP_ERR_NVS_INVALID_HANDLE); + CHECK(h2_count_entries == 0); + h2_count_entries = temp; + TEST_ESP_ERR(nvs_get_used_entry_count(handle_1, NULL), ESP_ERR_INVALID_ARG); + + nvs_handle_t handle_3; + // create namespace test_k3 + TEST_ESP_OK(nvs_open("test_k3", NVS_READWRITE, &handle_3)); + TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); + CHECK(stat2.free_entries + 1 == stat1.free_entries); + CHECK(stat2.namespace_count == 3); + CHECK(stat2.total_entries == stat1.total_entries); + CHECK(stat2.used_entries == 8); + + // create pair blobs + uint32_t blob[12]; + TEST_ESP_OK(nvs_set_blob(handle_3, "bl1", &blob, sizeof(blob))); + TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); + CHECK(stat1.free_entries + 4 == stat2.free_entries); + CHECK(stat1.namespace_count == 3); + CHECK(stat1.total_entries == stat2.total_entries); + CHECK(stat1.used_entries == 12); + + // amount valid pair in namespace 2 + size_t h3_count_entries; + TEST_ESP_OK(nvs_get_used_entry_count(handle_3, &h3_count_entries)); + CHECK(h3_count_entries == 4); + + CHECK(stat1.used_entries == (h1_count_entries + h2_count_entries + h3_count_entries + stat1.namespace_count)); + + nvs_close(handle_3); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +// TODO: leaks memory +TEST_CASE("Recovery from power-off when the entry being erased is not on active page", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE/2 ; + size_t read_size = blob_size; + uint8_t blob[blob_size] = {0x11}; + PartitionEmulationFixture f(0, 3); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3) ); + nvs_handle_t handle; + TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); + + f.emu.clearStats(); + f.emu.failAfter(Page::CHUNK_MAX_SIZE/4 + 75); + TEST_ESP_OK( nvs_set_blob(handle, "1a", blob, blob_size) ); + TEST_ESP_OK( nvs_set_blob(handle, "1b", blob, blob_size) ); + + TEST_ESP_ERR( nvs_erase_key(handle, "1a"), ESP_ERR_FLASH_OP_FAIL ); + + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3) ); + + /* Check 1a is erased fully*/ + TEST_ESP_ERR( nvs_get_blob(handle, "1a", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + + /* Check 2b is still accessible*/ + TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size)); + + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +// TODO: leaks memory +TEST_CASE("Recovery from power-off when page is being freed.", "[nvs]") +{ + const size_t blob_size = (Page::ENTRY_COUNT-3) * Page::ENTRY_SIZE; + size_t read_size = blob_size/2; + uint8_t blob[blob_size] = {0}; + PartitionEmulationFixture f(0, 3); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + // Fill first page + TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size/3)); + TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size/3)); + TEST_ESP_OK(nvs_set_blob(handle, "1c", blob, blob_size/4)); + // Fill second page + TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size/2)); + TEST_ESP_OK(nvs_set_blob(handle, "2b", blob, blob_size/2)); + + TEST_ESP_OK(nvs_erase_key(handle, "1c")); + + f.emu.clearStats(); + f.emu.failAfter(6 * Page::ENTRY_COUNT); + TEST_ESP_ERR(nvs_set_blob(handle, "1d", blob, blob_size/4), ESP_ERR_FLASH_OP_FAIL); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + + read_size = blob_size/3; + TEST_ESP_OK( nvs_get_blob(handle, "1a", blob, &read_size)); + TEST_ESP_OK( nvs_get_blob(handle, "1b", blob, &read_size)); + + read_size = blob_size /4; + TEST_ESP_ERR( nvs_get_blob(handle, "1c", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_ERR( nvs_get_blob(handle, "1d", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); + + read_size = blob_size /2; + TEST_ESP_OK( nvs_get_blob(handle, "2a", blob, &read_size)); + TEST_ESP_OK( nvs_get_blob(handle, "2b", blob, &read_size)); + + TEST_ESP_OK(nvs_commit(handle)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Multi-page blobs are supported", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *2; + uint8_t blob[blob_size] = {0}; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5)); + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_commit(handle)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Failures are handled while storing multi-page blobs", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *7; + uint8_t blob[blob_size] = {0}; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5)); + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_ERR(nvs_set_blob(handle, "abc", blob, blob_size), ESP_ERR_NVS_VALUE_TOO_LONG); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE*2)); + TEST_ESP_OK(nvs_commit(handle)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Reading multi-page blobs", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *3; + uint8_t blob[blob_size]; + uint8_t blob_read[blob_size]; + size_t read_size = blob_size; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5)); + nvs_handle_t handle; + memset(blob, 0x11, blob_size); + memset(blob_read, 0xee, blob_size); + TEST_ESP_OK(nvs_open("readTest", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); + CHECK(memcmp(blob, blob_read, blob_size) == 0); + TEST_ESP_OK(nvs_commit(handle)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *2; + uint8_t blob[blob_size] = {0}; + uint8_t blob_read[blob_size] = {0xfe};; + uint8_t blob2[blob_size] = {0x11}; + uint8_t blob3[blob_size] = {0x22}; + uint8_t blob4[blob_size] ={ 0x33}; + size_t read_size = blob_size; + PartitionEmulationFixture f(0, 6); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 6) ); + nvs_handle_t handle; + memset(blob, 0x11, blob_size); + memset(blob2, 0x22, blob_size); + memset(blob3, 0x33, blob_size); + memset(blob4, 0x44, blob_size); + memset(blob_read, 0xff, blob_size); + TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) ); + TEST_ESP_OK( nvs_set_blob(handle, "abc", blob, blob_size) ); + TEST_ESP_OK( nvs_set_blob(handle, "abc", blob2, blob_size) ); + TEST_ESP_OK( nvs_set_blob(handle, "abc", blob3, blob_size) ); + TEST_ESP_OK( nvs_set_blob(handle, "abc", blob4, blob_size) ); + TEST_ESP_OK( nvs_get_blob(handle, "abc", blob_read, &read_size)); + CHECK(memcmp(blob4, blob_read, blob_size) == 0); + TEST_ESP_OK( nvs_commit(handle) ); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Modification from single page blob to multi-page", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *3; + uint8_t blob[blob_size] = {0}; + uint8_t blob_read[blob_size] = {0xff}; + size_t read_size = blob_size; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5) ); + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE/2)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); + CHECK(memcmp(blob, blob_read, blob_size) == 0); + TEST_ESP_OK(nvs_commit(handle) ); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Modification from multi-page to single page", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *3; + uint8_t blob[blob_size] = {0}; + uint8_t blob_read[blob_size] = {0xff}; + size_t read_size = blob_size; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5) ); + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle) ); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, Page::CHUNK_MAX_SIZE/2)); + TEST_ESP_OK(nvs_set_blob(handle, "abc2", blob, blob_size)); + TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); + CHECK(memcmp(blob, blob_read, Page::CHUNK_MAX_SIZE) == 0); + TEST_ESP_OK(nvs_commit(handle) ); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Multi-page blob erased using nvs_erase_key should not be found when probed for just length", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *3; + uint8_t blob[blob_size] = {0}; + size_t read_size = blob_size; + PartitionEmulationFixture f(0, 5); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 5)); + nvs_handle handle; + TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); + TEST_ESP_OK(nvs_erase_key(handle, "abc")); + TEST_ESP_ERR(nvs_get_blob(handle, "abc", NULL, &read_size), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_OK(nvs_commit(handle)); + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + + +TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE *3 ; + uint8_t blob[blob_size] = {0x11}; + uint8_t blob2[blob_size] = {0x22}; + uint8_t blob3[blob_size] = {0x33}; + PartitionEmulationFixture f(0, 5); + Storage storage(&f.part); + + TEST_ESP_OK(storage.init(0, 5)); + + TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, "key", blob, sizeof(blob))); + + + TEST_ESP_OK(storage.init(0, 5)); + /* Check that multi-page item is still available.**/ + TEST_ESP_OK(storage.readItem(1, ItemType::BLOB, "key", blob, sizeof(blob))); + + TEST_ESP_ERR(storage.writeItem(1, ItemType::BLOB, "key2", blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE); + + Page p; + p.load(&f.part, 3); // This is where index will be placed. + p.erase(); + + TEST_ESP_OK(storage.init(0, 5)); + + TEST_ESP_ERR(storage.readItem(1, ItemType::BLOB, "key", blob, sizeof(blob)), ESP_ERR_NVS_NOT_FOUND); + TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, "key3", blob, sizeof(blob))); +} + +TEST_CASE("nvs blob fragmentation test", "[nvs]") +{ + PartitionEmulationFixture f(0, 4); + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 4) ); + const size_t BLOB_SIZE = 3500; + uint8_t *blob = (uint8_t*) malloc(BLOB_SIZE); + CHECK(blob != NULL); + memset(blob, 0xEE, BLOB_SIZE); + const uint32_t magic = 0xff33eaeb; + nvs_handle_t h; + TEST_ESP_OK( nvs_open("blob_tests", NVS_READWRITE, &h) ); + for (int i = 0; i < 128; i++) { + INFO("Iteration " << i << "...\n"); + TEST_ESP_OK( nvs_set_u32(h, "magic", magic) ); + TEST_ESP_OK( nvs_set_blob(h, "blob", blob, BLOB_SIZE) ); + char seq_buf[16]; + sprintf(seq_buf, "seq%d", i); + TEST_ESP_OK( nvs_set_u32(h, seq_buf, i) ); + } + free(blob); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("nvs code handles errors properly when partition is near to full", "[nvs]") +{ + const size_t blob_size = Page::CHUNK_MAX_SIZE * 0.3 ; + uint8_t blob[blob_size] = {0x11}; + PartitionEmulationFixture f(0, 5); + Storage storage(&f.part); + char nvs_key[16] = ""; + + TEST_ESP_OK(storage.init(0, 5)); + + /* Four pages should fit roughly 12 blobs*/ + for(uint8_t count = 1; count <= 12; count++) { + sprintf(nvs_key, "key:%u", count); + TEST_ESP_OK(storage.writeItem(1, ItemType::BLOB, nvs_key, blob, sizeof(blob))); + } + + for(uint8_t count = 13; count <= 20; count++) { + sprintf(nvs_key, "key:%u", count); + TEST_ESP_ERR(storage.writeItem(1, ItemType::BLOB, nvs_key, blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE); + } +} + +TEST_CASE("Check for nvs version incompatibility", "[nvs]") +{ + PartitionEmulationFixture f(0, 3); + + int32_t val1 = 0x12345678; + Page p; + p.load(&f.part, 0); + TEST_ESP_OK(p.setVersion(Page::NVS_VERSION - 1)); + TEST_ESP_OK(p.writeItem(1, ItemType::I32, "foo", &val1, sizeof(val1))); + + TEST_ESP_ERR(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3), + ESP_ERR_NVS_NEW_VERSION_FOUND); + + // if something went wrong, clean up + nvs_flash_deinit_partition(f.part.get_partition_name()); +} + +TEST_CASE("Check that NVS supports old blob format without blob index", "[nvs]") +{ + SpiFlashEmulator emu("../nvs_partition_generator/part_old_blob_format.bin"); + PartitionEmulation part(&emu, 0, 2 * SPI_FLASH_SEC_SIZE, "test"); + nvs_handle_t handle; + + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(&part, 0, 2) ); + TEST_ESP_OK( nvs_open_from_partition("test", "dummyNamespace", NVS_READWRITE, &handle)); + + char buf[64] = {0}; + size_t buflen = 64; + uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; + TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); + CHECK(memcmp(buf, hexdata, buflen) == 0); + + buflen = 64; + uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; + TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + CHECK(memcmp(buf, base64data, buflen) == 0); + + Page p; + p.load(&part, 0); + + /* Check that item is stored in old format without blob index*/ + TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "dummyHex2BinKey")); + + /* Modify the blob so that it is stored in the new format*/ + hexdata[0] = hexdata[1] = hexdata[2] = 0x99; + TEST_ESP_OK(nvs_set_blob(handle, "dummyHex2BinKey", hexdata, sizeof(hexdata))); + + Page p2; + p2.load(&part, 0); + + /* Check the type of the blob. Expect type mismatch since the blob is stored in new format*/ + TEST_ESP_ERR(p2.findItem(1, ItemType::BLOB, "dummyHex2BinKey"), ESP_ERR_NVS_TYPE_MISMATCH); + + /* Check that index is present for the modified blob according to new format*/ + TEST_ESP_OK(p2.findItem(1, ItemType::BLOB_IDX, "dummyHex2BinKey")); + + /* Read the blob in new format and check the contents*/ + buflen = 64; + TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + CHECK(memcmp(buf, base64data, buflen) == 0); + + TEST_ESP_OK(nvs_flash_deinit_partition(part.get_partition_name())); +} + +// TODO: leaks memory +TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") +{ + std::random_device rd; + std::mt19937 gen(rd()); + uint32_t seed = 3; + gen.seed(seed); + + PartitionEmulationFixture f(0, 10); + f.emu.randomize(seed); + f.emu.clearStats(); + + const uint32_t NVS_FLASH_SECTOR = 2; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; + static const size_t smallBlobLen = Page::CHUNK_MAX_SIZE / 3; + + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + RandomTest test; + + for ( uint8_t it = 0; it < 10; it++) { + size_t count = 200; + + /* Erase index and chunks for the blob with "singlepage" key */ + for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + Page p; + p.load(&f.part, num); + p.eraseItem(1, ItemType::BLOB, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY); + p.eraseItem(1, ItemType::BLOB_IDX, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY); + p.eraseItem(1, ItemType::BLOB_DATA, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY); + } + + /* Now write "singlepage" blob in old format*/ + for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + Page p; + p.load(&f.part, num); + if (p.state() == Page::PageState::ACTIVE) { + uint8_t buf[smallBlobLen]; + size_t blobLen = gen() % smallBlobLen; + + if(blobLen > p.getVarDataTailroom()) { + blobLen = p.getVarDataTailroom(); + } + + std::generate_n(buf, blobLen, [&]() -> uint8_t { + return static_cast(gen() % 256); + }); + + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", buf, blobLen, Item::CHUNK_ANY)); + TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage")); + test.handleExternalWriteAtIndex(9, buf, blobLen); // This assumes "singlepage" is always at index 9 + + break; + } + } + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); + /* Initialize again */ + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, + NVS_FLASH_SECTOR, + NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + + /* Perform random things */ + auto res = test.doRandomThings(handle, gen, count); + if (res != ESP_OK) { + nvs_dump(NVS_DEFAULT_PART_NAME); + CHECK(0); + } + + /* Check that only one version is present for "singlepage". Its possible that last iteration did not write + * anything for "singlepage". So either old version or new version should be present.*/ + bool oldVerPresent = false, newVerPresent = false; + + for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + Page p; + p.load(&f.part, num); + if(!oldVerPresent && p.findItem(1, ItemType::BLOB, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY) == ESP_OK) { + oldVerPresent = true; + } + + if(!newVerPresent && p.findItem(1, ItemType::BLOB_IDX, "singlepage", Item::CHUNK_ANY, VerOffset::VER_ANY) == ESP_OK) { + newVerPresent = true; + } + } + CHECK(oldVerPresent != newVerPresent); + } + + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); + s_perf << "Monkey test: nErase=" << f.emu.getEraseOps() << " nWrite=" << f.emu.getWriteOps() << std::endl; +} + +TEST_CASE("Recovery from power-off during modification of blob present in old-format (same page)", "[nvs]") +{ + std::random_device rd; + std::mt19937 gen(rd()); + uint32_t seed = 3; + gen.seed(seed); + + PartitionEmulationFixture f(0, 3); + f.emu.clearStats(); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + + uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; + uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee}; + size_t buflen = sizeof(hexdata); + uint8_t buf[Page::CHUNK_MAX_SIZE]; + + /* Power-off when blob was being written on the same page where its old version in old format + * was present*/ + Page p; + p.load(&f.part, 0); + /* Write blob in old-format*/ + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old))); + + /* Write blob in new format*/ + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0)); + /* All pages are stored. Now store the index.*/ + Item item; + item.blobIndex.dataSize = sizeof(hexdata); + item.blobIndex.chunkCount = 1; + item.blobIndex.chunkStart = VerOffset::VER_0_OFFSET; + + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data))); + + TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage")); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); + /* Initialize again */ + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + + TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen)); + CHECK(memcmp(buf, hexdata, buflen) == 0); + + Page p2; + p2.load(&f.part, 0); + TEST_ESP_ERR(p2.findItem(1, ItemType::BLOB, "singlepage"), ESP_ERR_NVS_TYPE_MISMATCH); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +TEST_CASE("Recovery from power-off during modification of blob present in old-format (different page)", "[nvs]") +{ + std::random_device rd; + std::mt19937 gen(rd()); + uint32_t seed = 3; + gen.seed(seed); + + PartitionEmulationFixture f(0, 3); + f.emu.clearStats(); + + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + + uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; + uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee}; + size_t buflen = sizeof(hexdata); + uint8_t buf[Page::CHUNK_MAX_SIZE]; + + + /* Power-off when blob was being written on the different page where its old version in old format + * was present*/ + Page p; + p.load(&f.part, 0); + /* Write blob in old-format*/ + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old))); + + /* Write blob in new format*/ + TEST_ESP_OK(p.writeItem(1, ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0)); + /* All pages are stored. Now store the index.*/ + Item item; + item.blobIndex.dataSize = sizeof(hexdata); + item.blobIndex.chunkCount = 1; + item.blobIndex.chunkStart = VerOffset::VER_0_OFFSET; + p.markFull(); + Page p2; + p2.load(&f.part, 1); + p2.setSeqNumber(1); + + TEST_ESP_OK(p2.writeItem(1, ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data))); + + TEST_ESP_OK(p.findItem(1, ItemType::BLOB, "singlepage")); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); + /* Initialize again */ + TEST_ESP_OK(NVSPartitionManager::get_instance()->init_custom(&f.part, 0, 3)); + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + + TEST_ESP_OK( nvs_get_blob(handle, "singlepage", buf, &buflen)); + CHECK(memcmp(buf, hexdata, buflen) == 0); + + Page p3; + p3.load(&f.part, 0); + TEST_ESP_ERR(p3.findItem(1, ItemType::BLOB, "singlepage"), ESP_ERR_NVS_NOT_FOUND); + + TEST_ESP_OK(nvs_flash_deinit_partition(f.part.get_partition_name())); +} + +static void check_nvs_part_gen_args(SpiFlashEmulator *spi_flash_emulator, + char const *part_name, + int size, + char const *filename, + bool is_encr, + nvs_sec_cfg_t* xts_cfg) +{ + nvs_handle_t handle; + + esp_partition_t esp_part; + esp_part.encrypted = false; // we're not testing generic flash encryption here, only the legacy NVS encryption + esp_part.address = 0; + esp_part.size = size * SPI_FLASH_SEC_SIZE; + strncpy(esp_part.label, part_name, PART_NAME_MAX_SIZE); + shared_ptr part; + + if (is_encr) { + NVSEncryptedPartition *enc_part = new NVSEncryptedPartition(&esp_part); + TEST_ESP_OK(enc_part->init(xts_cfg)); + part.reset(enc_part); + } else { + part.reset(new PartitionEmulation(spi_flash_emulator, 0, size, part_name)); + } + + TEST_ESP_OK( NVSPartitionManager::get_instance()->init_custom(part.get(), 0, size) ); + + TEST_ESP_OK( nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); + uint8_t u8v; + TEST_ESP_OK( nvs_get_u8(handle, "dummyU8Key", &u8v)); + CHECK(u8v == 127); + int8_t i8v; + TEST_ESP_OK( nvs_get_i8(handle, "dummyI8Key", &i8v)); + CHECK(i8v == -128); + uint16_t u16v; + TEST_ESP_OK( nvs_get_u16(handle, "dummyU16Key", &u16v)); + CHECK(u16v == 32768); + uint32_t u32v; + TEST_ESP_OK( nvs_get_u32(handle, "dummyU32Key", &u32v)); + CHECK(u32v == 4294967295); + int32_t i32v; + TEST_ESP_OK( nvs_get_i32(handle, "dummyI32Key", &i32v)); + CHECK(i32v == -2147483648); + + char buf[64] = {0}; + size_t buflen = 64; + TEST_ESP_OK( nvs_get_str(handle, "dummyStringKey", buf, &buflen)); + CHECK(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0); + + uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; + buflen = 64; + int j; + TEST_ESP_OK( nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); + CHECK(memcmp(buf, hexdata, buflen) == 0); + + uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; + TEST_ESP_OK( nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); + CHECK(memcmp(buf, base64data, buflen) == 0); + + buflen = 64; + uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; + TEST_ESP_OK( nvs_get_blob(handle, "hexFileKey", buf, &buflen)); + CHECK(memcmp(buf, hexfiledata, buflen) == 0); + + buflen = 64; + uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0"; + TEST_ESP_OK( nvs_get_str(handle, "stringFileKey", buf, &buflen)); + CHECK(memcmp(buf, strfiledata, buflen) == 0); + + char bin_data[5200]; + size_t bin_len = sizeof(bin_data); + char binfiledata[5200]; + ifstream file; + file.open(filename); + file.read(binfiledata,5200); + TEST_ESP_OK( nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); + CHECK(memcmp(bin_data, binfiledata, bin_len) == 0); + + file.close(); + + nvs_close(handle); + + TEST_ESP_OK(nvs_flash_deinit_partition(part_name)); +} + + +TEST_CASE("check and read data from partition generated via partition generation utility with multipage blob support disabled", "[nvs_part_gen]") +{ + int status; + int childpid = fork(); + if (childpid == 0) { + exit(execlp("cp", " cp", + "-rf", + "../nvs_partition_generator/testdata", + ".",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) != -1); + + childpid = fork(); + + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "generate", + "../nvs_partition_generator/sample_singlepage_blob.csv", + "partition_single_page.bin", + "0x3000", + "--version", + "1", + "--outdir", + "../nvs_partition_generator",NULL)); + } else { + CHECK(childpid > 0); + int status; + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + } + } + + SpiFlashEmulator emu("../nvs_partition_generator/partition_single_page.bin"); + + check_nvs_part_gen_args(&emu, "test", 3, "../nvs_partition_generator/testdata/sample_singlepage_blob.bin", false, NULL); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } +} + +TEST_CASE("check and read data from partition generated via partition generation utility with multipage blob support enabled", "[nvs_part_gen]") +{ + int status; + int childpid = fork(); + if (childpid == 0) { + exit(execlp("cp", " cp", + "-rf", + "../nvs_partition_generator/testdata", + ".",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "generate", + "../nvs_partition_generator/sample_multipage_blob.csv", + "partition_multipage_blob.bin", + "0x4000", + "--version", + "2", + "--outdir", + "../nvs_partition_generator",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + } + } + + SpiFlashEmulator emu("../nvs_partition_generator/partition_multipage_blob.bin"); + + check_nvs_part_gen_args(&emu, "test", 4, "../nvs_partition_generator/testdata/sample_multipage_blob.bin",false,NULL); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } +} + +TEST_CASE("check and read data from partition generated via manufacturing utility with multipage blob support disabled", "[mfg_gen]") +{ + int childpid = fork(); + int status; + + if (childpid == 0) { + exit(execlp("bash", "bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test && \ + cp -rf ../../../tools/mass_mfg/testdata mfg_testdata && \ + cp -rf ../nvs_partition_generator/testdata . && \ + mkdir -p ../../../tools/mass_mfg/host_test", NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../../../tools/mass_mfg/mfg_gen.py", + "generate", + "../../../tools/mass_mfg/samples/sample_config.csv", + "../../../tools/mass_mfg/samples/sample_values_singlepage_blob.csv", + "Test", + "0x3000", + "--outdir", + "../../../tools/mass_mfg/host_test", + "--version", + "1",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "generate", + "../../../tools/mass_mfg/host_test/csv/Test-1.csv", + "../nvs_partition_generator/Test-1-partition.bin", + "0x3000", + "--version", + "1",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + + } + + } + + SpiFlashEmulator emu1("../../../tools/mass_mfg/host_test/bin/Test-1.bin"); + check_nvs_part_gen_args(&emu1, "test", 3, "mfg_testdata/sample_singlepage_blob.bin", false, NULL); + + SpiFlashEmulator emu2("../nvs_partition_generator/Test-1-partition.bin"); + check_nvs_part_gen_args(&emu2, "test", 3, "testdata/sample_singlepage_blob.bin", false, NULL); + + + childpid = fork(); + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + rm -rf mfg_testdata | \ + rm -rf testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + +} + +TEST_CASE("check and read data from partition generated via manufacturing utility with multipage blob support enabled", "[mfg_gen]") +{ + int childpid = fork(); + int status; + + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + cp -rf ../../../tools/mass_mfg/testdata mfg_testdata | \ + cp -rf ../nvs_partition_generator/testdata . | \ + mkdir -p ../../../tools/mass_mfg/host_test",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../../../tools/mass_mfg/mfg_gen.py", + "generate", + "../../../tools/mass_mfg/samples/sample_config.csv", + "../../../tools/mass_mfg/samples/sample_values_multipage_blob.csv", + "Test", + "0x4000", + "--outdir", + "../../../tools/mass_mfg/host_test", + "--version", + "2",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "generate", + "../../../tools/mass_mfg/host_test/csv/Test-1.csv", + "../nvs_partition_generator/Test-1-partition.bin", + "0x4000", + "--version", + "2",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + + } + + } + + SpiFlashEmulator emu1("../../../tools/mass_mfg/host_test/bin/Test-1.bin"); + check_nvs_part_gen_args(&emu1, "test", 4, "mfg_testdata/sample_multipage_blob.bin", false, NULL); + + SpiFlashEmulator emu2("../nvs_partition_generator/Test-1-partition.bin"); + check_nvs_part_gen_args(&emu2, "test", 4, "testdata/sample_multipage_blob.bin", false, NULL); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + rm -rf mfg_testdata | \ + rm -rf testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + +} + +#if CONFIG_NVS_ENCRYPTION +TEST_CASE("check underlying xts code for 32-byte size sector encryption", "[nvs]") +{ + auto toHex = [](char ch) { + if(ch >= '0' && ch <= '9') + return ch - '0'; + else if(ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + else if(ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + else + return 0; + }; + + auto toHexByte = [toHex](char* c) { + return 16 * toHex(c[0]) + toHex(c[1]); + }; + + auto toHexStream = [toHexByte](char* src, uint8_t* dest) { + uint32_t cnt =0; + char* p = src; + while(*p != '\0' && *(p + 1) != '\0') + { + dest[cnt++] = toHexByte(p); p += 2; + } + }; + + uint8_t eky_hex[2 * NVS_KEY_SIZE]; + uint8_t ptxt_hex[Page::ENTRY_SIZE], ctxt_hex[Page::ENTRY_SIZE], ba_hex[16]; + mbedtls_aes_xts_context ectx[1]; + mbedtls_aes_xts_context dctx[1]; + + char eky[][2 * NVS_KEY_SIZE + 1] = { + "0000000000000000000000000000000000000000000000000000000000000000", + "1111111111111111111111111111111111111111111111111111111111111111" + }; + char tky[][2 * NVS_KEY_SIZE + 1] = { + "0000000000000000000000000000000000000000000000000000000000000000", + "2222222222222222222222222222222222222222222222222222222222222222" + }; + char blk_addr[][2*16 + 1] = { + "00000000000000000000000000000000", + "33333333330000000000000000000000" + }; + + char ptxt[][2 * Page::ENTRY_SIZE + 1] = { + "0000000000000000000000000000000000000000000000000000000000000000", + "4444444444444444444444444444444444444444444444444444444444444444" + }; + char ctxt[][2 * Page::ENTRY_SIZE + 1] = { + "d456b4fc2e620bba6ffbed27b956c9543454dd49ebd8d8ee6f94b65cbe158f73", + "e622334f184bbce129a25b2ac76b3d92abf98e22df5bdd15af471f3db8946a85" + }; + + mbedtls_aes_xts_init(ectx); + mbedtls_aes_xts_init(dctx); + + for(uint8_t cnt = 0; cnt < sizeof(eky)/sizeof(eky[0]); cnt++) { + toHexStream(eky[cnt], eky_hex); + toHexStream(tky[cnt], &eky_hex[NVS_KEY_SIZE]); + toHexStream(ptxt[cnt], ptxt_hex); + toHexStream(ctxt[cnt], ctxt_hex); + toHexStream(blk_addr[cnt], ba_hex); + + CHECK(!mbedtls_aes_xts_setkey_enc(ectx, eky_hex, 2 * NVS_KEY_SIZE * 8)); + CHECK(!mbedtls_aes_xts_setkey_enc(dctx, eky_hex, 2 * NVS_KEY_SIZE * 8)); + + CHECK(!mbedtls_aes_crypt_xts(ectx, MBEDTLS_AES_ENCRYPT, Page::ENTRY_SIZE, ba_hex, ptxt_hex, ptxt_hex)); + + CHECK(!memcmp(ptxt_hex, ctxt_hex, Page::ENTRY_SIZE)); + } +} + +TEST_CASE("test nvs apis with encryption enabled", "[nvs]") +{ + nvs_handle_t handle_1; + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + + nvs_sec_cfg_t xts_cfg; + for(int count = 0; count < NVS_KEY_SIZE; count++) { + xts_cfg.eky[count] = 0x11; + xts_cfg.tky[count] = 0x22; + } + EncryptedPartitionFixture fixture(&xts_cfg, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN); + fixture.emu.randomize(100); + fixture.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + for (uint16_t i = NVS_FLASH_SECTOR; i + init_custom(&fixture.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN)); + + TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); + + TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); + TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789)); + + nvs_handle_t handle_2; + TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a)); + const char* str = "value 0123456789abcdef0123456789abcdef"; + TEST_ESP_OK(nvs_set_str(handle_2, "key", str)); + + int32_t v1; + TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1)); + CHECK(0x23456789 == v1); + + int32_t v2; + TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2)); + CHECK(0x3456789a == v2); + + char buf[strlen(str) + 1]; + size_t buf_len = sizeof(buf); + + size_t buf_len_needed; + TEST_ESP_OK(nvs_get_str(handle_2, "key", NULL, &buf_len_needed)); + CHECK(buf_len_needed == buf_len); + + size_t buf_len_short = buf_len - 1; + TEST_ESP_ERR(ESP_ERR_NVS_INVALID_LENGTH, nvs_get_str(handle_2, "key", buf, &buf_len_short)); + CHECK(buf_len_short == buf_len); + + size_t buf_len_long = buf_len + 1; + TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len_long)); + CHECK(buf_len_long == buf_len); + + TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); + + CHECK(0 == strcmp(buf, str)); + nvs_close(handle_1); + nvs_close(handle_2); + TEST_ESP_OK(nvs_flash_deinit()); + +} + +TEST_CASE("test nvs apis for nvs partition generator utility with encryption enabled", "[nvs_part_gen]") +{ + int status; + int childpid = fork(); + if (childpid == 0) { + exit(execlp("cp", " cp", + "-rf", + "../nvs_partition_generator/testdata", + ".",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "encrypt", + "../nvs_partition_generator/sample_multipage_blob.csv", + "partition_encrypted.bin", + "0x4000", + "--inputkey", + "../nvs_partition_generator/testdata/sample_encryption_keys.bin", + "--outdir", + "../nvs_partition_generator",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + } + } + + SpiFlashEmulator emu("../nvs_partition_generator/partition_encrypted.bin"); + + nvs_sec_cfg_t cfg; + for(int count = 0; count < NVS_KEY_SIZE; count++) { + cfg.eky[count] = 0x11; + cfg.tky[count] = 0x22; + } + + check_nvs_part_gen_args(&emu, NVS_DEFAULT_PART_NAME, 4, "../nvs_partition_generator/testdata/sample_multipage_blob.bin", true, &cfg); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + +} + +TEST_CASE("test decrypt functionality for encrypted data", "[nvs_part_gen]") +{ + + //retrieving the temporary test data + int status = system("cp -rf ../nvs_partition_generator/testdata ."); + CHECK(status == 0); + + //encoding data from sample_multipage_blob.csv + status = system("python ../nvs_partition_generator/nvs_partition_gen.py generate ../nvs_partition_generator/sample_multipage_blob.csv partition_encoded.bin 0x5000 --outdir ../nvs_partition_generator"); + CHECK(status == 0); + + //encrypting data from sample_multipage_blob.csv + status = system("python ../nvs_partition_generator/nvs_partition_gen.py encrypt ../nvs_partition_generator/sample_multipage_blob.csv partition_encrypted.bin 0x5000 --inputkey ../nvs_partition_generator/testdata/sample_encryption_keys.bin --outdir ../nvs_partition_generator"); + CHECK(status == 0); + + //decrypting data from partition_encrypted.bin + status = system("python ../nvs_partition_generator/nvs_partition_gen.py decrypt ../nvs_partition_generator/partition_encrypted.bin ../nvs_partition_generator/testdata/sample_encryption_keys.bin ../nvs_partition_generator/partition_decrypted.bin"); + CHECK(status == 0); + + status = system("diff ../nvs_partition_generator/partition_decrypted.bin ../nvs_partition_generator/partition_encoded.bin"); + CHECK(status == 0); + CHECK(WEXITSTATUS(status) == 0); + + + //cleaning up the temporary test data + status = system("rm -rf testdata"); + CHECK(status == 0); + +} + +TEST_CASE("test nvs apis for nvs partition generator utility with encryption enabled using keygen", "[nvs_part_gen]") +{ + int childpid = fork(); + int status; + + if (childpid == 0) { + exit(execlp("cp", " cp", + "-rf", + "../nvs_partition_generator/testdata", + ".",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "../nvs_partition_generator/keys",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "encrypt", + "../nvs_partition_generator/sample_multipage_blob.csv", + "partition_encrypted_using_keygen.bin", + "0x4000", + "--keygen", + "--outdir", + "../nvs_partition_generator",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + } + } + + + DIR *dir; + struct dirent *file; + char *filename; + char *files; + char *file_ext; + + dir = opendir("../nvs_partition_generator/keys"); + while ((file = readdir(dir)) != NULL) + { + filename = file->d_name; + files = strrchr(filename, '.'); + if (files != NULL) + { + file_ext = files+1; + if (strncmp(file_ext,"bin",3) == 0) + { + break; + } + } + } + + std::string encr_file = std::string("../nvs_partition_generator/keys/") + std::string(filename); + SpiFlashEmulator emu("../nvs_partition_generator/partition_encrypted_using_keygen.bin"); + + char buffer[64]; + FILE *fp; + + fp = fopen(encr_file.c_str(),"rb"); + fread(buffer,sizeof(buffer),1,fp); + + fclose(fp); + + nvs_sec_cfg_t cfg; + + for(int count = 0; count < NVS_KEY_SIZE; count++) { + cfg.eky[count] = buffer[count] & 255; + cfg.tky[count] = buffer[count+32] & 255; + } + + check_nvs_part_gen_args(&emu, NVS_DEFAULT_PART_NAME, 4, "../nvs_partition_generator/testdata/sample_multipage_blob.bin", true, &cfg); + } +TEST_CASE("test nvs apis for nvs partition generator utility with encryption enabled using inputkey", "[nvs_part_gen]") +{ + int childpid = fork(); + int status; + + DIR *dir; + struct dirent *file; + char *filename; + char *files; + char *file_ext; + + dir = opendir("../nvs_partition_generator/keys"); + while ((file = readdir(dir)) != NULL) + { + filename = file->d_name; + files = strrchr(filename, '.'); + if (files != NULL) + { + file_ext = files+1; + if (strncmp(file_ext,"bin",3) == 0) + { + break; + } + } + } + + std::string encr_file = std::string("../nvs_partition_generator/keys/") + std::string(filename); + + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "encrypt", + "../nvs_partition_generator/sample_multipage_blob.csv", + "partition_encrypted_using_keyfile.bin", + "0x4000", + "--inputkey", + encr_file.c_str(), + "--outdir", + "../nvs_partition_generator",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + } + + SpiFlashEmulator emu("../nvs_partition_generator/partition_encrypted_using_keyfile.bin"); + + char buffer[64]; + FILE *fp; + + fp = fopen(encr_file.c_str(),"rb"); + fread(buffer,sizeof(buffer),1,fp); + + fclose(fp); + + nvs_sec_cfg_t cfg; + + for(int count = 0; count < NVS_KEY_SIZE; count++) { + cfg.eky[count] = buffer[count] & 255; + cfg.tky[count] = buffer[count+32] & 255; + } + + check_nvs_part_gen_args(&emu, NVS_DEFAULT_PART_NAME, 4, "../nvs_partition_generator/testdata/sample_multipage_blob.bin", true, &cfg); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "../nvs_partition_generator/keys",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + + if (childpid == 0) { + exit(execlp("rm", " rm", + "-rf", + "testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + } + } + +} + +TEST_CASE("check and read data from partition generated via manufacturing utility with encryption enabled using sample inputkey", "[mfg_gen]") +{ + int childpid = fork(); + int status; + + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + cp -rf ../../../tools/mass_mfg/testdata mfg_testdata | \ + cp -rf ../nvs_partition_generator/testdata . | \ + mkdir -p ../../../tools/mass_mfg/host_test",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../../../tools/mass_mfg/mfg_gen.py", + "generate", + "../../../tools/mass_mfg/samples/sample_config.csv", + "../../../tools/mass_mfg/samples/sample_values_multipage_blob.csv", + "Test", + "0x4000", + "--outdir", + "../../../tools/mass_mfg/host_test", + "--version", + "2", + "--inputkey", + "mfg_testdata/sample_encryption_keys.bin",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "encrypt", + "../../../tools/mass_mfg/host_test/csv/Test-1.csv", + "../nvs_partition_generator/Test-1-partition-encrypted.bin", + "0x4000", + "--version", + "2", + "--inputkey", + "testdata/sample_encryption_keys.bin",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + + } + + } + + SpiFlashEmulator emu1("../../../tools/mass_mfg/host_test/bin/Test-1.bin"); + + nvs_sec_cfg_t cfg; + for(int count = 0; count < NVS_KEY_SIZE; count++) { + cfg.eky[count] = 0x11; + cfg.tky[count] = 0x22; + } + + check_nvs_part_gen_args(&emu1, NVS_DEFAULT_PART_NAME, 4, "mfg_testdata/sample_multipage_blob.bin", true, &cfg); + + SpiFlashEmulator emu2("../nvs_partition_generator/Test-1-partition-encrypted.bin"); + + check_nvs_part_gen_args(&emu2, NVS_DEFAULT_PART_NAME, 4, "testdata/sample_multipage_blob.bin", true, &cfg); + + + childpid = fork(); + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + rm -rf mfg_testdata | \ + rm -rf testdata",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + +} + +TEST_CASE("check and read data from partition generated via manufacturing utility with encryption enabled using new generated key", "[mfg_gen]") +{ + int childpid = fork(); + int status; + + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf ../../../tools/mass_mfg/host_test | \ + cp -rf ../../../tools/mass_mfg/testdata mfg_testdata | \ + cp -rf ../nvs_partition_generator/testdata . | \ + mkdir -p ../../../tools/mass_mfg/host_test",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../../../tools/mass_mfg/mfg_gen.py", + "generate-key", + "--outdir", + "../../../tools/mass_mfg/host_test", + "--keyfile", + "encr_keys_host_test.bin",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../../../tools/mass_mfg/mfg_gen.py", + "generate", + "../../../tools/mass_mfg/samples/sample_config.csv", + "../../../tools/mass_mfg/samples/sample_values_multipage_blob.csv", + "Test", + "0x4000", + "--outdir", + "../../../tools/mass_mfg/host_test", + "--version", + "2", + "--inputkey", + "../../../tools/mass_mfg/host_test/keys/encr_keys_host_test.bin",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("python", "python", + "../nvs_partition_generator/nvs_partition_gen.py", + "encrypt", + "../../../tools/mass_mfg/host_test/csv/Test-1.csv", + "../nvs_partition_generator/Test-1-partition-encrypted.bin", + "0x4000", + "--version", + "2", + "--inputkey", + "../../../tools/mass_mfg/host_test/keys/encr_keys_host_test.bin",NULL)); + + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + + } + + } + + } + + + SpiFlashEmulator emu1("../../../tools/mass_mfg/host_test/bin/Test-1.bin"); + + char buffer[64]; + FILE *fp; + + fp = fopen("../../../tools/mass_mfg/host_test/keys/encr_keys_host_test.bin","rb"); + fread(buffer,sizeof(buffer),1,fp); + + fclose(fp); + + nvs_sec_cfg_t cfg; + + for(int count = 0; count < NVS_KEY_SIZE; count++) { + cfg.eky[count] = buffer[count] & 255; + cfg.tky[count] = buffer[count+32] & 255; + } + + check_nvs_part_gen_args(&emu1, NVS_DEFAULT_PART_NAME, 4, "mfg_testdata/sample_multipage_blob.bin", true, &cfg); + + SpiFlashEmulator emu2("../nvs_partition_generator/Test-1-partition-encrypted.bin"); + + check_nvs_part_gen_args(&emu2, NVS_DEFAULT_PART_NAME, 4, "testdata/sample_multipage_blob.bin", true, &cfg); + + childpid = fork(); + if (childpid == 0) { + exit(execlp("bash", " bash", + "-c", + "rm -rf keys | \ + rm -rf mfg_testdata | \ + rm -rf testdata | \ + rm -rf ../../../tools/mass_mfg/host_test",NULL)); + } else { + CHECK(childpid > 0); + waitpid(childpid, &status, 0); + CHECK(WEXITSTATUS(status) == 0); + + } + +} +#endif + +/* Add new tests above */ +/* This test has to be the final one */ + TEST_CASE("dump all performance data", "[nvs]") { std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl; diff --git a/components/nvs_flash/test_nvs_host/test_nvs_cxx_api.cpp b/components/nvs_flash/test_nvs_host/test_nvs_cxx_api.cpp new file mode 100644 index 000000000..da1d006be --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_nvs_cxx_api.cpp @@ -0,0 +1,193 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include +#include +#include "nvs_test_api.h" +#include "nvs_handle_simple.hpp" +#include "nvs_partition_manager.hpp" +#include "spi_flash_emulation.h" + +#include "test_fixtures.hpp" + +#include + +using namespace std; + +TEST_CASE("NVSHandleSimple CXX api open invalid arguments", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()-> + init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); + + handle = nvs::open_nvs_handle_from_partition(nullptr, "ns_1", NVS_READWRITE, &result); + CHECK(result == ESP_ERR_INVALID_ARG); + CHECK(!handle); + + handle = nvs::open_nvs_handle_from_partition("test", nullptr, NVS_READWRITE, &result); + CHECK(result == ESP_ERR_INVALID_ARG); + CHECK(!handle); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("test"); +} + +TEST_CASE("NVSHandleSimple CXX api open partition uninitialized", "[nvs cxx]") +{ + SpiFlashEmulator emu(10); + esp_err_t result; + shared_ptr handle; + + handle = nvs::open_nvs_handle_from_partition("test", "ns_1", NVS_READWRITE, &result); + bool result_expected = result == ESP_ERR_NVS_NOT_INITIALIZED || result == ESP_ERR_NVS_PART_NOT_FOUND; + CHECK(result_expected); + CHECK(!handle); +} + +TEST_CASE("NVSHandleSimple CXX api open successful", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + handle = nvs::open_nvs_handle_from_partition("test", "ns_1", NVS_READWRITE, &result); + CHECK(result == ESP_OK); + CHECK(handle); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); + + handle.reset(); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("test"); +} + +TEST_CASE("NVSHandleSimple CXX api open default part successful", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10); + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + handle = nvs::open_nvs_handle("ns_1", NVS_READWRITE, &result); + CHECK(result == ESP_OK); + CHECK(handle); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); + + handle.reset(); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); +} + +TEST_CASE("NVSHandleSimple CXX api open default part ns NULL", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10); + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + handle = nvs::open_nvs_handle(nullptr, NVS_READWRITE, &result); + CHECK(result == ESP_ERR_INVALID_ARG); + CHECK(!handle); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); +} + +TEST_CASE("NVSHandleSimple CXX api read/write string", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10); + char read_buffer [256]; + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + handle = nvs::open_nvs_handle("test_ns", NVS_READWRITE, &result); + CHECK(result == ESP_OK); + REQUIRE(handle); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); + + CHECK(handle->set_string("test", "test string") == ESP_OK); + CHECK(handle->commit() == ESP_OK); + CHECK(handle->get_string("test", read_buffer, sizeof(read_buffer)) == ESP_OK); + + CHECK(string(read_buffer) == "test string"); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); +} + +TEST_CASE("NVSHandleSimple CXX api read/write blob", "[nvs cxx]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10); + const char blob [6] = {15, 16, 17, 18, 19}; + char read_blob[6] = {0}; + esp_err_t result; + shared_ptr handle; + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + + handle = nvs::open_nvs_handle("test_ns", NVS_READWRITE, &result); + CHECK(result == ESP_OK); + REQUIRE(handle); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); + + CHECK(handle->set_blob("test", blob, sizeof(blob)) == ESP_OK); + CHECK(handle->commit() == ESP_OK); + CHECK(handle->get_blob("test", read_blob, sizeof(read_blob)) == ESP_OK); + + CHECK(vector(blob, blob + sizeof(blob)) == vector(read_blob, read_blob + sizeof(read_blob))); + + nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); +} diff --git a/components/nvs_flash/test_nvs_host/test_nvs_handle.cpp b/components/nvs_flash/test_nvs_host/test_nvs_handle.cpp new file mode 100644 index 000000000..d9f709e7a --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_nvs_handle.cpp @@ -0,0 +1,281 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include +#include +#include "nvs_test_api.h" +#include "nvs_handle_simple.hpp" +#include "nvs_partition_manager.hpp" +#include "spi_flash_emulation.h" + +#include "test_fixtures.hpp" + +#include +#include + +using namespace std; +using namespace nvs; + +TEST_CASE("NVSHandleSimple closes its reference in PartitionManager", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 1); + + delete handle; + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + +} + +TEST_CASE("NVSHandleSimple multiple open and closes with PartitionManager", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + + NVSHandleSimple *handle1; + NVSHandleSimple *handle2; + + REQUIRE(NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle1) == ESP_OK); + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 1); + + REQUIRE(NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle2) == ESP_OK); + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 2); + + delete handle1; + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 1); + + delete handle2; + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + +} + +TEST_CASE("NVSHandleSimple readonly fails", "[partition_mgr]") +{ + PartitionEmulationFixture f(0, 10); + + NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME); + NVSHandleSimple *handle_1; + NVSHandleSimple *handle_2; + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + CHECK(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + + // first, creating namespace... + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle_1) == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 1); + + delete handle_1; + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READONLY, &handle_2) == ESP_OK); + CHECK(handle_2->set_item("key", 47) == ESP_ERR_NVS_READ_ONLY); + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 1); + + delete handle_2; + + CHECK(NVSPartitionManager::get_instance()->open_handles_size() == 0); + // without deinit it affects "nvs api tests" + CHECK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} + +TEST_CASE("NVSHandleSimple set/get char", "[partition_mgr]") +{ + enum class TestEnum : char { + FOO = -1, + BEER, + BAR + }; + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + char test_e = 'a'; + char test_e_read = 'z'; + + CHECK(handle->set_item("key", test_e) == ESP_OK); + + CHECK(handle->get_item("key", test_e_read) == ESP_OK); + CHECK(test_e == test_e_read); + + delete handle; + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} + +TEST_CASE("NVSHandleSimple correctly sets/gets int enum", "[partition_mgr]") +{ + enum class TestEnum : int { + FOO, + BAR + }; + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + TestEnum test_e = TestEnum::BAR; + TestEnum test_e_read = TestEnum::FOO; + + CHECK(handle->set_item("key", test_e) == ESP_OK); + + CHECK(handle->get_item("key", test_e_read) == ESP_OK); + CHECK(test_e == test_e_read); + + delete handle; + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} + +TEST_CASE("NVSHandleSimple correctly sets/gets int enum with negative values", "[partition_mgr]") +{ + enum class TestEnum : int { + FOO = -1, + BEER, + BAR + }; + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + TestEnum test_e = TestEnum::FOO; + TestEnum test_e_read = TestEnum::BEER; + + CHECK(handle->set_item("key", test_e) == ESP_OK); + + CHECK(handle->get_item("key", test_e_read) == ESP_OK); + CHECK(test_e == test_e_read); + + delete handle; + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} + +TEST_CASE("NVSHandleSimple correctly sets/gets uint8_t enum", "[partition_mgr]") +{ + enum class TestEnum : uint8_t { + FOO, + BAR + }; + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + TestEnum test_e = TestEnum::BAR; + TestEnum test_e_read = TestEnum::FOO; + + CHECK(handle->set_item("key", test_e) == ESP_OK); + + CHECK(handle->get_item("key", test_e_read) == ESP_OK); + CHECK(test_e == test_e_read); + + delete handle; + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} + +TEST_CASE("NVSHandleSimple correctly sets/gets char enum", "[partition_mgr]") +{ + enum class TestEnum : char { + FOO = -1, + BEER, + BAR + }; + + PartitionEmulationFixture f(0, 10); + + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + + TestEnum test_e = TestEnum::BAR; + TestEnum test_e_read = TestEnum::FOO; + + CHECK(handle->set_item("key", test_e) == ESP_OK); + + CHECK(handle->get_item("key", test_e_read) == ESP_OK); + CHECK(test_e == test_e_read); + + delete handle; + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); +} diff --git a/components/nvs_flash/test_nvs_host/test_nvs_initialization.cpp b/components/nvs_flash/test_nvs_host/test_nvs_initialization.cpp new file mode 100644 index 000000000..7731d1da5 --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_nvs_initialization.cpp @@ -0,0 +1,46 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include "nvs_partition_manager.hpp" +#include "spi_flash_emulation.h" +#include "esp_partition.h" +#include "nvs.h" +#include + +using namespace nvs; + +TEST_CASE("nvs_flash_init_partition_ptr fails due to nullptr arg", "[nvs_custom_part]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + SpiFlashEmulator emu(10); + + CHECK(nvs_flash_init_partition_ptr(nullptr) == ESP_ERR_INVALID_ARG); +} + +TEST_CASE("nvs_flash_init_partition_ptr inits one partition", "[nvs_custom_part]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + SpiFlashEmulator emu(10); + + esp_partition_t partition = {}; + strcpy(partition.label, "test"); + partition.address = NVS_FLASH_SECTOR * SPI_FLASH_SEC_SIZE; + partition.size = NVS_FLASH_SECTOR_COUNT_MIN * SPI_FLASH_SEC_SIZE; + + CHECK(nvs_flash_init_partition_ptr(&partition) == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); + CHECK(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); +} diff --git a/components/nvs_flash/test_nvs_host/test_nvs_partition.cpp b/components/nvs_flash/test_nvs_host/test_nvs_partition.cpp new file mode 100644 index 000000000..4385b9a6d --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_nvs_partition.cpp @@ -0,0 +1,55 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include +#include +#include "nvs_test_api.h" +#include "nvs_handle_simple.hpp" +#include "nvs_partition.hpp" +#include "spi_flash_emulation.h" + +#include "test_fixtures.hpp" + +#include + +using namespace std; +using namespace nvs; + +TEST_CASE("encrypted partition read size must be item size", "[nvs]") +{ + char foo [32] = { }; + nvs_sec_cfg_t xts_cfg; + for(int count = 0; count < NVS_KEY_SIZE; count++) { + xts_cfg.eky[count] = 0x11; + xts_cfg.tky[count] = 0x22; + } + EncryptedPartitionFixture fix(&xts_cfg); + + CHECK(fix.part.read(0, foo, sizeof (foo) -1) == ESP_ERR_INVALID_SIZE); +} + +TEST_CASE("encrypted partition write size must be mod item size", "[nvs]") +{ + char foo [64] = { }; + nvs_sec_cfg_t xts_cfg; + for(int count = 0; count < NVS_KEY_SIZE; count++) { + xts_cfg.eky[count] = 0x11; + xts_cfg.tky[count] = 0x22; + } + EncryptedPartitionFixture fix(&xts_cfg); + + CHECK(fix.part.write(0, foo, sizeof (foo) -1) == ESP_ERR_INVALID_SIZE); + CHECK(fix.part.write(0, foo, sizeof (foo)) == ESP_OK); + CHECK(fix.part.write(0, foo, sizeof (foo) * 2) == ESP_OK); +} diff --git a/components/nvs_flash/test_nvs_host/test_nvs_storage.cpp b/components/nvs_flash/test_nvs_host/test_nvs_storage.cpp new file mode 100644 index 000000000..a0ace089c --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_nvs_storage.cpp @@ -0,0 +1,60 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include +#include "nvs_test_api.h" +#include "nvs_storage.hpp" +#include "nvs_partition_manager.hpp" +#include "spi_flash_emulation.h" + +#include "test_fixtures.hpp" + +#include + +using namespace std; +using namespace nvs; + +TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs_storage]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + uint8_t blob [] = {0x0, 0x1, 0x2, 0x3}; + uint8_t blob_new [] = {0x3, 0x2, 0x1, 0x0}; + Storage *storage = NVSPartitionManager::get_instance()->lookup_storage_from_name("test"); + uint8_t ns_index; + storage->createOrOpenNamespace("test_ns", true, ns_index); + + CHECK(storage->writeItem(ns_index, ItemType::BLOB, "test_blob", blob, sizeof(blob)) == ESP_OK); + + // changing provokes a blob with version offset 1 (VerOffset::VER_1_OFFSET) + CHECK(storage->writeItem(ns_index, ItemType::BLOB, "test_blob", blob_new, sizeof(blob_new)) == ESP_OK); + + nvs_opaque_iterator_t it; + it.storage = storage; + it.type = NVS_TYPE_ANY; + + // Central check: does the iterator recognize the blob with version 1? + REQUIRE(storage->findEntry(&it, "test_ns")); + + CHECK(string(it.entry_info.namespace_name) == string("test_ns")); + CHECK(string(it.entry_info.key) == string("test_blob")); + CHECK(it.entry_info.type == NVS_TYPE_BLOB); + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); +} diff --git a/components/nvs_flash/test_nvs_host/test_partition_manager.cpp b/components/nvs_flash/test_nvs_host/test_partition_manager.cpp new file mode 100644 index 000000000..b2db3a9cf --- /dev/null +++ b/components/nvs_flash/test_nvs_host/test_partition_manager.cpp @@ -0,0 +1,91 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "catch.hpp" +#include +#include +#include "nvs_test_api.h" +#include "nvs_handle_simple.hpp" +#include "nvs_partition_manager.hpp" +#include "spi_flash_emulation.h" +#include "nvs_test_api.h" + +#include "test_fixtures.hpp" + +using namespace nvs; + +TEST_CASE("Partition manager initializes storage", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(f.part.get_partition_name()) == ESP_OK); +} + +TEST_CASE("Partition manager de-initializes storage", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); + CHECK(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + CHECK(NVSPartitionManager::get_instance()->lookup_storage_from_name("test") == nullptr); +} + +TEST_CASE("Partition manager initializes multiple partitions", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + SpiFlashEmulator emu(10); + PartitionEmulation part_0(&emu, NVS_FLASH_SECTOR * SPI_FLASH_SEC_SIZE, NVS_FLASH_SECTOR_COUNT_MIN * SPI_FLASH_SEC_SIZE, "test1"); + PartitionEmulation part_1(&emu, NVS_FLASH_SECTOR * SPI_FLASH_SEC_SIZE, NVS_FLASH_SECTOR_COUNT_MIN * SPI_FLASH_SEC_SIZE, "test2"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&part_0, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + // TODO: why does this work, actually? same sectors used as above + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&part_1, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + Storage *storage1 = NVSPartitionManager::get_instance()->lookup_storage_from_name("test1"); + REQUIRE(storage1 != nullptr); + Storage *storage2 = NVSPartitionManager::get_instance()->lookup_storage_from_name("test2"); + REQUIRE(storage2 != nullptr); + + CHECK(storage1 != storage2); + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(part_0.get_partition_name()) == ESP_OK); + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition(part_1.get_partition_name()) == ESP_OK); +} + +TEST_CASE("Partition manager invalidates handle on partition de-init", "[partition_mgr]") +{ + const uint32_t NVS_FLASH_SECTOR = 6; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; + PartitionEmulationFixture f(0, 10, "test"); + + REQUIRE(NVSPartitionManager::get_instance()->init_custom(&f.part, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) + == ESP_OK); + + NVSHandleSimple *handle; + REQUIRE(NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle) == ESP_OK); + CHECK(handle->erase_all() == ESP_OK); + + REQUIRE(NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + + CHECK(handle->erase_all() == ESP_ERR_NVS_INVALID_HANDLE); + + delete handle; +} diff --git a/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp b/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp index 0c77aa966..64ea96b17 100644 --- a/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp +++ b/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include "catch.hpp" #include "esp_spi_flash.h" +#include "esp_partition.h" #include "spi_flash_emulation.h" +#include using namespace std; @@ -23,14 +25,21 @@ bool range_empty_n(Tit it_begin, size_t n) return all_of(it_begin, it_begin + n, bind(equal_to(), placeholders::_1, 0xffffffff)); } +struct FlashEmuFixture { + FlashEmuFixture(size_t sectors) : esp_part(), emu(sectors) { } + + esp_partition_t esp_part; + SpiFlashEmulator emu; +}; + TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]") { - SpiFlashEmulator emu(4); + FlashEmuFixture f(4); uint8_t sector[SPI_FLASH_SEC_SIZE]; for (int i = 0; i < 4; ++i) { - CHECK(spi_flash_read(0, sector, sizeof(sector)) == ESP_OK); + CHECK(esp_partition_read(&f.esp_part, 0, sector, sizeof(sector)) == ESP_OK); for (auto v: sector) { CHECK(v == 0xff); } @@ -39,116 +48,138 @@ TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]") TEST_CASE("invalid writes are checked", "[spi_flash_emu]") { - SpiFlashEmulator emu(1); + FlashEmuFixture f(1); uint32_t val = 0; - CHECK(spi_flash_write(0, &val, 4) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 0, &val, 4) == ESP_OK); val = 1; - CHECK(spi_flash_write(0, &val, 4) == ESP_ERR_FLASH_OP_FAIL); + CHECK(esp_partition_write(&f.esp_part, 0, &val, 4) == ESP_ERR_FLASH_OP_FAIL); } TEST_CASE("out of bounds writes fail", "[spi_flash_emu]") { - SpiFlashEmulator emu(4); + FlashEmuFixture f(4); uint32_t vals[8]; std::fill_n(vals, 8, 0); - CHECK(spi_flash_write(0, vals, sizeof(vals)) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 0, &vals, sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals), vals, sizeof(vals)) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 4*4096 - sizeof(vals), &vals, sizeof(vals)) == ESP_OK); - CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, vals, sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); + CHECK(esp_partition_write(&f.esp_part, 4*4096 - sizeof(vals) + 4, &vals, sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL); } - TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]") { - SpiFlashEmulator emu(4); + FlashEmuFixture f(4); uint32_t val1 = 0xab00cd12; - CHECK(spi_flash_write(0, &val1, sizeof(val1)) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 0, &val1, sizeof(val1)) == ESP_OK); uint32_t val2 = 0x5678efab; - CHECK(spi_flash_write(4096 - 4, &val2, sizeof(val2)) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 4096 - 4, &val2, sizeof(val2)) == ESP_OK); + + CHECK(f.emu.words()[0] == val1); + CHECK(range_empty_n(f.emu.words() + 1, 4096 / 4 - 2)); + CHECK(f.emu.words()[4096 / 4 - 1] == val2); + + CHECK(esp_partition_erase_range(&f.esp_part, 0, SPI_FLASH_SEC_SIZE) == ESP_OK); + + CHECK(f.emu.words()[0] == 0xffffffff); + CHECK(range_empty_n(f.emu.words() + 1, 4096 / 4 - 2)); + CHECK(f.emu.words()[4096 / 4 - 1] == 0xffffffff); +} + +TEST_CASE("EMU raw read function works", "[spi_flash_emu]") +{ + FlashEmuFixture f(4); + uint32_t value = 0xdeadbeef; + uint32_t read_value = 0; + CHECK(esp_partition_write(&f.esp_part, 0, &value, sizeof(value)) == ESP_OK); + + CHECK(esp_partition_read_raw(&f.esp_part, 0, &read_value, sizeof(&read_value)) == ESP_OK); + + CHECK(read_value == 0xdeadbeef); +} - CHECK(emu.words()[0] == val1); - CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2)); - CHECK(emu.words()[4096 / 4 - 1] == val2); +TEST_CASE("EMU raw write function works", "[spi_flash_emu]") +{ + FlashEmuFixture f(4); + uint32_t value = 0xdeadbeef; + uint32_t read_value = 0; + CHECK(esp_partition_write_raw(&f.esp_part, 0, &value, sizeof(value)) == ESP_OK); - CHECK(spi_flash_erase_sector(0) == ESP_OK); + CHECK(esp_partition_read(&f.esp_part, 0, &read_value, sizeof(&read_value)) == ESP_OK); - CHECK(emu.words()[0] == 0xffffffff); - CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2)); - CHECK(emu.words()[4096 / 4 - 1] == 0xffffffff); + CHECK(read_value == 0xdeadbeef); } TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_flash_emu]") { - SpiFlashEmulator emu(1); + FlashEmuFixture f(1); uint8_t data[512]; - spi_flash_read(0, data, 4); - CHECK(emu.getTotalTime() == 7); - CHECK(emu.getReadOps() == 1); - CHECK(emu.getReadBytes() == 4); - emu.clearStats(); - spi_flash_read(0, data, 8); - CHECK(emu.getTotalTime() == 5); - CHECK(emu.getReadOps() == 1); - CHECK(emu.getReadBytes() == 8); - emu.clearStats(); - spi_flash_read(0, data, 16); - CHECK(emu.getTotalTime() == 6); - CHECK(emu.getReadOps() == 1); - CHECK(emu.getReadBytes() == 16); - emu.clearStats(); - spi_flash_read(0, data, 128); - CHECK(emu.getTotalTime() == 18); - CHECK(emu.getReadOps() == 1); - CHECK(emu.getReadBytes() == 128); - emu.clearStats(); - spi_flash_read(0, data, 256); - CHECK(emu.getTotalTime() == 32); - emu.clearStats(); - spi_flash_read(0, data, (128+256)/2); - CHECK(emu.getTotalTime() == (18+32)/2); - emu.clearStats(); - - spi_flash_write(0, data, 4); - CHECK(emu.getTotalTime() == 19); - CHECK(emu.getWriteOps() == 1); - CHECK(emu.getWriteBytes() == 4); - emu.clearStats(); - CHECK(emu.getWriteOps() == 0); - CHECK(emu.getWriteBytes() == 0); - spi_flash_write(0, data, 8); - CHECK(emu.getTotalTime() == 23); - emu.clearStats(); - spi_flash_write(0, data, 16); - CHECK(emu.getTotalTime() == 35); - CHECK(emu.getWriteOps() == 1); - CHECK(emu.getWriteBytes() == 16); - emu.clearStats(); - spi_flash_write(0, data, 128); - CHECK(emu.getTotalTime() == 205); - emu.clearStats(); - spi_flash_write(0, data, 256); - CHECK(emu.getTotalTime() == 417); - emu.clearStats(); - spi_flash_write(0, data, (128+256)/2); - CHECK(emu.getTotalTime() == (205+417)/2); - emu.clearStats(); - - spi_flash_erase_sector(0); - CHECK(emu.getEraseOps() == 1); - CHECK(emu.getTotalTime() == 37142); + esp_partition_read(&f.esp_part, 0, data, 4); + CHECK(f.emu.getTotalTime() == 7); + CHECK(f.emu.getReadOps() == 1); + CHECK(f.emu.getReadBytes() == 4); + f.emu.clearStats(); + esp_partition_read(&f.esp_part, 0, data, 8); + CHECK(f.emu.getTotalTime() == 5); + CHECK(f.emu.getReadOps() == 1); + CHECK(f.emu.getReadBytes() == 8); + f.emu.clearStats(); + esp_partition_read(&f.esp_part, 0, data, 16); + CHECK(f.emu.getTotalTime() == 6); + CHECK(f.emu.getReadOps() == 1); + CHECK(f.emu.getReadBytes() == 16); + f.emu.clearStats(); + esp_partition_read(&f.esp_part, 0, data, 128); + CHECK(f.emu.getTotalTime() == 18); + CHECK(f.emu.getReadOps() == 1); + CHECK(f.emu.getReadBytes() == 128); + f.emu.clearStats(); + esp_partition_read(&f.esp_part, 0, data, 256); + CHECK(f.emu.getTotalTime() == 32); + f.emu.clearStats(); + esp_partition_read(&f.esp_part, 0, data, (128+256)/2); + CHECK(f.emu.getTotalTime() == (18+32)/2); + f.emu.clearStats(); + + esp_partition_write(&f.esp_part, 0, data, 4); + CHECK(f.emu.getTotalTime() == 19); + CHECK(f.emu.getWriteOps() == 1); + CHECK(f.emu.getWriteBytes() == 4); + f.emu.clearStats(); + CHECK(f.emu.getWriteOps() == 0); + CHECK(f.emu.getWriteBytes() == 0); + esp_partition_write(&f.esp_part, 0, data, 8); + CHECK(f.emu.getTotalTime() == 23); + f.emu.clearStats(); + esp_partition_write(&f.esp_part, 0, data, 16); + CHECK(f.emu.getTotalTime() == 35); + CHECK(f.emu.getWriteOps() == 1); + CHECK(f.emu.getWriteBytes() == 16); + f.emu.clearStats(); + esp_partition_write(&f.esp_part, 0, data, 128); + CHECK(f.emu.getTotalTime() == 205); + f.emu.clearStats(); + esp_partition_write(&f.esp_part, 0, data, 256); + CHECK(f.emu.getTotalTime() == 417); + f.emu.clearStats(); + esp_partition_write(&f.esp_part, 0, data, (128+256)/2); + CHECK(f.emu.getTotalTime() == (205+417)/2); + f.emu.clearStats(); + + esp_partition_erase_range(&f.esp_part, 0, SPI_FLASH_SEC_SIZE); + CHECK(f.emu.getEraseOps() == 1); + CHECK(f.emu.getTotalTime() == 37142); } TEST_CASE("data is randomized predictably", "[spi_flash_emu]") { SpiFlashEmulator emu1(3); emu1.randomize(0x12345678); - + SpiFlashEmulator emu2(3); emu2.randomize(0x12345678); - + CHECK(std::equal(emu1.bytes(), emu1.bytes() + emu1.size(), emu2.bytes())); } - From 0b857076c8986acf18d1155bd6815b7409641208 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Fri, 23 Jul 2021 17:21:22 +0800 Subject: [PATCH 17/59] feat(nvs_flash): Modify for ESP8266 --- components/esp8266/include/esp_attr.h | 6 ++++++ components/esp8266/source/phy_init.c | 12 ++++++------ components/esp8266/source/system_api.c | 4 ++-- components/lwip/port/esp8266/netif/dhcp_state.c | 1 - components/nvs_flash/Kconfig | 12 ------------ .../host_test/fixtures/test_fixtures.hpp | 4 ++-- components/nvs_flash/mock/int/crc.cpp | 2 +- components/nvs_flash/mock/int/crc.h | 2 +- components/nvs_flash/src/nvs_api.cpp | 12 ++++++------ components/nvs_flash/src/nvs_page.cpp | 4 ++-- components/nvs_flash/src/nvs_partition.cpp | 4 ++-- components/nvs_flash/src/nvs_partition.hpp | 4 ++-- components/nvs_flash/src/nvs_types.cpp | 16 ++++++++-------- components/nvs_flash/test/test_nvs.c | 1 - .../test_nvs_host/spi_flash_emulation.cpp | 4 ++-- .../test_nvs_host/test_spi_flash_emulation.cpp | 4 ++-- 16 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 components/nvs_flash/Kconfig diff --git a/components/esp8266/include/esp_attr.h b/components/esp8266/include/esp_attr.h index f21fb7a54..d5b14d38b 100644 --- a/components/esp8266/include/esp_attr.h +++ b/components/esp8266/include/esp_attr.h @@ -68,4 +68,10 @@ #define _COUNTER_STRINGIFY(COUNTER) #COUNTER +#ifdef IDF_CI_BUILD +#define IDF_DEPRECATED(REASON) __attribute__((deprecated(REASON))) +#else +#define IDF_DEPRECATED(REASON) +#endif + #endif /* __ESP_ATTR_H__ */ diff --git a/components/esp8266/source/phy_init.c b/components/esp8266/source/phy_init.c index 722dec55c..21563f8bb 100644 --- a/components/esp8266/source/phy_init.c +++ b/components/esp8266/source/phy_init.c @@ -204,15 +204,15 @@ static const char* PHY_NAMESPACE = "phy"; static const char* PHY_CAL_DATA_KEY = "cal_data"; static const char* PHY_RX_GAIN_DC_TABLE_KEY = "dc_table"; -static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle, +static esp_err_t load_cal_data_from_nvs_handle(nvs_handle_t handle, esp_phy_calibration_data_t* out_cal_data); -static esp_err_t store_cal_data_to_nvs_handle(nvs_handle handle, +static esp_err_t store_cal_data_to_nvs_handle(nvs_handle_t handle, const esp_phy_calibration_data_t* cal_data); esp_err_t esp_phy_load_cal_data_from_nvs(esp_phy_calibration_data_t* out_cal_data) { - nvs_handle handle; + nvs_handle_t handle; esp_err_t err = nvs_open(PHY_NAMESPACE, NVS_READONLY, &handle); if (err == ESP_ERR_NVS_NOT_INITIALIZED) { @@ -230,7 +230,7 @@ esp_err_t esp_phy_load_cal_data_from_nvs(esp_phy_calibration_data_t* out_cal_dat esp_err_t esp_phy_store_cal_data_to_nvs(const esp_phy_calibration_data_t* cal_data) { - nvs_handle handle; + nvs_handle_t handle; esp_err_t err = nvs_open(PHY_NAMESPACE, NVS_READWRITE, &handle); if (err != ESP_OK) { @@ -243,7 +243,7 @@ esp_err_t esp_phy_store_cal_data_to_nvs(const esp_phy_calibration_data_t* cal_da } } -static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle, +static esp_err_t load_cal_data_from_nvs_handle(nvs_handle_t handle, esp_phy_calibration_data_t* out_cal_data) { esp_err_t err; @@ -278,7 +278,7 @@ static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle, return ESP_OK; } -static esp_err_t store_cal_data_to_nvs_handle(nvs_handle handle, +static esp_err_t store_cal_data_to_nvs_handle(nvs_handle_t handle, const esp_phy_calibration_data_t* cal_data) { esp_err_t err; diff --git a/components/esp8266/source/system_api.c b/components/esp8266/source/system_api.c index 080514e47..1a0c1aad3 100644 --- a/components/esp8266/source/system_api.c +++ b/components/esp8266/source/system_api.c @@ -136,7 +136,7 @@ static const char *BACKUP_MAC_DATA_KEY = "backup_mac_data"; static esp_err_t load_backup_mac_data(uint8_t *mac) { esp_err_t err; - nvs_handle handle; + nvs_handle_t handle; uint32_t efuse[4]; uint8_t efuse_crc = 0; uint8_t calc_crc = 0; @@ -221,7 +221,7 @@ static esp_err_t load_backup_mac_data(uint8_t *mac) static esp_err_t store_backup_mac_data() { esp_err_t err; - nvs_handle handle; + nvs_handle_t handle; uint32_t efuse[4]; efuse[0] = REG_READ(EFUSE_DATA0_REG); efuse[1] = REG_READ(EFUSE_DATA1_REG); diff --git a/components/lwip/port/esp8266/netif/dhcp_state.c b/components/lwip/port/esp8266/netif/dhcp_state.c index 6ae00d700..8e2e3d9f3 100644 --- a/components/lwip/port/esp8266/netif/dhcp_state.c +++ b/components/lwip/port/esp8266/netif/dhcp_state.c @@ -31,7 +31,6 @@ static const char *interface_key[] = {"IF_STA", "IF_AP", "IF_ETH", "IF_TEST"}; _Static_assert(sizeof(interface_key) / sizeof(char*) == TCPIP_ADAPTER_IF_MAX, "Number interface keys differs from number of interfaces"); -typedef nvs_handle nvs_handle_t; bool dhcp_ip_addr_restore(void *netif) { nvs_handle_t nvs; diff --git a/components/nvs_flash/Kconfig b/components/nvs_flash/Kconfig deleted file mode 100644 index 98cf97987..000000000 --- a/components/nvs_flash/Kconfig +++ /dev/null @@ -1,12 +0,0 @@ -menu "NVS" - - config NVS_ENCRYPTION - bool "Enable NVS encryption" - default y - depends on SECURE_FLASH_ENC_ENABLED - help - This option enables encryption for NVS. When enabled, AES-XTS is used to encrypt - the complete NVS data, except the page headers. It requires XTS encryption keys - to be stored in an encrypted partition. This means enabling flash encryption is - a pre-requisite for this feature. -endmenu diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp index 06b12ce17..0baef7d3f 100644 --- a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -52,7 +52,7 @@ class PartitionMock : public nvs::Partition { esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override { - return esp_partition_read_raw(&partition, src_offset, dst, size); + return esp_partition_read(&partition, src_offset, dst, size); } esp_err_t read(size_t src_offset, void* dst, size_t size) override @@ -62,7 +62,7 @@ class PartitionMock : public nvs::Partition { esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override { - return esp_partition_write_raw(&partition, dst_offset, src, size); + return esp_partition_write(&partition, dst_offset, src, size); } esp_err_t write(size_t dst_offset, const void* src, size_t size) override diff --git a/components/nvs_flash/mock/int/crc.cpp b/components/nvs_flash/mock/int/crc.cpp index 5c1b74564..5098a1415 100644 --- a/components/nvs_flash/mock/int/crc.cpp +++ b/components/nvs_flash/mock/int/crc.cpp @@ -52,7 +52,7 @@ static const unsigned int crc32_le_table[256] = { -extern "C" uint32_t esp_rom_crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) +extern "C" uint32_t crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) { unsigned int i; crc = ~crc; diff --git a/components/nvs_flash/mock/int/crc.h b/components/nvs_flash/mock/int/crc.h index f675da327..405295f8b 100644 --- a/components/nvs_flash/mock/int/crc.h +++ b/components/nvs_flash/mock/int/crc.h @@ -24,7 +24,7 @@ extern "C" { * Mock function to replace ESP ROM function used in IDF with a Linux implementation. * Note: the name MUST have the prefix esp_rom_* since tools/ci/check_rom_apis.sh checks and complains otherwise. */ -uint32_t esp_rom_crc32_le(uint32_t crc, const uint8_t* buf, size_t len); +uint32_t crc32_le(uint32_t crc, const uint8_t* buf, size_t len); #ifdef __cplusplus } diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index fe58ccd2c..8d0848c48 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -27,7 +27,7 @@ #include "crc.h" #define ESP_LOGD(...) #else // LINUX_TARGET -#include +#include // Uncomment this line to force output from this module // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG @@ -586,13 +586,13 @@ extern "C" esp_err_t nvs_flash_generate_keys(const esp_partition_t* partition, n * But the read is decrypted through flash encryption engine. This allows unique NVS encryption configuration, * as flash encryption key is randomly generated per device. */ - err = esp_partition_write_raw(partition, 0, cfg->eky, NVS_KEY_SIZE); + err = esp_partition_write(partition, 0, cfg->eky, NVS_KEY_SIZE); if(err != ESP_OK) { return err; } /* Write without encryption, see note above */ - err = esp_partition_write_raw(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); + err = esp_partition_write(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); if(err != ESP_OK) { return err; } @@ -638,17 +638,17 @@ extern "C" esp_err_t nvs_flash_read_security_cfg(const esp_partition_t* partitio return true; }; - auto err = esp_partition_read_raw(partition, 0, eky_raw, NVS_KEY_SIZE); + auto err = esp_partition_read(partition, 0, eky_raw, NVS_KEY_SIZE); if(err != ESP_OK) { return err; } - err = esp_partition_read_raw(partition, NVS_KEY_SIZE, tky_raw, NVS_KEY_SIZE); + err = esp_partition_read(partition, NVS_KEY_SIZE, tky_raw, NVS_KEY_SIZE); if(err != ESP_OK) { return err; } - err = esp_partition_read_raw(partition, 2 * NVS_KEY_SIZE, &crc_raw, 4); + err = esp_partition_read(partition, 2 * NVS_KEY_SIZE, &crc_raw, 4); if(err != ESP_OK) { return err; } diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index 2f72a2090..600759fe8 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -15,7 +15,7 @@ #if defined(LINUX_TARGET) #include "crc.h" #else -#include +#include #endif #include #include @@ -27,7 +27,7 @@ Page::Page() : mPartition(nullptr) { } uint32_t Page::Header::calculateCrc32() { - return esp_rom_crc32_le(0xffffffff, + return crc32_le(0xffffffff, reinterpret_cast(this) + offsetof(Header, mSeqNumber), offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber)); } diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index f1b7d36d2..9ee8d378c 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -33,7 +33,7 @@ const char *NVSPartition::get_partition_name() esp_err_t NVSPartition::read_raw(size_t src_offset, void* dst, size_t size) { - return esp_partition_read_raw(mESPPartition, src_offset, dst, size); + return esp_partition_read(mESPPartition, src_offset, dst, size); } esp_err_t NVSPartition::read(size_t src_offset, void* dst, size_t size) @@ -47,7 +47,7 @@ esp_err_t NVSPartition::read(size_t src_offset, void* dst, size_t size) esp_err_t NVSPartition::write_raw(size_t dst_offset, const void* src, size_t size) { - return esp_partition_write_raw(mESPPartition, dst_offset, src, size); + return esp_partition_write(mESPPartition, dst_offset, src, size); } esp_err_t NVSPartition::write(size_t dst_offset, const void* src, size_t size) diff --git a/components/nvs_flash/src/nvs_partition.hpp b/components/nvs_flash/src/nvs_partition.hpp index 2af3e238e..d5259cdb5 100644 --- a/components/nvs_flash/src/nvs_partition.hpp +++ b/components/nvs_flash/src/nvs_partition.hpp @@ -50,7 +50,7 @@ class NVSPartition : public Partition, public intrusive_list_node const char *get_partition_name() override; /** - * Look into \c esp_partition_read_raw for more details. + * Look into \c esp_partition_read for more details. * * @return * - ESP_OK on success @@ -69,7 +69,7 @@ class NVSPartition : public Partition, public intrusive_list_node esp_err_t read(size_t src_offset, void* dst, size_t size) override; /** - * Look into \c esp_partition_write_raw for more details. + * Look into \c esp_partition_write for more details. * * @return * - ESP_OK on success diff --git a/components/nvs_flash/src/nvs_types.cpp b/components/nvs_flash/src/nvs_types.cpp index 0189dd70b..127e24b88 100644 --- a/components/nvs_flash/src/nvs_types.cpp +++ b/components/nvs_flash/src/nvs_types.cpp @@ -16,7 +16,7 @@ #if defined(LINUX_TARGET) #include "crc.h" #else -#include +#include #endif namespace nvs @@ -25,10 +25,10 @@ uint32_t Item::calculateCrc32() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), + result = crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, crc32) - offsetof(Item, nsIndex)); - result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); - result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data)); + result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = crc32_le(result, p + offsetof(Item, data), sizeof(data)); return result; } @@ -36,17 +36,17 @@ uint32_t Item::calculateCrc32WithoutValue() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), + result = crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, datatype) - offsetof(Item, nsIndex)); - result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); - result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); + result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); return result; } uint32_t Item::calculateCrc32(const uint8_t* data, size_t size) { uint32_t result = 0xffffffff; - result = esp_rom_crc32_le(result, data, size); + result = crc32_le(result, data, size); return result; } diff --git a/components/nvs_flash/test/test_nvs.c b/components/nvs_flash/test/test_nvs.c index b32fa93ba..7e172f5ac 100644 --- a/components/nvs_flash/test/test_nvs.c +++ b/components/nvs_flash/test/test_nvs.c @@ -7,7 +7,6 @@ #include "nvs.h" #include "nvs_flash.h" #include "esp_partition.h" -#include "esp_flash_encrypt.h" #include "esp_log.h" #include #include "esp_system.h" diff --git a/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp b/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp index 802256c1a..e5b9ae071 100644 --- a/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp +++ b/components/nvs_flash/test_nvs_host/spi_flash_emulation.cpp @@ -62,7 +62,7 @@ esp_err_t esp_partition_read(const esp_partition_t* partition, return ESP_OK; } -esp_err_t esp_partition_read_raw(const esp_partition_t* partition, +esp_err_t esp_partition_read(const esp_partition_t* partition, size_t src_offset, void* dst, size_t size) { if (!s_emulator) { @@ -90,7 +90,7 @@ esp_err_t esp_partition_write(const esp_partition_t* partition, return ESP_OK; } -esp_err_t esp_partition_write_raw(const esp_partition_t* partition, +esp_err_t esp_partition_write(const esp_partition_t* partition, size_t dst_offset, const void* src, size_t size) { if (!s_emulator) { diff --git a/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp b/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp index 64ea96b17..353e1212a 100644 --- a/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp +++ b/components/nvs_flash/test_nvs_host/test_spi_flash_emulation.cpp @@ -95,7 +95,7 @@ TEST_CASE("EMU raw read function works", "[spi_flash_emu]") uint32_t read_value = 0; CHECK(esp_partition_write(&f.esp_part, 0, &value, sizeof(value)) == ESP_OK); - CHECK(esp_partition_read_raw(&f.esp_part, 0, &read_value, sizeof(&read_value)) == ESP_OK); + CHECK(esp_partition_read(&f.esp_part, 0, &read_value, sizeof(&read_value)) == ESP_OK); CHECK(read_value == 0xdeadbeef); } @@ -105,7 +105,7 @@ TEST_CASE("EMU raw write function works", "[spi_flash_emu]") FlashEmuFixture f(4); uint32_t value = 0xdeadbeef; uint32_t read_value = 0; - CHECK(esp_partition_write_raw(&f.esp_part, 0, &value, sizeof(value)) == ESP_OK); + CHECK(esp_partition_write(&f.esp_part, 0, &value, sizeof(value)) == ESP_OK); CHECK(esp_partition_read(&f.esp_part, 0, &read_value, sizeof(&read_value)) == ESP_OK); From bd5a4c3f25f53b3428ce931045a68be51ff84e44 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Fri, 23 Jul 2021 14:40:35 +0800 Subject: [PATCH 18/59] fix(lib): fix smartconfig stop error --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libsmartconfig.a | Bin 157626 -> 156402 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index e62ac86d2..cd9a71636 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -4,5 +4,5 @@ gwen: pp: db51cd0 espnow: db51cd0 - smartconfig: 3.0.0/703cbb6c + smartconfig: 3.0.0/283eacca phy: 1163.0 diff --git a/components/esp8266/lib/libsmartconfig.a b/components/esp8266/lib/libsmartconfig.a index 3d434168789d904c1981f8093250319379d3a8fb..519602ecf6d5bc6b2a4574985b6dd36a84d0ce0b 100755 GIT binary patch delta 3830 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 From 2c9b38b2442d2ce0c5dfc3128db949db2b2581a2 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Fri, 23 Jul 2021 14:22:54 +0800 Subject: [PATCH 19/59] fix(wpa_supplicant): fix pmk error --- components/wpa_supplicant/src/ap/ap_config.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/wpa_supplicant/src/ap/ap_config.c b/components/wpa_supplicant/src/ap/ap_config.c index 14f73547e..5fc87cd5d 100644 --- a/components/wpa_supplicant/src/ap/ap_config.c +++ b/components/wpa_supplicant/src/ap/ap_config.c @@ -130,14 +130,12 @@ static int hostapd_derive_psk(struct hostapd_ssid *ssid) wpa_hexdump_ascii_key(MSG_DEBUG, "PSK (ASCII passphrase)", (u8 *) ssid->wpa_passphrase, strlen(ssid->wpa_passphrase)); -#ifdef ESP_SUPPLICANT - memcpy(ssid->wpa_psk->psk, esp_wifi_ap_get_prof_pmk_internal(), PMK_LEN); -#else + /* It's too SLOW */ pbkdf2_sha1(ssid->wpa_passphrase, - ssid->ssid, ssid->ssid_len, + (const char *)ssid->ssid, ssid->ssid_len, 4096, ssid->wpa_psk->psk, PMK_LEN); -#endif + wpa_hexdump_key(MSG_DEBUG, "PSK (from passphrase)", ssid->wpa_psk->psk, PMK_LEN); return 0; From 86f399a609afedbd49c90014f708ac5314621494 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Fri, 30 Jul 2021 17:59:30 +0800 Subject: [PATCH 20/59] feat(nvs_flash): Update nvs_flash from esp-idf MR !14548. --- .../host_test/fixtures/test_fixtures.hpp | 25 ++++-- .../nvs_page_test/main/nvs_page_test.cpp | 86 ++++++++++++++++--- .../nvs_flash/src/nvs_item_hash_list.cpp | 12 +-- .../nvs_flash/src/nvs_item_hash_list.hpp | 2 +- components/nvs_flash/src/nvs_page.cpp | 15 +++- .../nvs_flash/test_nvs_host/test_nvs.cpp | 5 +- 6 files changed, 115 insertions(+), 30 deletions(-) diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp index 0baef7d3f..b0915682f 100644 --- a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -52,7 +52,7 @@ class PartitionMock : public nvs::Partition { esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override { - return esp_partition_read(&partition, src_offset, dst, size); + return esp_partition_read_raw(&partition, src_offset, dst, size); } esp_err_t read(size_t src_offset, void* dst, size_t size) override @@ -62,7 +62,7 @@ class PartitionMock : public nvs::Partition { esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override { - return esp_partition_write(&partition, dst_offset, src, size); + return esp_partition_write_raw(&partition, dst_offset, src, size); } esp_err_t write(size_t dst_offset, const void* src, size_t size) override @@ -122,6 +122,9 @@ struct PartitionMockFixture { const char *partition_name = NVS_DEFAULT_PART_NAME) : part_mock(start_sector * SPI_FLASH_SEC_SIZE, sector_size * SPI_FLASH_SEC_SIZE) { std::fill_n(raw_header, sizeof(raw_header)/sizeof(raw_header[0]), UINT8_MAX); + + // This resets the mocks and prevents meeting accidental expectations from previous tests. + Mockesp_partition_Init(); } ~PartitionMockFixture() { } @@ -151,7 +154,7 @@ struct NVSPageFixture : public PartitionMockFixture { nvs::Page page; }; -struct NVSValidPageFixture : public PartitionMockFixture { +struct NVSValidPageFlashFixture : public PartitionMockFixture { const static uint8_t NS_INDEX = 1; // valid header @@ -164,7 +167,7 @@ struct NVSValidPageFixture : public PartitionMockFixture { uint8_t value_entry [32]; - NVSValidPageFixture(uint32_t start_sector = 0, + NVSValidPageFlashFixture(uint32_t start_sector = 0, uint32_t sector_size = 1, const char *partition_name = NVS_DEFAULT_PART_NAME) : PartitionMockFixture(start_sector, sector_size, partition_name), @@ -173,8 +176,7 @@ struct NVSValidPageFixture : public PartitionMockFixture { ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l', - 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - page() + 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} { std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); raw_entry_table[0] = 0xfa; @@ -202,7 +204,15 @@ struct NVSValidPageFixture : public PartitionMockFixture { // read normal entry second time during duplicated entry check esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + } +}; +struct NVSValidPageFixture : public NVSValidPageFlashFixture { + NVSValidPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : NVSValidPageFlashFixture(start_sector, sector_size, partition_name), page() + { if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); } @@ -392,9 +402,6 @@ struct NVSFullPageFixture : public PartitionMockFixture { 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, page() { - std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); - raw_entry_table[0] = 0xfa; - // entry_table with all elements deleted except the namespace entry written and the last entry free std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); raw_entry_table[0] = 0x0a; diff --git a/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp index d3899be78..e5ca9ed08 100644 --- a/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp +++ b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp @@ -1,11 +1,16 @@ -/* Hello World Example - - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. #include #include "unity.h" #include "test_fixtures.hpp" @@ -25,6 +30,8 @@ void test_Page_load_reading_header_fails() TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, page.load(&mock, 0)); + + Mockesp_partition_Verify(); } void test_Page_load_reading_data_fails() @@ -39,6 +46,8 @@ void test_Page_load_reading_data_fails() TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); TEST_ASSERT_EQUAL(ESP_FAIL, page.load(&mock, 0)); + + Mockesp_partition_Verify(); } void test_Page_load__uninitialized_page_has_0xfe() @@ -57,6 +66,8 @@ void test_Page_load__uninitialized_page_has_0xfe() TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); + + Mockesp_partition_Verify(); } void test_Page_load__initialized_corrupt_header() @@ -74,6 +85,60 @@ void test_Page_load__initialized_corrupt_header() TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); + + Mockesp_partition_Verify(); +} + +void test_Page_load__corrupt_entry_table() +{ + PartitionMockFixture fix; + + // valid header + uint8_t raw_header_valid [32] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32] = {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + uint8_t raw_header[4] = {0xff, 0xff, 0xff, 0xff}; + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0xfa; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // read next free entry's header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // we expect a raw word write from the partition in order to change the entry bits to erased (0) + esp_partition_write_raw_ExpectAndReturn(&fix.part_mock.partition, 32, nullptr, 4, ESP_OK); + esp_partition_write_raw_IgnoreArg_src(); + + // corrupt entry table as well as crc of corresponding item + raw_entry_table[0] = 0xf6; + + Page page; + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, page.state()); + TEST_ASSERT_EQUAL(1, page.getUsedEntryCount()); + + Mockesp_partition_Verify(); } void test_Page_load_success() @@ -881,6 +946,7 @@ int main(int argc, char **argv) RUN_TEST(test_Page_load_reading_data_fails); RUN_TEST(test_Page_load__uninitialized_page_has_0xfe); RUN_TEST(test_Page_load__initialized_corrupt_header); + RUN_TEST(test_Page_load__corrupt_entry_table); RUN_TEST(test_Page_load_success); RUN_TEST(test_Page_load_full_page); RUN_TEST(test_Page_load__seq_number_0); @@ -929,6 +995,6 @@ int main(int argc, char **argv) RUN_TEST(test_Page_calcEntries__active_wo_blob); RUN_TEST(test_Page_calcEntries__active_with_blob); RUN_TEST(test_Page_calcEntries__invalid); - UNITY_END(); - return 0; + int failures = UNITY_END(); + return failures; } diff --git a/components/nvs_flash/src/nvs_item_hash_list.cpp b/components/nvs_flash/src/nvs_item_hash_list.cpp index 7e1c1241a..21bf8b315 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.cpp +++ b/components/nvs_flash/src/nvs_item_hash_list.cpp @@ -65,7 +65,7 @@ esp_err_t HashList::insert(const Item& item, size_t index) return ESP_OK; } -void HashList::erase(size_t index, bool itemShouldExist) +bool HashList::erase(size_t index) { for (auto it = mBlockList.begin(); it != mBlockList.end();) { bool haveEntries = false; @@ -81,7 +81,7 @@ void HashList::erase(size_t index, bool itemShouldExist) } if (haveEntries && foundIndex) { /* item was found, and HashListBlock still has some items */ - return; + return true; } } /* no items left in HashListBlock, can remove */ @@ -95,12 +95,12 @@ void HashList::erase(size_t index, bool itemShouldExist) } if (foundIndex) { /* item was found and empty HashListBlock was removed */ - return; + return true; } } - if (itemShouldExist) { - assert(false && "item should have been present in cache"); - } + + // item hasn't been present in cache"); + return false; } size_t HashList::find(size_t start, const Item& item) diff --git a/components/nvs_flash/src/nvs_item_hash_list.hpp b/components/nvs_flash/src/nvs_item_hash_list.hpp index ca21c92c1..e724c4f02 100644 --- a/components/nvs_flash/src/nvs_item_hash_list.hpp +++ b/components/nvs_flash/src/nvs_item_hash_list.hpp @@ -29,7 +29,7 @@ class HashList ~HashList(); esp_err_t insert(const Item& item, size_t index); - void erase(const size_t index, bool itemShouldExist=true); + bool erase(const size_t index); size_t find(size_t start, const Item& item); void clear(); diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index 600759fe8..0be9960f5 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -393,8 +393,9 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, ui esp_err_t Page::eraseEntryAndSpan(size_t index) { + uint32_t seq_num; + getSeqNumber(seq_num); auto state = mEntryTable.get(index); - assert(state == EntryState::WRITTEN || state == EntryState::EMPTY); size_t span = 1; if (state == EntryState::WRITTEN) { @@ -404,7 +405,7 @@ esp_err_t Page::eraseEntryAndSpan(size_t index) return rc; } if (item.calculateCrc32() != item.crc32) { - mHashList.erase(index, false); + mHashList.erase(index); rc = alterEntryState(index, EntryState::ERASED); --mUsedEntryCount; ++mErasedEntryCount; @@ -601,6 +602,16 @@ esp_err_t Page::mLoadEntryTable() continue; } + if (mEntryTable.get(i) == static_cast(0x1)) { + lastItemIndex = INVALID_ENTRY; + auto err = eraseEntryAndSpan(i); + if (err != ESP_OK) { + mState = PageState::INVALID; + return err; + } + continue; + } + lastItemIndex = i; auto err = readEntry(i, item); diff --git a/components/nvs_flash/test_nvs_host/test_nvs.cpp b/components/nvs_flash/test_nvs_host/test_nvs.cpp index d8467dfe5..b7a2110a2 100644 --- a/components/nvs_flash/test_nvs_host/test_nvs.cpp +++ b/components/nvs_flash/test_nvs_host/test_nvs.cpp @@ -313,7 +313,8 @@ TEST_CASE("HashList is cleaned up as soon as items are erased", "[nvs]") INFO("Added " << count << " items, " << hashlist.getBlockCount() << " blocks"); // Remove them in reverse order for (size_t i = count; i > 0; --i) { - hashlist.erase(i - 1, true); + // Make sure that the element existed before it's erased + CHECK(hashlist.erase(i - 1) == true); } CHECK(hashlist.getBlockCount() == 0); // Add again @@ -326,7 +327,7 @@ TEST_CASE("HashList is cleaned up as soon as items are erased", "[nvs]") INFO("Added " << count << " items, " << hashlist.getBlockCount() << " blocks"); // Remove them in the same order for (size_t i = 0; i < count; ++i) { - hashlist.erase(i, true); + CHECK(hashlist.erase(i) == true); } CHECK(hashlist.getBlockCount() == 0); } From 8a752a110301b4e9bc135d500d2addb6b250c249 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Mon, 30 Aug 2021 16:31:36 +0800 Subject: [PATCH 21/59] fix(lib): do not rewrite nvs with invalid value and fix ap sta disconnect evt error --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 485864 -> 486240 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 537592 -> 538088 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index cd9a71636..7f6962c9a 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: db51cd0 + net80211: 8e675c3 pp: db51cd0 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index e2a7bb764755afe762e0957975fb531863dd9767..fc8e727e5868caaed9d12a02c06d393cdde30249 100755 GIT binary patch delta 10410 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 delta 9875 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 Kmu@#-tokc2CJ%K0 diff --git a/components/esp8266/lib/libnet80211_dbg.a b/components/esp8266/lib/libnet80211_dbg.a index 464a0afb655fa2bed3bee5f52d3eaa3e5d4e5f0e..71b7ddbdd82644c956912e43f849a4af9f66d91b 100755 GIT binary patch delta 14552 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 delta 14103 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 Date: Mon, 8 Nov 2021 15:42:59 +0800 Subject: [PATCH 22/59] Tools: Constrain the cryptography package for avoiding breaking changes The cryptography 35 release causes breaking changes. Some of certificates are rendered invalid and the package cannot be build without rust. See https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst#3500---2021-09-29 for more details. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a43c6d4ae..6a9793304 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ setuptools click>=5.0 pyserial>=3.0 future>=0.15.2 -cryptography>=2.1.4 +cryptography>=2.1.4,<35 pyparsing>=2.0.3,<2.4.0 pyelftools>=0.22 From e1f60ef9b27015d1160a304ba2c1710ada7c21b0 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 22 Dec 2021 11:19:30 +0800 Subject: [PATCH 23/59] fix(pp): do not wait null1 send ok before scan --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libpp.a | Bin 249654 -> 249530 bytes components/esp8266/lib/libpp_dbg.a | Bin 267410 -> 267294 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 7f6962c9a..b9a53615f 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: db51cd0 net80211: 8e675c3 - pp: db51cd0 + pp: 553d1ee espnow: db51cd0 smartconfig: 3.0.0/283eacca diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index ab5e5587ccb7218b59db78d18f87c81eb4d619d2..9dabb4d1de6283499ba7607da709783ca46bfc82 100755 GIT binary patch delta 5351 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 Hy~_UzW0N4} delta 5248 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 G^nU>1>xBdW diff --git a/components/esp8266/lib/libpp_dbg.a b/components/esp8266/lib/libpp_dbg.a index 8f47d81591a85291a39edaa181db9f3c14acb38a..415918003c6ccf8515ed6433237cdf8e97cd9d0f 100755 GIT binary patch delta 5955 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 From a7ac1b6f12679a8136551388103536e8da3b91a7 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Tue, 15 Mar 2022 16:25:29 +0800 Subject: [PATCH 24/59] fix(lib): fix some wifi issues 1. fix crash when recv null rates 2. fix rst error when malloc failed --- components/esp8266/lib/VERSION | 4 ++-- components/esp8266/lib/libnet80211.a | Bin 486240 -> 486144 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 538088 -> 538000 bytes components/esp8266/lib/libpp.a | Bin 249530 -> 249530 bytes components/esp8266/lib/libpp_dbg.a | Bin 267294 -> 267294 bytes 5 files changed, 2 insertions(+), 2 deletions(-) mode change 100755 => 100644 components/esp8266/lib/libnet80211.a mode change 100755 => 100644 components/esp8266/lib/libnet80211_dbg.a mode change 100755 => 100644 components/esp8266/lib/libpp.a mode change 100755 => 100644 components/esp8266/lib/libpp_dbg.a diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index b9a53615f..40df30cab 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: db51cd0 - net80211: 8e675c3 - pp: 553d1ee + net80211: 44c6f29 + pp: 44c6f29 espnow: db51cd0 smartconfig: 3.0.0/283eacca diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a old mode 100755 new mode 100644 index fc8e727e5868caaed9d12a02c06d393cdde30249..66cb78ce1adebebc30bb49c9f0c27f081daf27c3 GIT binary patch delta 11241 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 SioYKfK=d{g2AH)?n*RS?9aYr; delta 11050 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 Vu@tLK9lyJ`U>)Ct`e%wW{vWprs0082 delta 12571 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 diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a old mode 100755 new mode 100644 index 9dabb4d1de6283499ba7607da709783ca46bfc82..21964fba57c6e85fa186d03daee5724a06a2a573 GIT binary patch delta 151 zcmdlrmw(q>{t43T=Elax2Bt{t43T28Jd^rWO_(mAZvt?9E$*b4B1B4UM%2;G8=zT=ij`?HUq{PuIbD uBF7m!Rp7iOuNfKZ;Jh`NOf$3LJfEvfnoMwB&2^@eMvTte-ME-bjsO5Dcr3C2 diff --git a/components/esp8266/lib/libpp_dbg.a b/components/esp8266/lib/libpp_dbg.a old mode 100755 new mode 100644 index 415918003c6ccf8515ed6433237cdf8e97cd9d0f..5aa6b9226d1d4af373ce9303fbe8e7128b566697 GIT binary patch delta 152 zcmbQYLSWtsfeF&==Elax2BxMPmAZvt?9E$*|BAvnJ2ZLUz&P8_h%$ Date: Fri, 8 Apr 2022 14:41:17 +0800 Subject: [PATCH 25/59] chore(ci): Fix CI env issues --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f884330ee..9c007547b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,6 +10,9 @@ variables: # Versioned esp-idf-doc env image to use for all document building jobs ESP_IDF_DOC_ENV_IMAGE: "$CI_DOCKER_REGISTRY/esp-idf-doc-env:v8" + GIT_SUBMODULE_STRATEGY: recursive + ESPCI_TOKEN: $GITLAB_KEY + # before each job, we need to check if this job is filtered by bot stage/job filter .apply_bot_filter: &apply_bot_filter python $APPLY_BOT_FILTER_SCRIPT || exit 0 From 56ffb3d46cd071724314906cf427da4e97df94c0 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 28 Mar 2022 19:09:45 +0800 Subject: [PATCH 26/59] fix(esp8266): Fix compiling error when enable Wi-Fi debug --- components/esp8266/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esp8266/CMakeLists.txt b/components/esp8266/CMakeLists.txt index 26120f438..887f9b6e6 100644 --- a/components/esp8266/CMakeLists.txt +++ b/components/esp8266/CMakeLists.txt @@ -69,7 +69,12 @@ else() target_link_libraries(${COMPONENT_LIB} PUBLIC "-L ${CMAKE_CURRENT_SOURCE_DIR}/lib" "-lstdc++") target_compile_definitions(${COMPONENT_LIB} PUBLIC -DUSING_IBUS_FASTER_GET) if(NOT CONFIG_NO_BLOBS) - set(blobs "gcc" "hal" "core" "net80211" "phy" "rtc" "clk" "pp" "smartconfig" "ssc" "espnow") + set(blobs "gcc" "hal" "phy" "rtc" "clk" "smartconfig" "ssc") + if(CONFIG_ESP8266_WIFI_DEBUG_LOG_ENABLE) + list(APPEND blobs "core_dbg" "net80211_dbg" "pp_dbg" "espnow_dbg") + else() + list(APPEND blobs "core" "net80211" "pp" "espnow") + endif() foreach(blob ${blobs}) add_library(${blob} STATIC IMPORTED) set_property(TARGET ${blob} PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/lib${blob}.a) From 9d1387522c455f22d513bdb44c6a30ec292f635f Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 9 May 2022 19:15:37 +0800 Subject: [PATCH 27/59] feat(bootloader): Disable CPU interrupt in boot --- components/bootloader/subproject/main/bootloader_start.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/bootloader/subproject/main/bootloader_start.c b/components/bootloader/subproject/main/bootloader_start.c index 3ba0addee..d79496db8 100644 --- a/components/bootloader/subproject/main/bootloader_start.c +++ b/components/bootloader/subproject/main/bootloader_start.c @@ -22,6 +22,7 @@ #include "esp_image_format.h" #include "esp_spi_flash.h" #include "esp_log.h" +#include "driver/soc.h" static const char* TAG = "boot"; @@ -30,6 +31,11 @@ static int selected_boot_partition(const bootloader_state_t *bs); void call_start_cpu(void) { + esp_irqflag_t irq; + + irq = soc_save_local_irq(); + ESP_LOGD(TAG, "CPU local irq: 0x%x", irq); + #ifdef CONFIG_BOOTLOADER_FAST_BOOT REG_SET_BIT(DPORT_CTL_REG, DPORT_CTL_DOUBLE_CLK); #endif From 3efd52d2e08a848c3014a0f41fcafe56227ebf93 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Fri, 13 May 2022 19:40:09 +0800 Subject: [PATCH 28/59] feat(bootloader): Boot close IRQ with level 3 --- components/esp8266/include/driver/soc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/esp8266/include/driver/soc.h b/components/esp8266/include/driver/soc.h index ac3e330de..894f64e95 100644 --- a/components/esp8266/include/driver/soc.h +++ b/components/esp8266/include/driver/soc.h @@ -31,7 +31,11 @@ static inline esp_irqflag_t soc_save_local_irq(void) esp_irqflag_t flag; __asm__ __volatile__( +#ifdef BOOTLOADER_BUILD + "rsil %0, 3\n" +#else "rsil %0, 1\n" +#endif : "=a"(flag) : : "memory" From b5301bf124b6efa7818eae4bb2f4bb481fe2013c Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Wed, 10 Aug 2022 16:04:45 +0800 Subject: [PATCH 29/59] feat(bootloader): add xmc spi_flash startup flow to improve reliability --- components/bootloader/Kconfig.projbuild | 9 + .../subproject/main/esp8266.bootloader.rom.ld | 3 +- .../include_priv/bootloader_flash.h | 21 +++ .../bootloader_support/src/bootloader_flash.c | 163 ++++++++++++++++-- .../bootloader_support/src/bootloader_init.c | 7 + 5 files changed, 192 insertions(+), 11 deletions(-) diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 50c7633cf..2e108b512 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -174,6 +174,15 @@ config BOOTLOADER_STORE_OFFSET bootloader of the SDK's bootloader, you can set the option to store SDK's bootloader to other space in the flash instead of "0x0". +config BOOTLOADER_FLASH_XMC_SUPPORT + bool "Enable the support for flash chips of XMC (READ HELP FIRST)" + default y + help + Perform the startup flow recommended by XMC. Please consult XMC for the details of this flow. + XMC chips will be forbidden to be used, when this option is disabled. + + DON'T DISABLE THIS UNLESS YOU KNOW WHAT YOU ARE DOING. + endmenu # Bootloader diff --git a/components/bootloader/subproject/main/esp8266.bootloader.rom.ld b/components/bootloader/subproject/main/esp8266.bootloader.rom.ld index 3ff8f2710..27d35bbee 100644 --- a/components/bootloader/subproject/main/esp8266.bootloader.rom.ld +++ b/components/bootloader/subproject/main/esp8266.bootloader.rom.ld @@ -8,4 +8,5 @@ PROVIDE ( gpio_input_get = 0x40004cf0 ); PROVIDE ( xthal_get_ccount = 0x4000dd38 ); PROVIDE ( uart_div_modify = 0x400039d8 ); -PROVIDE ( ets_io_vprintf = 0x40001f00 ); \ No newline at end of file +PROVIDE ( ets_io_vprintf = 0x40001f00 ); +PROVIDE ( ets_rom_delay_us = 0x40002ecc ); \ No newline at end of file diff --git a/components/bootloader_support/include_priv/bootloader_flash.h b/components/bootloader_support/include_priv/bootloader_flash.h index 763136e03..101a32752 100644 --- a/components/bootloader_support/include_priv/bootloader_flash.h +++ b/components/bootloader_support/include_priv/bootloader_flash.h @@ -22,6 +22,11 @@ #define FLASH_SECTOR_SIZE 0x1000 +#define CMD_RDSFDP 0x5A /* Read the SFDP of the flash */ +#define CMD_RDJEDECID 0x9F /* Read the JEDEC ID of the flash */ + +#define XMC_VENDOR_ID 0x20 + /* Provide a Flash API for bootloader_support code, that can be used from bootloader or app code. @@ -100,4 +105,20 @@ esp_err_t bootloader_flash_write(size_t dest_addr, void *src, size_t size, bool */ esp_err_t bootloader_flash_erase_sector(size_t sector); +/** + * @brief Read the SFDP of the flash + * + * @param sfdp_addr Address of the parameter to read + * @param miso_byte_num Bytes to read + * @return The read SFDP, little endian, 4 bytes at most + */ +uint32_t bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_num); + +/** + * @brief Startup flow recommended by XMC. Call at startup before any erase/write operation. + * + * @return ESP_OK When startup successfully, otherwise ESP_FAIL (indiciating you should reboot before erase/write). + */ +esp_err_t bootloader_flash_xmc_startup(void); + #endif diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index f93226b3a..47edf2126 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -263,9 +263,13 @@ esp_err_t bootloader_flash_erase_sector(size_t sector) #include "esp_err.h" #include "esp_log.h" +#include "esp8266/rom_functions.h" #ifndef BOOTLOADER_BUILD #include "esp_spi_flash.h" +#else +#include "bootloader_flash.h" +#include "priv/esp_spi_flash_raw.h" #endif #ifdef CONFIG_SOC_FULL_ICACHE @@ -274,18 +278,11 @@ esp_err_t bootloader_flash_erase_sector(size_t sector) #define SOC_CACHE_SIZE 0 // 16KB #endif -extern void Cache_Read_Disable(); -extern void Cache_Read_Enable(uint8_t map, uint8_t p, uint8_t v); - -static const char *TAG = "bootloader_flash"; +#define XMC_SUPPORT CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT -typedef enum { SPI_FLASH_RESULT_OK = 0, - SPI_FLASH_RESULT_ERR = 1, - SPI_FLASH_RESULT_TIMEOUT = 2 } SpiFlashOpResult; +#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF) -SpiFlashOpResult SPIRead(uint32_t addr, void *dst, uint32_t size); -SpiFlashOpResult SPIWrite(uint32_t addr, const uint8_t *src, uint32_t size); -SpiFlashOpResult SPIEraseSector(uint32_t sector_num); +static const char *TAG = "bootloader_flash"; static bool mapped; @@ -406,4 +403,150 @@ esp_err_t bootloader_flash_erase_sector(size_t sector) return ESP_OK; } +#ifdef BOOTLOADER_BUILD +uint32_t bootloader_read_flash_id(void) +{ + uint32_t id = spi_flash_get_id_raw(&g_rom_flashchip); + id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00); + return id; +} + +#if XMC_SUPPORT +static bool is_xmc_chip_strict(uint32_t rdid) +{ + uint32_t vendor_id = BYTESHIFT(rdid, 2); + uint32_t mfid = BYTESHIFT(rdid, 1); + uint32_t cpid = BYTESHIFT(rdid, 0); + + if (vendor_id != XMC_VENDOR_ID) { + return false; + } + + bool matched = false; + if (mfid == 0x40) { + if (cpid >= 0x13 && cpid <= 0x20) { + matched = true; + } + } else if (mfid == 0x41) { + if (cpid >= 0x17 && cpid <= 0x20) { + matched = true; + } + } else if (mfid == 0x50) { + if (cpid >= 0x15 && cpid <= 0x16) { + matched = true; + } + } + return matched; +} + +bool bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len) +{ + bool ret; + spi_cmd_t cmd; + + cmd.cmd = command; + cmd.cmd_len = 1; + cmd.addr = NULL; + cmd.addr_len = 0; + cmd.dummy_bits = 0; + cmd.data = NULL; + cmd.data_len = 0; + + ret = spi_user_cmd_raw(&g_rom_flashchip, SPI_TX, &cmd); + if (!ret) { + ESP_LOGE(TAG, "failed to write cmd=%02x", command); + } + + return ret; +} + +uint32_t bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_num) +{ + bool ret; + spi_cmd_t cmd; + uint32_t data = 0; + uint32_t addr = sfdp_addr << 8; + + cmd.cmd = CMD_RDSFDP; + cmd.cmd_len = 1; + cmd.addr = &addr; + cmd.addr_len = 3; + cmd.dummy_bits = 8; + cmd.data = &data; + cmd.data_len = miso_byte_num; + + ret = spi_user_cmd_raw(&g_rom_flashchip, SPI_RX, &cmd); + if (!ret) { + ESP_LOGE(TAG, "failed to read sfdp"); + } + + return data; +} + +esp_err_t bootloader_flash_xmc_startup(void) +{ + extern void ets_rom_delay_us(uint16_t us); + + uint32_t id = bootloader_read_flash_id(); + + // If the RDID value is a valid XMC one, may skip the flow + const bool fast_check = true; + if (fast_check && is_xmc_chip_strict(id)) { + ESP_LOGD(TAG, "XMC chip detected by RDID (%08X), skip.", id); + return ESP_OK; + } + + // Check the Manufacturer ID in SFDP registers (JEDEC standard). If not XMC chip, no need to run the flow + const int sfdp_mfid_addr = 0x10; + uint8_t mf_id = (bootloader_flash_read_sfdp(sfdp_mfid_addr, 1) & 0xff); + if (mf_id != XMC_VENDOR_ID) { + ESP_LOGD(TAG, "non-XMC chip detected by SFDP Read (%02X), skip.", mf_id); + return ESP_OK; + } + + ESP_LOGI(TAG, "XM25QHxxC startup flow"); + // Enter DPD + bootloader_execute_flash_command(0xB9, 0, 0, 0); + // Enter UDPD + bootloader_execute_flash_command(0x79, 0, 0, 0); + // Exit UDPD + bootloader_execute_flash_command(0xFF, 0, 0, 0); + // Delay tXUDPD + ets_rom_delay_us(2000); + // Release Power-down + bootloader_execute_flash_command(0xAB, 0, 0, 0); + ets_rom_delay_us(20); + // Read flash ID and check again + id = bootloader_read_flash_id(); + if (!is_xmc_chip_strict(id)) { + ESP_LOGE(TAG, "XMC flash startup fail"); + return ESP_FAIL; + } + + return ESP_OK; +} +#else +static bool is_xmc_chip(uint32_t rdid) +{ + uint32_t vendor_id = (rdid >> 16) &0xff; + + return vendor_id == XMC_VENDOR_ID; +} + +esp_err_t bootloader_flash_xmc_startup(void) +{ + uint32_t id = bootloader_read_flash_id(); + + if (is_xmc_chip(id)) { + ESP_LOGE(TAG, "XMC chip detected(%08X) while support disable.", id); + return ESP_FAIL; + } else { + ESP_LOGI(TAG, "flash chip is %08X", id); + } + + return ESP_OK; +} +#endif +#endif + #endif diff --git a/components/bootloader_support/src/bootloader_init.c b/components/bootloader_support/src/bootloader_init.c index fda2a49f7..2ca04d11f 100644 --- a/components/bootloader_support/src/bootloader_init.c +++ b/components/bootloader_support/src/bootloader_init.c @@ -642,6 +642,8 @@ esp_err_t bootloader_init() static esp_err_t bootloader_main() { + esp_err_t ret; + #ifdef CONFIG_BOOTLOADER_DISABLE_JTAG_IO /* Set GPIO 12-15 to be normal GPIO */ PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); @@ -655,6 +657,11 @@ static esp_err_t bootloader_main() uart_console_configure(); + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + return ESP_FAIL; + } + esp_image_header_t fhdr; if (bootloader_flash_read(ESP_BOOTLOADER_OFFSET, &fhdr, sizeof(esp_image_header_t), true) != ESP_OK) { ESP_LOGE(TAG, "failed to load bootloader header!"); From 06f3948b27418f3ca0ad040782b718a36c15123b Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Tue, 21 Jun 2022 14:33:58 +0800 Subject: [PATCH 30/59] feat(spi_flash): Support Flash ID 0x164068 --- components/spi_flash/src/spi_flash.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/spi_flash/src/spi_flash.c b/components/spi_flash/src/spi_flash.c index 93240e9d2..221b25951 100644 --- a/components/spi_flash/src/spi_flash.c +++ b/components/spi_flash/src/spi_flash.c @@ -259,6 +259,12 @@ bool spi_flash_check_wr_protect(void) special_flash_write_status(0x01, status&(~(SPI_GD25Q32_FLASH_WRITE_PROTECT_STATUS)), 1, true); } } + } else if (flash_id == 0x164068) { + if(spi_flash_read_status(&status)==0) { //Read status Ok + if(status&SPI_GD25Q32_FLASH_WRITE_PROTECT_STATUS) { + special_flash_write_status(0x01, status&(~(SPI_GD25Q32_FLASH_WRITE_PROTECT_STATUS)), 1, true); + } + } } //Others else { @@ -435,6 +441,10 @@ void user_spi_flash_dio_to_qio_pre_init(void) to_qio = true; } //ENBALE FLASH QIO 0X01H+0X00+0X02 + } else if (flash_id == 0x164068) { + if (flash_gd25q32c_enable_QIO_mode() == true) { + to_qio = true; + } } else { if (spi_flash_enable_qmode_raw(&g_rom_flashchip) == ESP_OK) { to_qio = true; From 04281f67e1f5c4250a3edb19c8dbd3bbdc25775a Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Tue, 13 Sep 2022 09:44:18 +0800 Subject: [PATCH 31/59] fix(lib): do not check basic rate for signal test router --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 486144 -> 486252 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 538000 -> 538108 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 40df30cab..c1ac715ae 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: 44c6f29 + net80211: 35bfa7c pp: 44c6f29 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index 66cb78ce1adebebc30bb49c9f0c27f081daf27c3..a4b8a093c981e61efd338f29f609d565cf6a7b6f 100644 GIT binary patch delta 5409 zcmaKwe^gUfw#UyofqM}We%c^PAol`EfB;EAqEBcgtZ>}>sU1OQ@0&v! z|Czg1*q`&=`|PvNx#yf@(S6eN^-0r)$qAL^j`H&9m6f~IL$bQGk;wo2Gq5`$YvApw z_&r4lS4*Rv`CE^%%stcHmyA(qp#7q8Pu0A8O;LrPeO=1j^IGdRb5zoT8vE4jyOqH= zk9}WQq}#v!?!@=q_OxF--+xz^;HtIMwfb&p=u29vi%LN(k?{#vXxd$xk@4*1y2RAg zEzgubS>~)9Uk@lPGq~~_I^7Tr*D48qI7)kzsD?8LHs-KblA==Zb|GuAecxO03+YgFRNOXbYquDQCN z?-NJ5)-BgvtBJ;GU6*gsr7wy~*mY9yrR%zzLs2dG(%W$%9&Kv#f1z!5e6Ve{w6Y

MAkp!%$Q^9d>UK4+89(cvrW_qphEcS3|LP@GMQ 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 PVf6o3LHJMD_4I!Lwx^L3 delta 5294 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 diff --git a/components/esp8266/lib/libnet80211_dbg.a b/components/esp8266/lib/libnet80211_dbg.a index f25311a231d1fe3ab939a7c1fa4db9458dbea89f..1ab3c53ea4a73091737b4455ff4e9e917c721480 100644 GIT binary patch delta 6302 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 DtX!d? delta 6157 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 Z(2ibxg#TI5We@vb+N4f7L-&{XzX2Xbd(Hp= From 20dbb96fcdc3ae5f2117292227b073a70b35ab07 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 19 Sep 2022 16:13:20 +0800 Subject: [PATCH 32/59] fix(spi_flash): Fix bootloader failed to read image data --- components/spi_flash/src/spi_flash_raw.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/spi_flash/src/spi_flash_raw.c b/components/spi_flash/src/spi_flash_raw.c index dee89962b..10abf29e0 100644 --- a/components/spi_flash/src/spi_flash_raw.c +++ b/components/spi_flash/src/spi_flash_raw.c @@ -39,7 +39,9 @@ uint32_t spi_flash_get_id_raw(esp_rom_spiflash_chip_t *chip) { uint32_t rdid = 0; +#ifndef BOOTLOADER_BUILD Cache_Read_Disable(); +#endif Wait_SPI_Idle(chip); @@ -49,7 +51,9 @@ uint32_t spi_flash_get_id_raw(esp_rom_spiflash_chip_t *chip) rdid = READ_PERI_REG(PERIPHS_SPI_FLASH_C0)&0xffffff; +#ifndef BOOTLOADER_BUILD Cache_Read_Enable_New(); +#endif return rdid; } @@ -150,8 +154,11 @@ bool spi_user_cmd_raw(esp_rom_spiflash_chip_t *chip, spi_cmd_dir_t mode, spi_cmd { int idx = 0; +#ifndef BOOTLOADER_BUILD // Cache Disable Cache_Read_Disable_2(); +#endif + //wait spi idle if((mode & SPI_RAW) == 0) { Wait_SPI_Idle(chip); @@ -272,8 +279,11 @@ bool spi_user_cmd_raw(esp_rom_spiflash_chip_t *chip, spi_cmd_dir_t mode, spi_cmd if((mode & SPI_RAW) == 0) { Wait_SPI_Idle(chip); } + +#ifndef BOOTLOADER_BUILD //enable icache Cache_Read_Enable_2(); +#endif return true; } From eb4066e80e1b791fa40a6c336d15e499e68609d4 Mon Sep 17 00:00:00 2001 From: Zhang Jun Hao Date: Sun, 9 Oct 2022 14:12:03 +0800 Subject: [PATCH 33/59] fix(lib): fix scan state error --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 486252 -> 486256 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 538108 -> 538112 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index c1ac715ae..b3e43f0b4 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: 35bfa7c + net80211: 4357636 pp: 44c6f29 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index a4b8a093c981e61efd338f29f609d565cf6a7b6f..0ba47b53907a338bed204e5fa698b5f784c5a150 100644 GIT binary patch delta 3221 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 Q(QaMqu&QKJk^WE1KOJKE@&Et; From 8afe4a6785aaabde972cc14c98e5b250ac88a67f Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 16 Feb 2023 17:11:31 +0800 Subject: [PATCH 34/59] feat(spi_flash): Add patch to fix TH25Q16HB page 0 hardware issue --- components/esp8266/source/startup.c | 4 + components/spi_flash/CMakeLists.txt | 4 + components/spi_flash/Kconfig | 12 + components/spi_flash/component.mk | 4 + components/spi_flash/include/spi_flash.h | 9 + components/spi_flash/linker.lf | 1 + components/spi_flash/src/patch/th25q16hb.c | 709 +++++++++++++++++++++ 7 files changed, 743 insertions(+) create mode 100644 components/spi_flash/Kconfig create mode 100644 components/spi_flash/src/patch/th25q16hb.c diff --git a/components/esp8266/source/startup.c b/components/esp8266/source/startup.c index b9e582cec..42fc18825 100644 --- a/components/esp8266/source/startup.c +++ b/components/esp8266/source/startup.c @@ -93,6 +93,10 @@ static void user_init_entry(void *param) esp_set_cpu_freq(ESP_CPU_FREQ_160M); #endif +#ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 + assert(th25q16hb_apply_patch_0() == 0); +#endif + app_main(); vTaskDelete(NULL); diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index b2a0f715c..255eff8e7 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -5,6 +5,10 @@ if(BOOTLOADER_BUILD) set(srcs "${srcs}" "port/port.c") set(priv_requires "bootloader_support") else() + if(CONFIG_ENABLE_TH25Q16HB_PATCH_0) + list(APPEND srcs "src/patch/th25q16hb.c") + endif() + set(priv_requires "esp8266" "freertos" "bootloader_support") endif() diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig new file mode 100644 index 000000000..3d97ba109 --- /dev/null +++ b/components/spi_flash/Kconfig @@ -0,0 +1,12 @@ +menu "SPI Flash" + + menu "Patch" + config ENABLE_TH25Q16HB_PATCH_0 + bool "Enable TH25Q16HB Patch 0" + default n + help + WARNING: If you don't use TH25Q16HB, you must not enable this option. + Although you use TH25Q16HB, you should ask your flash manufacturer + if your flash need use this patch. + endmenu +endmenu diff --git a/components/spi_flash/component.mk b/components/spi_flash/component.mk index f5eb97ca6..c4dfc940a 100644 --- a/components/spi_flash/component.mk +++ b/components/spi_flash/component.mk @@ -15,4 +15,8 @@ CFLAGS += -DPARTITION_QUEUE_HEADER=\"sys/queue.h\" ifdef IS_BOOTLOADER_BUILD COMPONENT_SRCDIRS += port COMPONENT_OBJS += port/port.o +else +ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 +COMPONENT_SRCDIRS += src/patch +endif endif diff --git a/components/spi_flash/include/spi_flash.h b/components/spi_flash/include/spi_flash.h index d51756d72..7dfeb6a24 100644 --- a/components/spi_flash/include/spi_flash.h +++ b/components/spi_flash/include/spi_flash.h @@ -204,6 +204,15 @@ int esp_patition_table_init_data(void *partition_info); int esp_patition_copy_ota1_to_ota0(const void *partition_info); #endif +#ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 +/** + * @brief Apply TH25Q16HB patch 0 to avoid some hardware issues. + * + * @return 0 if success or others if failed + */ +int th25q16hb_apply_patch_0(void); +#endif + #ifdef __cplusplus } #endif diff --git a/components/spi_flash/linker.lf b/components/spi_flash/linker.lf index 9ac365ea5..27695eb70 100644 --- a/components/spi_flash/linker.lf +++ b/components/spi_flash/linker.lf @@ -2,3 +2,4 @@ archive: libspi_flash.a entries: spi_flash_raw (noflash) + th25q16hb (noflash) diff --git a/components/spi_flash/src/patch/th25q16hb.c b/components/spi_flash/src/patch/th25q16hb.c new file mode 100644 index 000000000..425096f9b --- /dev/null +++ b/components/spi_flash/src/patch/th25q16hb.c @@ -0,0 +1,709 @@ +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_attr.h" +#include "spi_flash.h" +#include "priv/esp_spi_flash_raw.h" +#include "FreeRTOS.h" + +#include "esp8266/eagle_soc.h" +#include "esp8266/pin_mux_register.h" +#include "esp8266/spi_register.h" +#include "esp8266/spi_struct.h" + +#define SPI_FLASH SPI0 +#define SPI_BLOCK_SIZE 32 +#define ADDR_SHIFT_BITS 8 + +#if 0 +typedef int (*__ets_printf_t)(const char *fmt, ...); +#define ROM_PRINTF(_fmt, ...) ((__ets_printf_t)(0x400024cc))(_fmt, ##__VA_ARGS__) +#else +#define ROM_PRINTF(_fmt, ...) +#endif + +#define TOCHAR(_v) #_v +#define PRINT_STEP(_s) ROM_PRINTF("Step %d\n", (_s)); +#define JUMP_TO_STEP(_s) { ROM_PRINTF("Jump to " TOCHAR(_s) "\n"); goto _s; } +#define GOTO_FAILED(_s) { ROM_PRINTF("ERROR: " TOCHAR(_s) " failed\n"); ret = -EIO; JUMP_TO_STEP(step17); } + +#define write_u8_dummy(_c, _a, _d8,_d) {uint32_t __data = _d8; spi_trans(1, (_c), 8, (_a), 24, (uint8_t *)&__data, 1, (_d));} +#define write_u8(_c, _a, _d8) write_u8_dummy((_c), (_a), (_d8), 0) + +extern void Cache_Read_Disable_2(void); +extern void Cache_Read_Enable_2(); +extern void vPortEnterCritical(void); +extern void vPortExitCritical(void); +extern uint32_t spi_flash_get_id(void); + +static void delay(int ms) +{ + for (volatile int i = 0; i < ms; i++) { + for (volatile int j = 0; j < 7800; j++) { + } + } +} + +#if 0 +static void dump_hex(const uint8_t *ptr, int n) +{ + const uint8_t *s1 = ptr; + const int line_bytes = 16; + + ROM_PRINTF("\nHex:\n"); + for (int i = 0; i < n ; i += line_bytes) + { + int m = MIN(n - i, line_bytes); + + ROM_PRINTF("\t"); + for (int j = 0; j < m; j++) + { + ROM_PRINTF("%02x ", s1[i + j]); + } + + ROM_PRINTF("\n"); + } + + ROM_PRINTF("\n"); +} + +static void dump_hex_compare(const uint8_t *s1, const uint8_t *s2, int n) +{ + const int line_bytes = 16; + + ROM_PRINTF("\nHex:\n"); + for (int i = 0; i < n ; i += line_bytes) + { + int m = MIN(n - i, line_bytes); + + ROM_PRINTF("\t"); + for (int j = 0; j < m; j++) + { + ROM_PRINTF("%02x:%02x ", s1[i + j], s2[i + j]); + } + + ROM_PRINTF("\n"); + } + + ROM_PRINTF("\n"); +} +#endif + +typedef struct spi_state { + uint32_t io_mux_reg; + uint32_t spi_clk_reg; + uint32_t spi_ctrl_reg; + uint32_t spi_user_reg; +} spi_state_t; + +static void spi_enter(spi_state_t *state) +{ + vPortEnterCritical(); + Cache_Read_Disable_2(); + + Wait_SPI_Idle(&g_rom_flashchip); + + state->io_mux_reg = READ_PERI_REG(PERIPHS_IO_MUX_CONF_U); + state->spi_clk_reg = SPI_FLASH.clock.val; + state->spi_ctrl_reg = SPI_FLASH.ctrl.val; + state->spi_user_reg = SPI_FLASH.user.val; + + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user.flash_mode = 0; + SPI_FLASH.user.usr_miso_highpart = 0; + + CLEAR_PERI_REG_MASK(PERIPHS_IO_MUX_CONF_U, SPI0_CLK_EQU_SYS_CLK); + + SPI_FLASH.user.cs_setup = 1; + SPI_FLASH.user.cs_hold = 1; + SPI_FLASH.user.usr_mosi = 1; + + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user.flash_mode = 0; + + SPI_FLASH.ctrl.fread_qio = 0; + SPI_FLASH.ctrl.fread_dio = 0; + SPI_FLASH.ctrl.fread_quad = 0; + SPI_FLASH.ctrl.fread_dual = 0; + + SPI_FLASH.clock.val = 0; + SPI_FLASH.clock.clkcnt_l = 3; + SPI_FLASH.clock.clkcnt_h = 1; + SPI_FLASH.clock.clkcnt_n = 3; + + SPI_FLASH.ctrl.fastrd_mode = 1; + + while (SPI_FLASH.cmd.usr) { + ; + } +} + +static void spi_exit(spi_state_t *state) +{ + while (SPI_FLASH.cmd.usr) { + ; + } + + WRITE_PERI_REG(PERIPHS_IO_MUX_CONF_U, state->io_mux_reg); + + SPI_FLASH.ctrl.val = state->spi_ctrl_reg; + SPI_FLASH.clock.val = state->spi_clk_reg; + SPI_FLASH.user.val = state->spi_user_reg; + + Cache_Read_Enable_2(); + vPortExitCritical(); +} + +static void spi_trans_block(bool write_mode, + uint32_t cmd, + uint32_t cmd_bits, + uint32_t addr, + uint32_t addr_bits, + uint8_t *data, + uint32_t data_bytes, + uint32_t dummy_bits) +{ + if ((uint32_t)data & 0x3) { + ROM_PRINTF("ERROR: data=%p\n", data); + return; + } + + if (cmd_bits) { + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user2.usr_command_value = cmd; + SPI_FLASH.user2.usr_command_bitlen = cmd_bits - 1; + } else { + SPI_FLASH.user.usr_command = 0; + } + + if (addr_bits) { + SPI_FLASH.user.usr_addr = 1; + SPI_FLASH.addr = addr << ADDR_SHIFT_BITS; + SPI_FLASH.user1.usr_addr_bitlen = addr_bits - 1; + } else { + SPI_FLASH.user.usr_addr = 0; + } + + if (dummy_bits) { + SPI_FLASH.user.usr_dummy = 1; + SPI_FLASH.user1.usr_dummy_cyclelen = dummy_bits - 1; + } else { + SPI_FLASH.user.usr_dummy = 0; + } + + if (write_mode && data_bytes) { + int words = (data_bytes + 3) / 4; + uint32_t *p = (uint32_t *)data; + + SPI_FLASH.user.usr_mosi = 1; + SPI_FLASH.user.usr_miso = 0; + SPI_FLASH.user1.usr_mosi_bitlen = data_bytes * 8 - 1; + + for (int i = 0; i < words; i++) { + SPI_FLASH.data_buf[i] = p[i]; + } + } else if (!write_mode && data_bytes) { + int words = (data_bytes + 3) / 4; + + SPI_FLASH.user.usr_mosi = 0; + SPI_FLASH.user.usr_miso = 1; + SPI_FLASH.user1.usr_miso_bitlen = data_bytes * 8 - 1; + + for (int i = 0; i < words; i++) { + SPI_FLASH.data_buf[i] = 0; + } + } else { + SPI_FLASH.user.usr_mosi = 0; + SPI_FLASH.user.usr_miso = 0; + } + + SPI_FLASH.cmd.usr = 1; + while (SPI_FLASH.cmd.usr) { + ; + } + + if (!write_mode && data_bytes) { + int words = (data_bytes + 3) / 4; + uint32_t *p = (uint32_t *)data; + + for (int i = 0; i < words; i++) { + p[i] = SPI_FLASH.data_buf[i]; + } + } +} + +static void spi_trans(bool write_mode, + uint32_t cmd, + uint32_t cmd_bits, + uint32_t addr, + uint32_t addr_bits, + uint8_t *data, + uint32_t data_bytes, + uint32_t dummy_bits) + +{ + if (!data_bytes || data_bytes <= SPI_BLOCK_SIZE) { + return spi_trans_block(write_mode, cmd, cmd_bits, addr, + addr_bits, data, data_bytes, dummy_bits); + } + + for (int i = 0; i < data_bytes; i += SPI_BLOCK_SIZE) { + uint32_t n = MIN(SPI_BLOCK_SIZE, data_bytes - i); + + spi_trans_block(write_mode, cmd, cmd_bits, addr + i, + addr_bits, data + i, n, dummy_bits); + } +} + +static void write_cmd(uint32_t cmd) +{ + spi_trans(1, cmd, 8, 0, 0, NULL, 0, 0); +} + +static void write_buffer(uint32_t addr, uint8_t *buffer, int size) +{ + for (int i = 0; i < size; i += SPI_BLOCK_SIZE) { + int n = MIN(size - i, SPI_BLOCK_SIZE); + + write_cmd(0x6); + spi_trans(1, 0x42, 8, addr + i, 24, buffer + i, n, 0); + delay(3); + } +} + +static void read_buffer(uint32_t addr, uint8_t *buffer, int n) +{ + spi_trans(0, 0x48, 8, addr, 24, buffer, n, 8); +} + +static void erase_sector(uint32_t addr) +{ + write_cmd(0x6); + spi_trans(1, 0x44, 8, addr, 24, NULL, 0, 0); + delay(8); +} + +int th25q16hb_apply_patch_0(void) +{ + int ret = 0; + uint32_t flash_id; + int count; + spi_state_t state; + uint8_t *buffer256_0; + uint8_t *buffer256_1; + uint8_t *buffer256_2; + uint8_t *buffer1024; + + flash_id = spi_flash_get_id(); + if (flash_id != 0x1560eb) { + ROM_PRINTF("WARN: id=0x%x, is not TH25Q16HB\n", flash_id); + return 0; + } + + buffer1024 = heap_caps_malloc(1024, MALLOC_CAP_8BIT); + if (!buffer1024) { + return -ENOMEM; + } + + buffer256_0 = buffer1024; + buffer256_1 = buffer1024 + 256; + buffer256_2 = buffer1024 + 512; + + spi_enter(&state); + + // Step 1 + PRINT_STEP(1); + write_cmd(0x33); + + // Step 2 + PRINT_STEP(2); + write_cmd(0xcc); + + // Step 3 + PRINT_STEP(3); + write_cmd(0xaa); + + // Step 4 + PRINT_STEP(4); + write_u8(0xfa, 0x1200d, 0x3); + write_u8_dummy(0xfa, 0x2200d, 0x3, 1); + + // Step 5-1 + PRINT_STEP(5); + read_buffer(0xbed, buffer256_0, 3); + if (buffer256_0[0] == 0xff && + buffer256_0[1] == 0xff && + buffer256_0[2] == 0xff) { + ROM_PRINTF("INFO: check done 0\n"); + } else if (buffer256_0[0] == 0x55 && + buffer256_0[1] == 0xff && + buffer256_0[2] == 0xff) { + JUMP_TO_STEP(step10); + } else if (buffer256_0[0] == 0x55 && + buffer256_0[1] == 0x55 && + buffer256_0[2] == 0xff) { + JUMP_TO_STEP(step14); + } else if (buffer256_0[0] == 0x55 && + buffer256_0[1] == 0x55 && + buffer256_0[2] == 0x55) { + JUMP_TO_STEP(step17); + } else { + ROM_PRINTF("ERROR: 0xbed=0x%x 0xbee=0x%x 0xbef=0x%x\n", + buffer256_0[0], buffer256_0[1], buffer256_0[2]); + GOTO_FAILED(5-1); + } + + // Step 5-2 + read_buffer(0x50d, buffer256_0, 1); + buffer256_0[0] &= 0x7f; + if (buffer256_0[0] == 0x7c) { + JUMP_TO_STEP(step17); + } else if (buffer256_0[0] == 0x3c) { + ROM_PRINTF("INFO: check done 1\n"); + } else { + ROM_PRINTF("ERROR: 0x50d=0x%x\n", buffer256_0[0]); + GOTO_FAILED(5-2); + } + + // Step 6 + PRINT_STEP(6); + for (count = 0; count < 3; count++) { + erase_sector(0); + + bool check_done = true; + read_buffer(0x0, buffer1024, 1024); + for (int i = 0; i < 1024; i++) { + if (buffer1024[i] != 0xff) { + check_done = false; + ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + break; + } + } + + if (check_done) { + break; + } + } + if (count >= 3) { + GOTO_FAILED(6) + } + + // Step 7-1.1 + PRINT_STEP(7); + read_buffer(0x400, buffer256_0, 256); + read_buffer(0x400, buffer256_1, 256); + read_buffer(0x400, buffer256_2, 256); + if (memcmp(buffer256_0, buffer256_1, 256) || + memcmp(buffer256_0, buffer256_2, 256)) { + GOTO_FAILED(7-1.1); + } + + write_buffer(0, buffer256_0, 256); + write_buffer(0x200, buffer256_0, 256); + + // Step 7-1.2 + for (count = 0; count < 3; count++) { + read_buffer(0, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + + read_buffer(0x200, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + } + if (count < 3) { + GOTO_FAILED(7-1.2); + } + + // Step 7-2.1 + read_buffer(0x500, buffer256_0, 256); + read_buffer(0x500, buffer256_1, 256); + read_buffer(0x500, buffer256_2, 256); + if (memcmp(buffer256_0, buffer256_1, 256) || + memcmp(buffer256_0, buffer256_2, 256)) { + GOTO_FAILED(7-2.1); + } + write_buffer(0x100, buffer256_0, 256); + write_buffer(0x300, buffer256_0, 256); + + // Step 7-2.2 + for (count = 0; count < 3; count++) { + read_buffer(0x100, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + read_buffer(0x300, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + } + if (count < 3) { + GOTO_FAILED(7-2.2); + } + + // Step 8 + PRINT_STEP(8); + read_buffer(0x0, buffer256_0, 1); + read_buffer(0x23, buffer256_1, 1); + if (buffer256_0[0] != 0x13 || buffer256_1[0] != 0x14) { + ROM_PRINTF("ERROR: 0x0=0x%x 0x23=0x%x\n", buffer256_0[0], buffer256_1[0]); + GOTO_FAILED(8); + } + + // Step 9-1 + PRINT_STEP(9); + read_buffer(0x140, buffer256_0, 2); + if (buffer256_0[0] != 0 || buffer256_0[1] != 0xff) { + ROM_PRINTF("ERROR: 0x140=0x%x 0x141=0x%x\n", buffer256_0[0], buffer256_0[1]); + GOTO_FAILED(9-1); + } + + // Step 9-2 + buffer256_0[0] = 0x55; + write_buffer(0xaed, buffer256_0, 1); + write_buffer(0xbed, buffer256_0, 1); + for (count = 0; count < 3; count++) { + read_buffer(0xaed, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + + read_buffer(0xbed, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + } + if (count < 3) { + GOTO_FAILED(9-2); + } + +step10: + // Step 10-1 + PRINT_STEP(10); + for (count = 0; count < 3; count++) { + erase_sector(0x400); + + bool check_done = true; + read_buffer(0x400, buffer1024, 1024); + for (int i = 0; i < 1024; i++) { + if (buffer1024[i] != 0xff) { + check_done = false; + ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + break; + } + } + + if (check_done) { + break; + } + } + if (count >= 3) { + GOTO_FAILED(10-1); + } + + // Step 10-3.1 + read_buffer(0, buffer256_0, 256); + read_buffer(0, buffer256_1, 256); + read_buffer(0, buffer256_2, 256); + if (memcmp(buffer256_0, buffer256_1, 256) || + memcmp(buffer256_0, buffer256_2, 256)) { + GOTO_FAILED(10-3.1); + } + write_buffer(0x400, buffer256_0, 256); + write_buffer(0x600, buffer256_0, 256); + + // Step 10-3.2 + for (count = 0; count < 3; count++) { + read_buffer(0x400, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + + read_buffer(0x600, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + } + if (count < 3) { + GOTO_FAILED(10-3.2); + } + + // Step 10-3.3 + read_buffer(0x100, buffer256_0, 256); + read_buffer(0x100, buffer256_1, 256); + read_buffer(0x100, buffer256_2, 256); + if (memcmp(buffer256_0, buffer256_1, 256) || + memcmp(buffer256_0, buffer256_2, 256)) { + GOTO_FAILED(10-3.3); + } + buffer256_0[9] = 0x79; + buffer256_0[13] = (buffer256_0[13] & 0x3f) | + ((~buffer256_0[13]) & 0x80) | + 0x40; + write_buffer(0x500, buffer256_0, 256); + write_buffer(0x700, buffer256_0, 256); + + // Step 10-3.4 + for (count = 0; count < 3; count++) { + read_buffer(0x500, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + + read_buffer(0x700, buffer256_1, 256); + if (memcmp(buffer256_0, buffer256_1, 256)) { + break; + } + } + if (count < 3) { + GOTO_FAILED(10-3.4); + } + + // Step11-1 + PRINT_STEP(11); + + for (count = 0; count < 3; count++) { + read_buffer(0x400, buffer256_0, 1); + read_buffer(0x423, buffer256_1, 1); + if (buffer256_0[0] != 0x13 || buffer256_1[0] != 0x14) { + ROM_PRINTF("ERROR: 0x400=0x%x 0x423=0x%x\n", buffer256_0[0], buffer256_1[0]); + break; + } + } + if (count < 3) { + GOTO_FAILED(11-1); + } + + // Step11-2.1 + for (count = 0; count < 3; count++) { + read_buffer(0x540, buffer256_0, 2); + if (buffer256_0[0] != 0 || buffer256_0[1] != 0xff) { + ROM_PRINTF("ERROR: 0x540=0x%x 0x541=0x%x\n", buffer256_0[0], buffer256_0[1]); + break; + } + } + if (count < 3) { + GOTO_FAILED(11-2); + } + + // Step11-2.2 + for (count = 0; count < 3; count++) { + read_buffer(0x50d, buffer256_0, 1); + buffer256_0[0] &= 0x7f; + if (buffer256_0[0] != 0x7c) { + ROM_PRINTF("ERROR: 0x50d=0x%x\n", buffer256_0[0]); + break; + } + } + if (count < 3) { + GOTO_FAILED(11-2); + } + + // Step 12 + PRINT_STEP(12); + buffer256_0[0] = 0x55; + write_buffer(0xaee, buffer256_0, 1); + write_buffer(0xbee, buffer256_0, 1); + + // Step 13 + PRINT_STEP(13); + for (count = 0; count < 3; count++) { + read_buffer(0xaee, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + + read_buffer(0xbee, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + } + if (count < 3) { + GOTO_FAILED(13); + } + +step14: + // Step 14 + PRINT_STEP(14); + for (count = 0; count < 3; count++) { + erase_sector(0); + + bool check_done = true; + read_buffer(0x0, buffer1024, 1024); + for (int i = 0; i < 1024; i++) { + if (buffer1024[i] != 0xff) { + check_done = false; + ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + break; + } + } + + if (check_done) { + break; + } + } + if (count >= 3) { + GOTO_FAILED(14); + } + + // Step 15 + PRINT_STEP(15); + buffer256_0[0] = 0x55; + write_buffer(0xaef, buffer256_0, 1); + write_buffer(0xbef, buffer256_0, 1); + + // Step 16 + PRINT_STEP(16); + for (count = 0; count < 3; count++) { + read_buffer(0xaef, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + + read_buffer(0xbef, buffer256_0, 1); + if (buffer256_0[0] != 0x55) { + break; + } + } + if (count < 3) { + GOTO_FAILED(16); + } + +step17: + // Step 17 + PRINT_STEP(17); + write_cmd(0x55); + + // Step 18 + PRINT_STEP(18); + write_cmd(0x88); + + spi_exit(&state); + + heap_caps_free(buffer1024); + + if (!ret) { + ROM_PRINTF("INFO: Patch for TH25Q16HB is done\n"); + } + + return ret; +} From 1c33bd488b90d4f6a7727834164f532e35d43687 Mon Sep 17 00:00:00 2001 From: yuanjianmin Date: Mon, 3 Apr 2023 20:03:14 +0800 Subject: [PATCH 35/59] esp-tls: Add API for mbedtls to get and set ciphersuites --- components/esp-tls/esp_tls.c | 5 +++++ components/esp-tls/esp_tls.h | 13 +++++++++++-- components/esp-tls/esp_tls_mbedtls.c | 10 ++++++++++ .../esp-tls/private_include/esp_tls_mbedtls.h | 5 +++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/components/esp-tls/esp_tls.c b/components/esp-tls/esp_tls.c index da4dfe291..a5daf24f9 100644 --- a/components/esp-tls/esp_tls.c +++ b/components/esp-tls/esp_tls.c @@ -54,6 +54,7 @@ static const char *TAG = "esp-tls"; #define _esp_tls_set_global_ca_store esp_mbedtls_set_global_ca_store /*!< Callback function for setting global CA store data for TLS/SSL */ #define _esp_tls_get_global_ca_store esp_mbedtls_get_global_ca_store #define _esp_tls_free_global_ca_store esp_mbedtls_free_global_ca_store /*!< Callback function for freeing global ca store for TLS/SSL */ +#define _esp_tls_get_ciphersuites_list esp_mbedtls_get_ciphersuites_list #elif CONFIG_ESP_TLS_USING_WOLFSSL /* CONFIG_ESP_TLS_USING_MBEDTLS */ #define _esp_create_ssl_handle esp_create_wolfssl_handle #define _esp_tls_handshake esp_wolfssl_handshake @@ -437,6 +438,10 @@ mbedtls_x509_crt *esp_tls_get_global_ca_store(void) return _esp_tls_get_global_ca_store(); } +const int *esp_tls_get_ciphersuites_list(void) +{ + return _esp_tls_get_ciphersuites_list(); +} #endif /* CONFIG_ESP_TLS_USING_MBEDTLS */ #ifdef CONFIG_ESP_TLS_SERVER /** diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index a5983b2e3..a9ff17d5d 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -200,7 +200,8 @@ typedef struct esp_tls_cfg { esp_err_t (*crt_bundle_attach)(void *conf); /*!< Function pointer to esp_crt_bundle_attach. Enables the use of certification bundle for server verification, must be enabled in menuconfig */ - + const int *ciphersuites_list; /*!< Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. + Please check the list validity by esp_tls_get_ciphersuites_list() API */ } esp_tls_cfg_t; #ifdef CONFIG_ESP_TLS_SERVER @@ -574,6 +575,15 @@ esp_err_t esp_tls_get_and_clear_last_error(esp_tls_error_handle_t h, int *esp_tl */ mbedtls_x509_crt *esp_tls_get_global_ca_store(void); +/** + * @brief Get supported TLS ciphersuites list. + * + * See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 for the list of ciphersuites + * + * @return Pointer to a zero-terminated array of IANA identifiers of TLS ciphersuites. + * + */ +const int *esp_tls_get_ciphersuites_list(void); #endif /* CONFIG_ESP_TLS_USING_MBEDTLS */ #ifdef CONFIG_ESP_TLS_SERVER /** @@ -602,7 +612,6 @@ int esp_tls_server_session_create(esp_tls_cfg_server_t *cfg, int sockfd, esp_tls */ void esp_tls_server_session_delete(esp_tls_t *tls); #endif /* ! CONFIG_ESP_TLS_SERVER */ - #ifdef __cplusplus } #endif diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index 245921fd0..4e40c865e 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -468,6 +468,11 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t ESP_LOGE(TAG, "You have to provide both clientcert_buf and clientkey_buf for mutual authentication"); return ESP_ERR_INVALID_STATE; } + + if (cfg->ciphersuites_list != NULL && cfg->ciphersuites_list[0] != 0) { + ESP_LOGD(TAG, "Set the ciphersuites list"); + mbedtls_ssl_conf_ciphersuites(&tls->conf, cfg->ciphersuites_list); + } return ESP_OK; } @@ -564,3 +569,8 @@ void esp_mbedtls_free_global_ca_store(void) global_cacert = NULL; } } + +const int *esp_mbedtls_get_ciphersuites_list(void) +{ + return mbedtls_ssl_list_ciphersuites(); +} diff --git a/components/esp-tls/private_include/esp_tls_mbedtls.h b/components/esp-tls/private_include/esp_tls_mbedtls.h index 362a71fd6..4e542cbe6 100644 --- a/components/esp-tls/private_include/esp_tls_mbedtls.h +++ b/components/esp-tls/private_include/esp_tls_mbedtls.h @@ -102,3 +102,8 @@ mbedtls_x509_crt *esp_mbedtls_get_global_ca_store(void); * Callback function for freeing global ca store for TLS/SSL using mbedtls */ void esp_mbedtls_free_global_ca_store(void); + +/** + * Internal Callback for esp_tls_get_ciphersuites_list + */ +const int *esp_mbedtls_get_ciphersuites_list(void); From 0d9450ea0066d5a7a84548c296f500d63fe2fc71 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Fri, 5 May 2023 17:44:59 +0800 Subject: [PATCH 36/59] feat(httpd): Allow binding to same address and port upon restarting server without delay Issue : Restarting the server without 30sec delay between httpd_stop() and httpd_start() causes EADDRINUSE error Resolution : Use setsockopt() to enable SO_REUSEADDR on listener socket Closes https://github.com/espressif/esp-idf/issues/3381 --- components/esp_http_server/src/httpd_main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/esp_http_server/src/httpd_main.c b/components/esp_http_server/src/httpd_main.c index a67513195..5724383aa 100644 --- a/components/esp_http_server/src/httpd_main.c +++ b/components/esp_http_server/src/httpd_main.c @@ -262,6 +262,16 @@ static esp_err_t httpd_server_init(struct httpd_data *hd) .sin_port = htons(hd->config.server_port) }; #endif /* CONFIG_LWIP_IPV6 */ + + /* Enable SO_REUSEADDR to allow binding to the same + * address and port when restarting the server */ + int enable = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { + /* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But + * it does not affect the normal working of the HTTP Server */ + ESP_LOGW(TAG, LOG_FMT("error in setsockopt SO_REUSEADDR (%d)"), errno); + } + int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); if (ret < 0) { ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno); From 22e9115e1e68cda6f877e460de5dedad729e657e Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Wed, 16 Aug 2023 14:33:04 +0800 Subject: [PATCH 37/59] docs(wifi): Add WPA3 support --- docs/en/api-reference/wifi/esp_wifi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/api-reference/wifi/esp_wifi.rst b/docs/en/api-reference/wifi/esp_wifi.rst index d7c24b176..2368cbd26 100644 --- a/docs/en/api-reference/wifi/esp_wifi.rst +++ b/docs/en/api-reference/wifi/esp_wifi.rst @@ -10,7 +10,7 @@ The WiFi libraries provide support for configuring and monitoring the ESP8266 Wi - AP mode (aka Soft-AP mode or Access Point mode). Stations connect to the ESP8266. - Combined AP-STA mode (ESP8266 is concurrently an access point and a station connected to another access point). -- Various security modes for the above (WPA, WPA2, WEP, etc.) +- Various security modes for the above (WPA, WPA2, WPA3, WEP, etc.) - Scanning for access points (active & passive scanning). - Promiscuous mode monitoring of IEEE802.11 WiFi packets. From 494817f32bfc88e6e6cd2bfb570db5ad3d32c71e Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Tue, 5 Sep 2023 12:02:41 +0800 Subject: [PATCH 38/59] fix the multiple country info issue --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 486256 -> 485200 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 538112 -> 544100 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index b3e43f0b4..b6e16327c 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: 4357636 + net80211: 58ed843 pp: 44c6f29 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index 0ba47b53907a338bed204e5fa698b5f784c5a150..c36ff388c9406f7e75e7def4c73ebf4514c69e51 100644 GIT binary patch literal 485200 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

#bIP! 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

iy96#>_>9%U< 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

5%mZNEf$+P^Doe?#Vm)b?9a`K77+ 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 diff --git a/components/esp8266/lib/libnet80211_dbg.a b/components/esp8266/lib/libnet80211_dbg.a index 1586a362e04e12ff3a26f5f6bdd5663679c163cb..a5e7c8094697762691c79405d890ef293af44883 100644 GIT binary patch literal 544100 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

KnNhkr2NYVZyb2knyHG&~` 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

fIW?ux%(&!kocj=VpHQ%^w!8}Yu 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⪚T-24}x`OyK)h_%kc*>>vhP}kprSP;}!J0Ujk9* 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\C9?Ve2Dm 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 literal 538112 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 X^_HoRI&cm~X*kOLetZkN7saR`k6@_`QTv<@L1% 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

@7hPK_ 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 From fdac6aaf6b2ed245076ebd9099194159ff953195 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Mon, 6 Nov 2023 11:31:09 +0800 Subject: [PATCH 39/59] fix(ESPCS-924): Fixed a potential freertos crash - Reason: A task and B interrupt indirectly access the shared resource pxDelayedTaskList without proper security protection, leading to further crash. A task uses xEventGroupSetBits() to access the pxDelayedTaskList resource: xEventGroupSetBits() -> vTaskRemoveFromUnorderedEventList() -> uxListRemove() -> pxList, where pxList is the pxDelayedTaskList. At this point, another B interrupt is triggered (xEventGroupSetBits only suspends task scheduling and does not disable interrupts) and also accesses the pxDelayedTaskList resource: MacIsrSigPostDefHdl() -> __wifi_queue_send_from_isr() -> xQueueGenericSendFromISR() -> xTaskRemoveFromEventList() -> prvResetNextTaskUnblockTime() -> pxDelayedTaskList. This leads to an unsafe access to the pxDelayedTaskList resource by two entities, causing subsequent crash exceptions. - Fix: Modify the timing of the call to prvResetNextTaskUnblockTime() within xTaskRemoveFromEventList from unconditional execution to only execute when task scheduling is enabled. This way, when the B interrupt reaches xTaskRemoveFromEventList, it will not call prvResetNextTaskUnblockTime to access the pxDelayedTaskList resource (due to task scheduling being disabled). After the B interrupt execution is complete and control returns to A task, xTaskResumeAll() will be called, and then prvResetNextTaskUnblockTime() will update the pxDelayedTaskList resource again. --- components/freertos/freertos/tasks.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/components/freertos/freertos/tasks.c b/components/freertos/freertos/tasks.c index c747cd74e..61e10e501 100644 --- a/components/freertos/freertos/tasks.c +++ b/components/freertos/freertos/tasks.c @@ -3044,6 +3044,19 @@ BaseType_t xReturn; { ( void ) uxListRemove( &( pxUnblockedTCB->xStateListItem ) ); prvAddTaskToReadyList( pxUnblockedTCB ); + #if( configUSE_TICKLESS_IDLE != 0 ) + { + /* If a task is blocked on a kernel object then xNextTaskUnblockTime + might be set to the blocked task's time out time. If the task is + unblocked for a reason other than a timeout xNextTaskUnblockTime is + normally left unchanged, because it is automatically reset to a new + value when the tick count equals xNextTaskUnblockTime. However if + tickless idling is used it might be more important to enter sleep mode + at the earliest possible time - so reset xNextTaskUnblockTime here to + ensure it is updated at the earliest possible time. */ + prvResetNextTaskUnblockTime(); + } + #endif } else { @@ -3068,20 +3081,6 @@ BaseType_t xReturn; xReturn = pdFALSE; } - #if( configUSE_TICKLESS_IDLE != 0 ) - { - /* If a task is blocked on a kernel object then xNextTaskUnblockTime - might be set to the blocked task's time out time. If the task is - unblocked for a reason other than a timeout xNextTaskUnblockTime is - normally left unchanged, because it is automatically reset to a new - value when the tick count equals xNextTaskUnblockTime. However if - tickless idling is used it might be more important to enter sleep mode - at the earliest possible time - so reset xNextTaskUnblockTime here to - ensure it is updated at the earliest possible time. */ - prvResetNextTaskUnblockTime(); - } - #endif - return xReturn; } /*-----------------------------------------------------------*/ From 0a612356c1d956ed5237d1b9e4e5ca2f03c289b3 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Fri, 24 Nov 2023 19:57:40 +0800 Subject: [PATCH 40/59] fix(esp8266): Fix open mode RX fragment packet fail issue --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libpp.a | Bin 249530 -> 238578 bytes components/esp8266/lib/libpp_dbg.a | Bin 267294 -> 256458 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index b6e16327c..a1918400a 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: db51cd0 net80211: 58ed843 - pp: 44c6f29 + pp: e27e66a espnow: db51cd0 smartconfig: 3.0.0/283eacca diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index 21964fba57c6e85fa186d03daee5724a06a2a573..aa7a91376a9ff4576c0f0e270c6162557c778bfa 100644 GIT binary patch literal 238578 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

xrS3X! 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 literal 249530 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

leN=!5lZ<%NrN77Ox+_`=sqcN$Y~W z9B?;0rbNu%i3EE-+y@It9tN~Q$9Jfj_Kq0#FoZFC|B+xX2lg_R1@}YSdo95p|KLHl zcd=pbY=XTC*c;P_z4sFAjf6eDZt!_Amdo)N3NZVx6ZZbthdqA(%RGgWRsnl_E}$E6 zUb8Wc_V_MW)7}!?OXD6>JL=K#T@mC$M&K4NwbXlOy1_TWGmZ8x0hso9e=47e;xvu} zkPAg#fD1%^XVHy#6+F{uZwA1$mzm!Cck`%6Hw8d0WCX6T1953@4dP6rz4-vs-rKO} z^wC~^2X5L6ufX54iM=|*-r@v%BS+x<>qys*Hs}@t$c2o+`8(t5Py^yjWBrx_%=&GH zy;{h%qaIxifLzE3T>ff2BZrLL;6`|+(H`G*YuY;ndvXf{sjeQw<>JL=JW4L~kr1g>z6`)qnQxDB3ZwD%am zv{!+HyqE%U)T8?jfLzE3+~M2e^M&()X|%TwVA|V`@3h~8Ts!K~@mUCRAtP|R*1GSS z>IO&PnMQj@0H(cdpK{ZYq#X6=egPmCG6Gk(4$l`NV>fsuJkx0J4*=7iZ6SMar zluUc4VXu-I#L)&Fp9diqG6Hu5_BJ5whR0CH?D+=twzoI~f6wZLf;ifsGH1^-91bcqJ`%Y3MDW~m?NU*nOGoEKe!)R|UytX$n!QShBTx%d*J8f@l zg1x#g#OE`HI%aQrg1ub<)L9!7Jx2LV3~d)O0(a`(_;tz##C83O6YL#;y)i0>`=RSM zJHcN1miYM%T^X}?U4p%nuqR)`D1+J_-w{nNR8s6&411d()Aegju$St%e=8~3lpgu! z1bdrduL|;R_`Bh?y*r_2USF2O9)>OEG=H(5djFiDw;1+lUpviD_fzk;{nU&0Q*RXJ zxY=ITPq&eE)O=|ldb;k^)6D0J67A_UQ?I%YJ&u8Pns5Jq*!$Y>sH$`Ay=M{#%p@cs zAQse}A;5r1oJ;})1)YQdLD7crVenA%9g-SINP>oz)`-fXEgnChJ=kJTu-3zAX+=fr zX>A)mq+nx2SX{bQ(BoQ$S~T1@fSy@io$fCO z&_ff9iPqzDP-guxT^&bdjsFNe<{=~&s5etEJ;u@f6?%AdC??yWR~QSqIt=oeP+ca5 zk3g@{AT;DNSjCB|Ge?dS!OexoA&z+LWwtu|Hg# zoa49OjrL&F=l3W0)9rVNlkyH`rte8jKCSHJMWc?>(Gy6UGF}+V0#S4!N-k?gAjZa$ zwN$#t(wv@-R}+dpi1sW`yLL%VqQA~l?X}-$5=CdD#{%I53x7h6i&%l}p#VzV5h$_j z({2tBI+%GhtuGrE@)1UHJ&}-ezvCo)BvqB>p1-4{#eQaR(&CKpV;8LSNGSh~P(f;V zf#sGo;J56vMj}QMOaptWvIYbG3}4vq31w||lQQB09{&Jbvdh7Mt{YVG0G#L{zg1Od z%gQ<;k&aNP1G^2&v;XF1%*<1Qb%s+zUD0Utc3s14gIc7nPuauJicsHwPDHggjQ3~0 z9qs8&dw%b->>(Fh4I>L@CE!@A+PyyI@GPe%+p+)5)Ed&$^WKbRz8URt@NTS=<-nip zg)SJ(xDCfbyEzH7{Pqb3LH~jNE9rT^jb{Fr-X~SotD+zZrD5*&};BvphM;_DRL5?E&2N z&Pu{=Z;)#kS2DzB`2vC29ro)a4zP!@j}vD8CaO-0j&h`ygp@?9~E-^cIq zhHVE4%&<{~NyAo4^IIL$LfPDQ`Jpn>FYEo|W_iY)+0-8JdINjAv5Qek=;64cZTGV= zZ}#jfonUQSOM81qOG|Uk4fCA7z^WBFdAZ?mZYT#$I%h@w)M?Y}Yo||krsjfE7YGLx zz^<_ZpP_B9uAINBp~YIVXijBO@uE*JSh9Fd<%0Q(=ad&MSTrj%yr_8otUOE}Q1pqz z3j;;_@jROdMaK`;&vJK*4NCRJJl_7t*;8o-T_fDb2Btp+jtBipxKVHcIQ|Jw(P
-^?2)7^%;R9?C%aW`39t>a!e_f%MfW8gvH( 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 IixPSNA7_b|od5s; diff --git a/components/esp8266/lib/libpp_dbg.a b/components/esp8266/lib/libpp_dbg.a index 5aa6b9226d1d4af373ce9303fbe8e7128b566697..9ba53cd420aac104f02a6bc6ca8fbabdec0068ad 100644 GIT binary patch literal 256458 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
aww`lkW8h%XT-H1u*8=C3B^odJCwG5Mk0^~=a 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 NzOM5OGdo4x{|EJ+&=CLt literal 267294 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 H;QszELU0UM From 7c318e9b555fd9e5f064dfb4cebedc0b9f3a72b1 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Mon, 27 Nov 2023 19:31:07 +0800 Subject: [PATCH 41/59] fix(esp8266): Fix compiling error --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libpp.a | Bin 238578 -> 248218 bytes components/esp8266/lib/libpp_dbg.a | Bin 256458 -> 267110 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index a1918400a..8b7e15491 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: db51cd0 net80211: 58ed843 - pp: e27e66a + pp: ff7160f espnow: db51cd0 smartconfig: 3.0.0/283eacca diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index aa7a91376a9ff4576c0f0e270c6162557c778bfa..806d3ed5024c817e5652c2f5ef4d02565d36c2a9 100644 GIT binary patch delta 64134 zcmc${dt6mj{{O$%1`$1ntB5zsbB+iH69c3~(*h*KQbRMPvIH-rWr>#(8=Hfd$jT-Y zr-NlPR5n@p>855iHA7}*lT&6^Hr0$;Q_f5mGn*`d^LxG4elOgeKELmOzwd87*n2(K zYrWTXuf6t_vyZ=?cwbY}+J0U0^NP+HI<(-Kz#$ddnQp27ZTWnHrT#B0AybGw`*G|4 z+Vak?E!$Z_2L6BYXLr`*|H}$A-QTZv>qW8e#3oIypHy#6uAeY}*6ay4UT=kqA6DtU z3G?SJnmu{$^|Kd_y+Lfm%_rlqP(WJ zdjwPOwJa;fvI^l@R?u2w-5Ktau5x!=ovu1m_PFb7W=^gzop;@cfwQb7b8F{XR3;gv zu5QRoG+=U5$&g-M5|BYEXiO;em3`fwdE>zk@A=pA<27B?SCLlB@_!mpUlP=RxL)(D z{~r-=kAhNbw~ch(9=S2dkYzQy~?tiZ!Z$13$9?JHC$J2zjOzVjv$_9)pCxym~#!JDe~Gd`{h$}4#- za;S2#7T==y+~9-iuY|*|MDqU}3GB3e0ssC)D=^yfKOfouSA}9w*!~Gt;L&~RRa$=V zP$~++zWWM8drMU-egBq7X>yANTmPdG^%5oZQaJwymF}&!&_Aynvuk2hdsD;tY2n)5 zD%fk)-40q|LvQ;pg+fQZ2#&xr-@TL7!!Nt4#TEUo-yCN4asjEBTtK2@uj;-oZS#_fB zverqjo%Gt3l_zriamkjIn=rg&>GBN=)LpDxQAz#s<^^k>%}r8wFziigToHb!p9)-X zLu5|k$F&tHQ)adK=SQxX`eWjxtL-=J4^zv(iC9)aH!IMx#y2#%v}N_`b2IEQP3eJd z%I8a~E$ABPc4nLkpPiu!2kw49a&ghcIo@5DPn?TVZ?8eQ(^*c{#5pK=bxrevDJ#OO zGt`-rOCwuSOUfeN)XlNar}ew&(L$9Q^J3uj_4Ws;-BG)7Otm}5${!U8oM{&gy<&gC zt0#uNJYi4y_%1gO9~8WN;_Wr6OgAZ`YGPjS#GrvCL&K*sRNpgmBbAlgP*v-)l*&te zmwQ*&6!@)zDo<__k_-(OoT1K{z5BNj6f%(M4<|)}iEB#L00#Vt5pUew#2HT0cc)mv zl*3&kA0N++yL{r(<*FZTswUPi&yVfwqL>eRl&@lLJsGL9>Om@L+p+fy)o80s>}J`j zddIJPsy0T&g^vtS`N|hg%T)eur9-+;dg|4JOts_uOcih2aSOv0S!!=$Y7+KapS~--Fb%usC25U4t?<*?>de@! z<=w6DJJ~8dTW{u|8s$~DGdr)1T}s_XTy~_~wu&AqidpjAuHE6Jpvu~DZV&~WKUsNK zyk3x$HE!B-wx8xR4s%O757!7c8gs;Ox3<* z@4h1ZK#nT!I|g0xl($z^>9Uh?iRg^qMCQb<%NjE5a$mSDNA-)Fv&*L`rWpgM!jToBp6!|7*y0$Fk{f(nFTGIM-&vyI7>}oWT3?g%*xjER|ARGI|boioux9;N@;pl z0JBzLyjA3VCm8Vu!%v*0{Gp&ahjDus1`=bt&aii9S%E|~^+>$R)5Q$1)Y*jHK8(UC z|Lj_l7RjsJUOYN^O<^#@S9M}eN$uz7#fu>(KGUfH%IN<@;gl&h)>~5eQ67pOjE^ zJUu2M==G-g2FDf$@>*l8xI8r1Tk=B3m>B#0l~&CkJ4)k9YSRX-Sr^#()|%4tpamBk z@9k1*V|wjqC{w{eaYg>91och_>Qf%T-u75Wc1(`{iH?A6)u!5w{a|17Kt%^q1kXhR zZx<)QZk-_AvhpA52*gyUFN_V2FB(;4*WBMx`fBgW*6r7HiQSXhy5~f0%M4@ zea)Ts4pF^Aw{`^2#ooP@Qf`8JmcDw1>-<|f^bi=#A@C%XlKKt~fx#RCgE<7A<`BqL zn;AK&#hQd6@CWjgv-ibq>rk7>*yPuCaFcV3w@`7`yPxP#zv~D&7B!s~_tPTokq&jA zB)+mU>Rz2#uXk_k2&Sm@+V;K<)g-o+owj#V@lU|knUub8Q!q6>u&LFy;_mKHcQ9u6 znhs05rVsig>j@Rh_KDod#hBo_yKIXz0Q zjsyo32a>&u862>?5BIIvOtJT&z~JtIfb{`(hg?h5gg-7&eS2TYsIP2w1^wFJ*l_{u zqe@K;Cl1BVulGe~kM!C(8-rVl5`w9|Kw)bjWzc}4g*#_j$7gg^W0|&S&BmtSmeQEA zsz5Nca@Ig!er#oBA#9c2OH~Ptf?F_+1Om1%bD%n(LR@#Wd6W~yyuFD&f9yWyUJ%Is zSLVhyVvf(qR_D?nH!*&}o|=ObkCvRwPkqIga4bIAYr%#04pq(Jd4;NXc-IdqxvAlz z`0(I!RD5_^n;pMnW1)&yI}V(qHmPv(65Bf$dz;0RueIWHgRR3dM8d7`6&C8RU>S@B zyO{MAmQ-OJ$aNmHQ84Zy;WY5|VzWH_U_wlGc+>eRFT83P4qnw)c)N`f!-?TlkkM|XjMpf-;KW0%%88DO4~2C(FS z>6j&%X(5hRvC`1AhUIp`nq&7 z2D8p5)1{M>#D3^${kLbuq=b(ZtBp<1U+N8+LDq&sFP2Qkz?k+dbS%IJpW(S!ali$z zECZYFx{Hx;v?ue7pEm1UI=M{r_rW6^4q3_QUhYjy!0neIT%@;wlPS3$bmn&{*!0w7 z@Hnw=ap@0&`-_g_obCnY|2CM86(x7G1~{1>bVZOWVZg$I+}`0AO4XRot#%<=XU141 z80Yi4?w5njR+tH9jx47Z%rVCDSAtF5SAos&xfe`Fd$O_LdfGl@Z9~8Z1J>&Rm>F{U zFPIt=p9Hg)86FvTx??L`JQ++!d$J!s?QeAHWOg|7U#s15M$-^O8w=^L(#kMZunDMO_Su{x~Y0dy8lJ#}eEz;x8fMn49A0vc;{KN8Z6 zHXFelWXy{Lj*dFnOj=)pAAlCXayvFyUx)iz4M;P zQ3ekn%mVUt2IpDNO89IY9%a3OFbf=@!xvZwqtbs96^=xOJ?!eHcdzNEhJ!= zhQsheS3_sHtbD39+PVQ@PJv@|_(E&7gs1E980&$bg~wWtOSnwOUu->(FsIG2Iy}MJ zgRpVz=y%0_C^*%s3|WVw3_1|z8N?@;K2oh&Ru>F;X8x{DFv&_n*f?f*MpQUIDm)=7 zT=TOqwn!a>-pFVrmQ-u9wd!XEmbD=&ybWRY&~R-(#d<9&{t&`ATwA;?lWJ92N2B6@ za)&u075bl>z!_W9tYieZIiAuPR9j~j-m5RO#>>>w~D| z|BMQM7Zpy#5vT=e&%@A2MXr0J_K9@oXvGod6zrG7mkH-0%-LZNdYW7GT!fhi59$nu zk55upb(=nY*4&#G%^m98kWT&O4e6%5`F%q37foNxyVNjVRI~7UtM2;g3u~v(T{L~> zy!ne}&$665)-&hKo_XE$dGqJjS(7?%bkAN?2kDyWOBT*uJlk40yJpt(IZK>d+_f-W zXwAbN`y^JF^H%0A#U5l0JGz%LMHC3TBoL*_7}J#Ef+F|$y!v4Ue9ZDP4kFyN7h%O&Ct z`0-d&Emm+>VqpSmWRK`Kh+Yq$yO6pDD>#N}PmSymox7cygB9E=EK_JikpYiH04+6_ zYo|BTx;Wem9+~HBdfS6}hQcs)9=Lp1)R3muCA~s%Kse6DN>~>!tgFilT{zuT9;@L{ zAxq8Es8`C_Z>_}$rEyro-HL@w+q;A}!snb%o%w}cmH>zME@2k>h0)=ECCrMo86Eb# zAZJFsq4gE^fzJ?TQwIw(PkjOdd-6<2*VCQeuN1cuoZeV4$`tm99zXUuF2 zHL_=kGyZg>LIj=OY2#0`2YR&=w9IbC=rmr>!Np=R12Y*+of_E;Oxi2ijkUe(t{i{3 zL5>w1FF?~!It~27@SJ%QIy3QNF@+5Q)1DgH6vl#0uM~r6Lyc^DMOOlIOlSMmuMi8z z6T~!L*}@IRLZ&gNFfujW8tzkYg;=OlBYQ*#b{-r!m!+|maJtgqq2LUb&j=pc=*-cC zTOut%OI9!8PMhV9o=QFOG=AvB=m+5EVNs1(!Tkjb?en<*G2oGiLa;IVT#TrZjZG148nA-<8VfU` zM)ovd1;=|2)Y*$UE*^CS`dIi}$d|y^^ENmRT{>0ttKjS7 zI`l)}Vx(8MF&CMVp%)8#L~oF^TNL_#r3BW%hoB4F2LCHz-i$9p1}4))6wV4!Bb!Wl zSD1PZ{0iDPUnw`JsWx8fZO z7R>YXbYb3I?juZ_N>-x5sl=J0hbS^&x}{Je_}F?f6Ws137%e(AvPbkP+B7&ecZdx& zvdQya(Ko{9oeox%1M^QYoc|Hn1@RS$V0TS(Wx88*YGjk?JEC)5sCL=ED>^l@vHwPN zKE{~UX^-vygG5jyn*?(@jZkAGVnwNujZKE=oM7g;?0Gwd_SDG69zjb%r{h63EtITT z2Rw+VbAp{m!u~W$al{PaGWch^82NWjLHnOK0G1^dBi zJjTMEiG_Rxe0}^D=EP)lU5Q!Jq2j}iaq_e0~Bbx#`yAE2O*ia*z0y4qoRNY6e#|n;nkZRJXE2ZMF{pobe z);Y{pt6>gvZ8kv}IE_t#n~#Oe%54*7C3Xn2d*2aee(wvjoX>=r)fX(E8L?(R3H#u; z3$rD&ggM%SSk!N^f@{UXwA9ER(FcRoy;#A$iG?=Q$R5$zTPo~~KkFkgqDb~2g02vY zYS!qSXdokM`b0zYBHD=k2`~$zMmG6yd?=nO!`VEn!-g6-WO*c_7=pP3ix(qmWOE6| zZdI%S9J^a_bi#2nDDLFq%;w`+eOezUevmNJbxeE}xJoQ({WMzD!YAP0>~Kie^bZoTAO2Iq%=~3x7J5MVzwkLonJFulhQ*wQUnDv; zvN`%yiCzmIW3Tf_cm=mVGop9}7IP$=1E#(Jeg+nE=wz5WHL|f^BKl+SeOT1}SivEC zZSNB{<3?{*&|+EPxu>(}EQow*+;zK8VG%m<3X&Mm8;eUUc^0PT_;_ zISx!?_JTu9OpyW8nmm`$=V01UBO9AS+B}FA9OnbtP}6gR=&=Z!*%N4~EUxhg0!tdZ zkR5cf1Ze!Q@DK1$2#?49UyVf)ST_sb3jY?D&hcV;MGra@K#gpA#Y>w;tl+r0(=yQi z6o0Ypk@yDa*J&dhy-<)IvCzr)iM|lMZvMHDYQsLZ^?o}+bq4p}bzXuVl zse5GVc^gL5^t>&#eivaTW)H#ZJRH9-IyJI6TKd4|aI7~I45;bNgu^t;cLw0CBwSKS zmvR7-O$&kun`3{P*ia*z!Z?mhVFhACjcg9hd9;zn6mkA%YbYY{^TG9dh;U;hBMvUI zNAyb3?}C1%Fvp3$4~RVHf#-@|2^)r47RSwE;aik$KWc;sy6#xg&d{jY1HH%zc8+9j zsx+Q~!s!t%72>goIyJJXD6li_NN`$z%lb9^I&uhsdH@AR(CK{|{3O_ig*v-h7bcrY zpJYO(rcW|OXXVr;tl-%7OiPXI5nWdl`W9#*PT4dX1b>V$BQ6uZ3H}Th*9x=JdhbA5 zvKNbzUSXJdQX_kyvl8n?FNVLt;gGJy0}{c4A9C@dE@p2q)92tPV=;s1Y0;^X%>)JP z?Di^fnhyircCIbSbgy&#Gw?42x^hr}>){U-=1qk;E?y_R4}Pml=L<5WXjcn}mL`MP6EGhP-qH}L2d-!OP1?UG4K4+4+O^nWk z52>@T--%9*Yzli^biUsB58->^XELp9j-N!QMmC!x$nCHGfE8RUTF1>16!wTd7-5qU z2N-RrkxfSWE*EsZOUX$_an}JC(R&;#xI!$7lM9@#98LsTzU9NBHj`xA=Qzy4qcegZ zPoCxIv1H~f&YYC$&E$7PJw1b|U5_PAc$te=>-irM%jT1%!`FAfVKWUmy(c>P6X87A4@8&+ zQ_mIVrsFL(>fG*|g=rt##nF@Dhq5HV1SP^NvO*7EN z6NNb#rwOA&tcAS(X97mt;)=M-m7v+BKk3q6aPcme%>iLn=$|f|uU%Y>IQ0Zp-2aB4 za8`mM*~199LeW{d;ldZdA1TatQ%i&=!`F{n{diyj=9v=nY=D1}Fn39wO;hI^#ypV? zF)$5qITkWsU}czG2fs%68u+t>`SL5nwBf#etuWuBWg6-?!CxfIw{97x&i#L>@X=C^ZVN(`+Db0`m zr^tXQY%-0cuotzFb0?f^3Oguz8+?6?_d65P`7$5yNm>>2*@6{ZrRdbi9?`Gh{eRY! zvs10`GWcAm^YwCL!-o)@q^R?)-oFZSW}JqM)e07ct7Flrkv*bUBWzmFG|VSNkpa_s z{onx+e0VU632L!|`%V&2Bb#&nIGKp}TKgQtne%^!SpYS%IsczW8|ht?f#W}nD6&A) zy#YzQOJUa(&V^sc#Q%d8oPL;ywA9ER(HA4E9;OBEf=XIyWRK|e2s=H2?ay6-m7qvA zJ+X{NQqzmXh8o#a^hVJe;WyBpCBfB;-XLt=nz%>w2jOoabN)XFSOr7HQ(U;G#E2T% zBl>pHTj9Ux;+KUv|Gz5yHvH8{uXsBJj!#b5NNQw{=%FuK{0R&X}bpsbMde<}!gB*N=N=%ZOP4AryR z-s&$pHL^$aK`whf4`u^c(cvy0Ej%7R<7hJj{&ao*hX4209?_40_59CFxvA9-n&Sl{NkEP45&b*S*;_vf$Aj5$^*mEr zR)^@+$R5$@nMb(Yk%pT9TF7F+Ji~p7MoCz~@qJwyQ6rnDV|_*ExxxV93*iqI?gBmE z#TNySNpFs~50>yIJ&BVUOsyiOv(3wZi`=hY8Te|Fzp|)g5wzy8@5;2 zBlN(QW|qtly#oG0+Sg(Q*I#sMWOKYI5Pc;4 zk6FGsU{GKI5cR+Em;=Tkgv~U}LTE#cY^LD}qEE(T^BL`L#R`rm9<--MHkCe#u#+d+ zUn@ow$tKe;X~fY7H&1M+kv*axL)a9?vmX{ljcf`#L7PUb;BFHeYGhAHB2Gdu8S%W1 z8BrsfjM``;_78~-HL|I+rC^f*5pJ{CP$Qe0S+TTP=GcU`iV;P!M+5*un{Q=z|!&Xd@GxHy?` z;5Z!ASsID9m*K6sCVvn3euc zI2nEbo0<8b0wF730#)!w3HOE(UpS%>Yk##cGg=_*1OHYSX4YfE&%oa*{5^cWA;)~k z9|{LyuW!|4qW>B3l|*p+>Dw?!z+Td~Rls@Rk+zdjA@~O2B5Vi7BVmAdGsZ}1^;<&e-rq#FuQk`i{BMycNSwYkLmv*IyJI+O#hYW?DAv6H=}{4 zgfqYi(%=vy`bdCW1|!AN;j%@iM)rtafv~aB?}}wQmt$mOGfvWSvyOAw>pN|*rv}FJ zU&&P8!$-tCB+&Qp!RLbYJ$&K$k|`@%>B>~!&xZ{)vdMF~*xv=eS@=2l4-0SM{eKGF z1RMu13m<^L)1@;^n>YeM4yGQIYxx^g(i$BlFdP4l_cH> zU*92v%_i_~MJGQf%!|=E%yc(aaDNb;8rdWI7ST6hFPMio*rWg94oU=l~(Fk>renS{brr$nJ)VV+H=#h{hT0JBZ_2G>s1Jw)2TACp+@%90^n9-F=tS- z#D*H#oI%YKo&WK>hW6+gx|IlrSTIEf%s=+5McDKN6VZkm+4RJE+Wa0XxckJ08reMg zYDU;JV3XKTBTEC$axS~BfeN=vj5Z3J7BEho8}{9y&g;{a0q5SLig!2E_gH7nN>J`=Ic_ zBHRfo-Pb)gAmnj`Ote@D=ec+!+3VO;xR__SMz3};x3STA^TF^ca)Oi3S{I+W(G}S2 z;%%D4)px7&_-;B~8ztfR*wf4xA&u^H@n9Dhxwy>5l`iHLxyfgaiw7-s1sYtu#>Kpn zR!QN_BkeIYQpYb{(I;KZ``)G;UcwsAa54W2)aZpSF4i3Wa_#AES?!8m@8T90Z*lSS zF5czheJ=i(oa}V_Q5T~i(hi_9?eY$Uc4^+k0v!Tp`Vj<8FpdSFC1L2?oY^Yq)hC?DkHpaJvO*V z^R(nd*SmO?i`Tk%ql-7Yc$6Zqppzd_R6qCez8T60@5U zp622?E?(^71{be!akGmzx%g=pzvSZ39#`Oiix0W@OBbJXacs)zN+-KG!^J@t7rMCE zVV>yWs>c;k<>FZ`u5FMXmL5FvxR3VV(1okn4cP8LICJ%M^ zD7nz#6J*{_p~Hbio$H{N%v&gMy~)GFc^BG)!dE|_`i5_PKurnX^6QxH;k zl*bN#|9f?5IP-l(kL{+;43FrBqxChQ9e1~=^>*%zMT>^i&0aWn{;bab*~d=<%)HJT z!k8QI>$aj(+~I#%<*M-bO=@De;{z2`wN01chbEWp_-2#3TiG2}xZw|Kf}N?FDg#+| zspC~S-hAz;)#3S%s!P-p;Xl)RFPyYlEwL?|3Bpfq#;=zC`*}MU?f}^pT2)tuFL_Lj zX}Td0Z!fo-iU(w=+rqzpOl8}5*-dK)WQC918I#uZ(BIOA^Z6W3H>_fI4qRcmbCc+H za26KE@sw^G>=b+)3bmUS{{7>s*KpoHa3?QDa_%Th&dq3&^Rb^hd2{$1Cf^g4d=`>_ zCdmi6k{^vqzA1dc6G;APRPu^3&i{I|^uey=7EcBdHkB_9-;Lz8r=pUVRp5pR%$R(L zD|vTGF2m2d8tG(=!pg~-+4I85UCe6OEmz^{yJFJyPmvVU7_I^3@Kl!NM-SL=ZY{im}jTj62=Q+zm{6eg2KGX=Y|@_|Ow7Jv1yTeKFFTc5-(y=`W5d zpXa_45pNvr=*pw=TZa7BLT7upo0w49A*W#15XtN3xWQ z8GwtQ;N0S81xv7){2qzQk7sVZomrj9PPe!vD#OK-Pq%oq*s;Z}Q5o`-?^0$Y&eWAZ z;ODMjGxDp44J#PB5GzwdKC*SUnCFPsNoMp+hJT65F!{36V-9&~zFm-#hK z{HSNB1?g8I9V;}}mA)$qbf@PDXC>l|Gxf0n7eiwlJFFjo`I)(6GV()} z20(HgBe`54hGuM5d&0+_!p=VeIx{QALbnkG9fZ${W2<)-bVXG9J)Us!(@1|=RQknJ zPw#AO81rUD7e=Lz_lDOp{q<4l4yVIF;y3zcVVm zHz~Z2>G`}Gf1O=jc@=JgOL|nLGyOYJ>06SWd3tYD`eV~i??lsF=?_Jve?HmSR3Am9 zPpdvXPxE09qV=ZwDJuQRRgM3cI=;Po%}qO>Eqw`54WH~ska~^9tI}whx#{F zf0~~9+bDT+|E8xQKk-X>?LX5~|MW|lnH+%3jcm%?+&{c_JIXv1r9bZIBfgB%xAZ?f zK#+~ji#{LRyYqdvfA|pdjYY%VZE1Dda_0Xkj~EJ>xtNx>qBK)emST8ZRPvopuGd5* z7kNpP^{)O+#pt0mzmUTXAE{m`zmS{u4NOmcE-G&)lTmzs&^>sj{mR+6+}O8ZF&lel zRIYo_vSUq6{~TcN(X|=z_y5HJJ7A|{x6tv`OLvFv=^t+0!H(wbAjIa#ohipw55DoT)ox6Q=(7rW@WczN#V%j%2D!&naOmeuWGS1|e z?c|qk6-&&CWsV^;ye)jmG`n9y^-vtdv4`9NX;%1>p7_D~V;A5ICN5mpQw`{HH%|Au zh6lf(2L9|r9XoD#LG{Zt(hhv_F-iI%|j!R$e{>ch>1k3W4tc`4?(@@dF*b%QjNv9*44j> zg!A*%z^2L}>+xIH_yUPu_s7>q*6yt7`$|LPcug<6rr-XWK1XVL2fSAPuOs~efpM1q zS8UiE8>^z2x%Uju&##Zv=Oc77#v21~xV>Y^rT!Zufq*qE)o!&dqaCe_R*U>{wq+PLbs#5SFVh}a>_%Ne~MGFg)=PwRAiQYyB(Kn4g1k< zJj*(EuyDuxw5$hG@+U_86CHGC3M?D+gE(!sKnfa zv4>RyZ7L3D4Jr8V`CZq1opolvxHKzk!lc+@-b;`ibKlyx%kzr$@FJ@qY-8@z?#_BQPS4o z9i36CavAZ7oxZN?oeRCYMkVETU07qOAXWd-j*=4{x(3<5yQC=B|0O<5kW@4(&o^ty zr}lFzVl%$W-Wpf(Wk)$WQ)M8@aLeZ_vk>O|rRCq#A??pm{ypL3zuUdf zd$r?eYEhzYZ28~&Sda1*)r%Bz`j1NbFbNIa!BEMnj{KWD@DYc&pjDoO9t|x- zS=f5zS=Kv?J0{-JF+M?Wh5vC1N)N=}ISE@KF}I6)jM7!M|LTr8N^gUbMIF^gS_2P% z@x`-~iq7^|V`KCTUf|1{wd6?re^++-;&?)wvNDbjd$vo-+>S<%EWWho{=!j9`2Q{c4@X85Z*(npWhHSl)J znYcmywOf0)+=QcNs9Twq5efN~9eH|=4P0-j23LXScLayj1Usb2Ap!grVS0h5=nSNq z6ywD@5aY#%1==pe_+HkvE|8pccX zWvy#z_%UTei8nPr0prHED0!)WZ?E0|vEy+2D(~Ks4Jm6Lt1qb=nmjxueNpU>Db4=$ zoYcP#P*8-uAq+ZWu1J1Z556C>@#zU@_nC)G?>3Q>;(m#)zlHKK4AZ6nB?IoYJbD$P4J-nhOWy7~>AWQnIojxh< z&Kcfa7?ssozPU>ID3`Vb3*Pbu()XsRGZ zvtND6Na$<6ymRCDIP41DHYoKEigWzwjY;X#&P}RMX;w?U8!|?DF@is2R9328KFlh4 zs6GGAc6@IGwZ~xurz;!^RWX%S7q*=f&iT|HT7G%VoQZE}+YgC;y^24g1P-2Udf;Rippb3zxlA3DOs|B9)f z=wHxYad=pGYI%~Cy*;>_M->0O_64)sD{TLD?aODi7ufbaGuw|1NEs10gfC)bj^f^G zT@^cu2jr@lFTS_STO)Dr;QKuGX`}2>>XXTlFn)9H)u-)4&7-&xj>^zZE!rZzf zY?$cFip3sRhhsq?W%x5W?=)_()+JgSYNp&i&>w6M;1fj0GfNWMCp|R3pe5G3ll8np z*E8iRyP!p(la_o3PYepUKV{iK>iRoR}0NJLZ}_Vx%SW-P`) z&Mur1TvO7`8M+g@VVJr{Z!AY|pAbHZWQH%Qi%+|pYBOOB=QWLsR7KqKzqj2Mo4Pvv zo>OfNr73GyCl^=v#-?L;Uw*R9T9?wWDtRr0xQeXhAGOuL(@>UNSLPd=)%;A!w{2?! zt>*9ts>Fq+tv_BLA)>1my_uKtgXI(C>U5;lk>`yl^&EHOj>@V%@@27Sdr8( z;PaI=1e9YADJQ8h(`pH{F8HJk8DTa=UQR~uw7t0+1M8i(hcH?Or9AEosq7+kS1fal ze`Rl5No$*I>v%2o+ki@eLOJod@fhltA4ab`^TtnY=t&Ius>A3`ObpH6emoSGfuPm8 zGuhwN=G)6rzH^S{-`=)4P_C@aC3hux`&1+rqsw2k@p&`fUs|U$#RXGF#d&u**9l{IL4KNsU9{dn8osjgt0wRB^Xuk?JS*Te5WGE46LS6N76GElp{DkPWqO z{#X4(-;nj$kJ^n_N6ME(O5@_asb6(nvFoh|x)vX43N@^2TKk*iBTsq${n?>~O-NGE z#oO>j@}WbE@jb06zw2EeSF)mQOxI<3#f4dA)$NJ3tL(<%)|71t%VP0KsjLG-+wH8+ z?ADzQ(x8aO z?$+_A@_zbmeBy#BZO2MOE8AmByH*!Rl0Qy5(zV3%HL8M3S9~?75$pCuGc?rA?osjI zNjFWople=UVeQIm{oUI#J`1L1ti;*=LOY?ZA#L5B_O*p8OKtDEmNje3lGnAY4zITh zUbUO!9!zLkTfF_zZm|{V#T6%NgSo>(nS9q^72mL(&b2t!i2&v>IE|V1KSE-%ka+9BUfF@>Un$U3BdR1%9C;a?@nbW zbvv1U?Bof~tk$t(+XGLv9e>;7i*Vc{n`>s+$KMW}^ml2?k7=t4)W={_9iPxS+h*UD z)W~fGt_;+c{s1N<>UB79aZ>(gh#;@(o2D7kd-G=waid zUxAjc=s8YgLs+q|Z0t{{TY71CCQ4ZF?^CjyhZ4+wjvX3%Goo`6;(V4b(yFunVxe8` zv+numl>ehstGlhU?KjJg<)qxlJ=_;;U|sb79J~B4r~2(}aAxz^;!Gb(FRe*r1(IaL zRd9Fy@?o9pJ%5q%SBGLv`Iy#xL0x`qxk6XsR5WAapH2k_{_w25`=iK|@4WS@!!>T#%xpoI-)dbRA4}os)2~dXRoWATSpglJial(vL<=$V*^vJkg4(IlsH_dw@WPL(YAd8P6f{yhADs^naEC7)&`OTBhc;<3z>ivpo$KQ`r{ z0VN|&{jedK2l$u|)6`}fHR3j!2)h$I=H!2HQ#Wsncc*$tH#2|Cl9c?jPx;S2Rr-gJ z`W>~n2(GAKUz(bqbILym@+!4KWXtb6<>j$ptaIu%Wk4jq=PCTpqvw)903QVHcM1pL zHS7GnPR-%<{R~{4C!MOl2e{P6gw>pqKbjenDNSpmTb#ljF+iZ^t`_5C|o{Z!z#okzO&29y-WQS`a zc6JIM%rL4H3vO9if$;M9nEp-2rh0dL)^2BXxn93l8N`iG?^^hDf~}=SO#vKNqCG3q2IloKH2tJbG6iVh}8y;B5d%&iE zH^BL#^U;WDO&26I8-V|SN5`HcuYqr4`2+Z3_{@|4nno9o@^yfBduYU4+jLA!Hr>k` z$h-~5#5Ld{!gqsB<@lmv4Rq#X9*Hm?GRtGy|AO(SneQ61d|em=XM>seEDTQiOpptH zTKE$VS~zA(HZ49b_S8>+%@|6i07s8(#!#QrddOm+9}HMnA=tF$958!@dIi{w%mdSw%WuH8@LI4*`v=kCS(~v6vi(f34}mF?AdA(6qY>HcG$X*K8!rI&MLg49 z4!%NoE7&xEuQ9SGSdo3;BC(0bGg%H^rtKR-zznF1!8B&UE4(!nA9GjU9CJ%>6RGa{S)^#SlqXtdEA8y;=Q%i&R9&Orc2oqQI2 z>U`iwN1eO?zK!J!PRwxe-2M!h33CXT2279#Hrx23&II`XcCZ=w)4`^DtHEZV-Q=<- zn}K}0ODCss|6@%ba7B=lMgJd{PNprFx51|1ePA;%-*f5jgV`9`^KC>tGSoZ{nNw#Y zLlY3Fl!)KCB3i)bivGS!KM1~2^zrOAIA%mP-Chr#42=cvb=i9L;)8b6#^ODDeDf|jypgMyxYA$0`;z3n z2roI}6L@thd>{=U!aKoLTF;oeaA6K%P7q16O+ng1-p?~V!Vjr)EoUVX~@S$MR5$qJ_g`pgdF>O%dS zBkXq?rdnh4zjSfC_1EFCoO|@6&cX2cfvDB+jbjDw!@}uz9hOv_I&6b)9OF6F(w~kc z)f#W@hi{zz-R#dy2tEyuM}@m!OH;oRODf*5;XgjIz^OVs*~;P&6-T>aQDOdY4o6H5 zmQ?IJmm^#Vw)uaH{Onp?RDuSCgD_aH6I@}P%^@j{1@QN3*h8FlQ_+8qAnXIz>hRV2 zTePR+tF1jhi=UxC$mhhPZ~5W6FJM4NF9pA+a9qNyb&B;zXP=+}94o-D*>W3l;U?*a zFi*z2Vs1#auF;={K$Ld0KR2oY>X!eh& z@DEYpNK}~rhi4qqXGMj17H=HlL;8nZXgC~8sx{Xd2LraDNr$hqrX$R8`nnEZufIqE z)Q;)+e^l)G0xYTc`RCsvOr3{{R2+97mGrB1c#-vzgiF!6saEJl>m3MmPS3w&{S)D8 zgwwUb66PLsn$}9KU2@KdaVxs${K($cee9%_!euJ#4p!j{EQO9tWc2-FSjNm zj6eNpRqU-Rtva`W*AVzmRGkLaZPx$%EPT857Q)kz0sm7t)wu1(2ZTcW_`HpM)$6TvYr_P#v)8x6=Us79p>FmYUHPh$L zY+Bp3SNI)Y%&zc8e~fQ<-JGSDe)^0XYp2)D zn>T-^{^s4J`m*^q%+_UGUuT6M{54)npOx$n#|C0@x89#zrgq#hAm&l$gLWKgX=o~| z&h0^_WX5xrBr}~)I0!%A;gG&Xz)G0U zOK>|d0X4GuyhJf|Y!AB8Sja5kVqtC%PVdxNSgkPgsTbz9yF-|R>j55_n1C&NP9l8p zcL`_0KP=2ze<3^={&C?v_}>e&wfyM{=2-;x39})r6#r3@jw7ASt8ym-Lz+%U$na)Z zozJz?(li-m466&cmM-n;a5g;t_Z-VOTbPCF-dzKL<1RrPYUGedBG@(RPORXDVWAN< zvPX1gYzhW;R*spau_QP>hQPGdV+hQsEG)EPPwTSE0i2H+sqWDMok)##m}~R<228|{ zY0(v9A+rFQl9|V3;Va-T5WWTe3Sm}qy)Y~AJI&nwfHNeb7=EUUdAw%<)bm_?j*CYL zZ-!qjTm_$nGwqG=8-!`22OxB^zAIu{%odrR09wlFB4^j)TnEPiX1)woB78CYN@4Z{ zTWY>-pt~JusgXTA%)#;K#e70#fNCu4Y9=lg_Q4-4%pM8~b5pQ~XwO~FCk#)Ask0%p zGjjven>izy%ux=9O5s<3B*4NA|G)J_BW%p)!!`)h_Dx~taY&dg{#2NS9ut1SMmgLq zVUWiCU+=F7u*M}4!In)F_JMB^X3K9EW{*56JQ)0pa2|M{FgMo`;UX~4%2+VxgIH7~ zErdWZ0zn4AW#Cc572pen$AK>wt_0TzPX_Z$i)nq}#lltK<-*gz{I40>tc8D4fG?1C;W5TTIMl94h%gw=JK0EQg=+wyOvlD+8o%ZvvsC%)3`!^QmLye42 zP;makK$C9CLZNjCo6ksGEIKu^`HaM3+N{S4Zid)UBYQ;W8957^2EQJ0CeKZxQ`^K} z{Va~bKMkws!^WMi{RdWBsW7G@8|Gc9K$I1b4FZ8uJaZa!s~C#>gxJ%QlFU<|}@ z62S~ne&>E15S<#??8ieyFNRNNDlrgjzKItUX2o)anLnqO5RI7WWQnMT&zh?Hu!8H4 zg*Md49?{dl=Gz9k(y*aMHs3a=1~V-O<1c!}=`Cme=LeGLI2Dj-e5UYV_*|&_;b&km z^F(j596NM{*d(}%v818K5%|@@@boz!b>`2BFfAF|-wSwH2b}HrmM~j)cH2VRI;tcj-XP|FX?k!?YBv;PlZ6%w*(8LSv|7pUneIBaBQ~u=9E6qLVMXo{Y9rOw@+G6 ztl)-XA=75EFm2Fituw#SbP2HUt`TOR>J96zJ2Il?)ExlFbAJ{v9{wf5EQpOT14lOu zHqi~!4M>**01M~$p;*&i!fX{2QD?1Fg{g74tIYuT+!e?h;I~nF3@f;kShNlNedI^6 zg4>J5Od5Mwz8QgQ7*R$OXD2nMFAWH*kyPbyCPRD{!fJ$;^(>>#bGV9niNo9+=BFzL zyEv1)7Ausm=S{_;#c(Oa0kl_Sh;sZShW&)&R5bSAGAX3xzP z<|gKeLx=@1qooqTd#HTMN}UPxZw`Z*;C9h{V14+LwD*Zln=QhugnkkW8@BcpSDt(- z%lw0E|K}25_xwvZ5B$9_ef{hf2@0X>p8*E*(dZB?%!tf?)Ft!LC~xW*u5vN|snzI< zHKYHTfEh4?l~^XsN~{*9zd@KoVWV&v_z%Lx;3tH6*N6Xw$vi8d^I0r;9GEwF$dzE5 zKl{o+F#^0IB(vKygeQabkJ&=60`or_X)_HxQMek+pU$B^3#@;S7CZ;MRP=e^jlyie z?}bBk2yBr6GvxzeX0#amnlK*#zb9M|{RiP?V8+p&1@Pf8nJw!jd`?$vXJPh4p)ebU zUtz`1J|h7Ou9k>u@O8qo!1`BJ#pY|#c}GWKVOla5auw_c3O9iBgjqTMcNA?_fyW51 z=Kgo71lAy;L3k~Ar7#ooznz$vt>M#WQ`r43o8Jkqhy6}rb~S$)hxW~2{UexQHkkj5 zlompOkE*%QXd@zi6z0?EL@d1L?vvVwidr66Xu^dCZlVOIK0VOE+yzoQ$;`M(P? zU|=seL6}dfdkCKopM}t74{Z7gvjCqk(+&`30Xf1fxJbAd{>8#gsK5-cp8xq!?`DaZ ziowCh-Yje+65lGk5B!+$X7Eno1K`8L$>1-Ax%o~BvvPi~ljog@m}P~bhpclXFb_u8 zxOj~)Te)7Ct=S~ZR=zFFZvVTn$BDwH!rb=%5k3h1QMfxUxA=TMjsLg*F(SMW*y4I{ zKjA~*EaA_GukE*Jh$V|3$b86@ZT>?o1cW) z;w+3d+Hf0>73OaWO%aYoJ`03-j<{Bs_V)`v%nQKhCBSX-M`3QWcZI)#KNu(cER5Ui zB4Iw*zfzd?R|_+rTZFk;pAcr+H-z`Y=c4C-CO9Gy^yzy@$;I2qlo*{qg zV&2tMPoTMQ--u3)Y(6U!hkW(?@8uaO6Kru3q%w@csFBS_O8Sb<*Bj3e<|~svVZJ7b zw^wyqz8c9m=EJue&k~*nzcA6s2m}Aqp);aJHXp+9(dH?q z(v!r78rgiSYGl);O``K! zfxkP$*7McK=Y)A(@PaVUC3l4+z!x|73iD!tKaIe|Su9)}xp<+Z~&)#fBQ$eAeTH=xk*f z?d5)4M09FoPe>vHFl1r8R2nGE?R}x}#qjw{NVK6oRhU;zvxRxpG)I^=e2z~0o8T{U z=@qPyY_bp|X+)81wweAO1@v2?t#Bp2TbM8T-Xk1_&xiEPhgV1U3Aez1z@;-x8>W5O z;gC-7ghV_8|4CuCV4I8oBFs06-xua}-#=aalQ3T>PM52w4ETM7nWz391uDVo#6kMA zUkLC*aU8ov2IB>ifEw8h#!AuoW^yI%WiVbTIyJHxjI?8mS@2|+{XEephafUw5>(Mh z2G`AEM2&0)SEJ~>D7{^nZ!iB=m=~qZ!Yp_i^OS}?DmpcC$h7P!SAuFLke0nDIyJJX z=zF5`BJ~qtUc~-W_*?i#UHl(mzJC4PX%6XciJ4u_MrQ1;StyKbcDXsMkn{?FdxJLA z$fj2Yiq00#qrL1|XNyjaY;J)M(>nJ5QGgL*#50x)g?SZJ$Bdetef>(&sgXURFQ$#m z84JaR8rd}99?|)J_anl5vHYo!1o+nYpM?3M_#t7wWj+{TwwRaULxlMXk;5M5o60r_J1Or|2wTz3_eT!@}Ee^~^9U z%}epE!Y{yoPMDYZukp2H8tsGlSBc;o=L}QlTj|SKAsM|#MW;qKgZG%|%(Q{_w>z6G zB04p)8N91HZLs|lk%)y+Bpaik=)5U0UYG@3>f$NFn~^zNZCdF}4#f^wG`|Bne zH9Hl0P7+fioBe(>ZJu*%-Vz&XWbr)X3%wU9(*F+h{MFW|8RB$RV?BZj*?^@IM#kEfW5&GJA-3 zH=2d{p9;@2Q|Y0%M5jhJJ#<)f-pueIJ@ZL{pD2v~e!%`O0scF~OU(3kr)AlqQzM(v z+e#Z;#nAB=mYFFvGQV4Zt95G^ZJyUA)(v7qjcmR{_ORBm{dw!;4`RfB>^LYq8#x^k z=ADhth52t0{}KKc{@231LBTL9#2Xhs3iB>XhcIu)@K@z&L;LRhX?+HGKW3OKVx}y*eT3^k=Vmt+357jA<;t=8SqHN*Al^>VgA;| zKM1o|&cwn>XTl#WTnL{Fb=nMb@%h5MJyRykf5(`SEb~7LyFns;1Ai}T`hru_UyDwS zY`(VEBzg<{XN7qy=TqV1@Q(`f77iEP?eM$G-IjzDZ2#^O_Nyhb>qvAH^td7ODRHNc2lrg6y#l-GpRHT%X7rm5JV^NMtMZ2W5 zvAbhZQc;rPhDypN?P9ST75kAsvD^N>=Y5_#4>$IF=DqLtJm)#T^XEP9pZm^v-^=}+ z?G$Xg)?~9|gMHU(F*^@=TxraGAYS840X*YzlQ9p7>_Br()|mUujty=sS;9I?;HJ>e zjS+gzn5RyNQvmmcb|Pwf!SRZxmdOVDUa$+FO=U?**k8*XD#xA*3TRBP~c0(+?e`RuxkL<6;8-1 zccQVrOJ>t1WLMVg2hhYio@LWvUN2BssHGsr|GipDM9A=dOCZBi;|H`}W~^RKudTzM zTWSmwxLIq?EpUt0qmV=9y;4#A&8EH$t|Z#E34?f*sMTjmew}lby|xM8$OJpcee; z(L0URhHR|J-Yout*;}+8p?os5DuQF05}`GP``nEWhzry7q3|OuFzGKCQ@P(5Q=vn~ zRH#ASk3W?z7^lr`JeBsRNuP3RmeUeTpip)XGT)#zT?zBZ*p0@Y)B5(nv?w>K4~5iv z`D23{NA;nwRtq{`)%pRrWD#{mO8C0g-!$H+_4kY!xwEWtRG1#~?aI?hh+>2NO6gj& z)2PM96x=~xgcVA3laKam#f4^lD5P$0&boS~;@IiNyNnyO{;kM9vZZkU-V&LIV%Vrvr{ua1J9}4f*0;99U82zQj=&Yc8oDlkyvAQKagIPFHppYsg zMrf`U>FgMBfm&H`JNXMIxmEtf>=~-Ml%ciKc18W`N_4dX4fJC~Gfc}Y zcn>lb3#Vkp)Ic$1q#hSjuXK74Q{P*);7|K>>Pbwy((%4l!GWJ&l4HF0VEwZ<8^NJu%}_>eY5jC@O(xLCM@D zzSwxaxW~9#oUSqCLj`)Y_NgEn$HSxu4>a9o7-T zmJab|rxgVTsX`hWVm85?IE8w@t@4z zsr5v*rR-heDaJ){I&;h3El%58JYRgR7W_Nq+au0>4i$yFwUB@jKGMMXn)xtr;Kuta z`)6toK25BUc4{#lMaY@-(c;3KWJ;Y2Pf(PO_UWvo`gGQb;sOm-NM{u>zKvS2lTPrw zrR2ovLE%^}1@RlT);E|c3#*3|#0m^v3x23cb`HD>c`XHPDm0S5_$arZ8SH0j3z9&s z{4oJ~z#oiY2)TVoTsSLhP1%_A?--NsF=O%v}X4R1|Y30qzn{GNux3#`FX~a)8f#F;gT;M7kA8b0x%dX^$4a z#rX{}KG#+x;bHl-Kk%37dp#)u)*&04zthK zdI0}t^`Y=NvtxrB&AwD|_j!>2DAEFq4+f`Y!RZn6!3Mj~^5Db$QgpDv?lTyCo;DwB zFg~gMY3jN%bH`@|8OG~F;S?Jz*kHdNUa7dB5HBzvY_LBHu?ioq6%;NvA8fGS#U4lb zzJH&u#DcC(WlSR&CUBmC8??U8nEKsnJW*>3$ETq6XN{Y*PM0ULPZc+7@j-pL82@J4 zPsB&{J1yZ6ty{GCw!g;g*kIrGt-9U>9l+ zK3^8o0&KARbl_v%zQKI3!QSl$@!^D^kY*HmWUma)y8}~4w{H);HSlQQO@S%c^Ns`_ z&N$ou)`Wziz$*g}23{6;An@YAeSsGQ?g>1f?eDcKhJ=p5ZGl?@HwSJCJTY)XUlt|Gn{Y z@wXGJ{|6OGPk$)$3h^#;S}Cp{mleE9%&$^Vi6QYcot1VoUp+zG&=}=%Y3lGF4Puuo-iM5ulA~|pUnpw>^fb1gLk2$Y`^c4 z6YN6sgU}T7!3MifH$FBP-()`6VAtshI{Y3bo2-ZHKO!#F8-&g^Cv30_EeJwam=8AC zbruGlzcn9hufo!)y&%dqhdRM+VbI-fBe z*-Gh$99?K22&D)0B!mrip`}4+y$NB1U1wR)dE9)k!LGAB>2Us4_@Oyrgk5Mb2)$@N z*kBi0k%TgxUzra!*mYJ0omb2U+bhf{cA-^4=zq)!8|*?uLFl!1JjVvR&g!6Zg85*B zU1yEyr1qz2$LeZ~unVmXLT8!~HrR!RO^Eit)qJqQuCp%aq{kpt7B+FIDl`&=($r)* zVM|k!ZE&p*Li{Qt0}UJO71|JVK59PLV6V`|s*c+KNpr#oyU?Z}^hNW*2D{Mas*q+1 znh!SEbw-2Ecg+VI+*q=fZ3#k;nG-hHg|-HvpPLUh*ekRx=={Qbu)(ggJ?NDFU``le z7upeo{%AhfU>DjMgkEFkcxQXfHRW%z~D;=Z5vEaE2B# zV1yfo^`X$D#eHrTnkl`4Xo`#7>3_mZk~2=Kjh52d7OEU$ay$%rK7)o z-UM#YD(uBHp)x2_4uWzbcaCB~@Jnhk@%HucG?>U#-qYC^iX!fep?5f+D z=dYcMfx8n|_PnqD3g)B|)?+f}oqNt(0&fqzEAZaH2LkgRXV)1YxGC^kT0(@G`JSOO z@ch8Nf%_7d`@eI`RaW&qA$5OXW}>@tgYK3)^XfC_=D_Jk8cnk!*(-bQ$u05T+7Mhf z2i_WZN8sIo_XR!}c${`Nugb*0Qv;V;L!>=$F>rU_1%Vd@9tgZV@XEl<_Vx-5XPg~x z*N22nfwu%^R<~!~6_}aaZa)y1*QmRFeBh?QEj+>PPHiEfGw}Sty@C4z_Xl1Uctzl$ zz-ygp|A~60T)Ap}{v1D2^oI1l!2N-j1zr)D zUv_ovwSh+hZwx#dxU?-qb_U)PczPx3e045uf)2*8v<_*ymdl2QtSu`y94tGzKb$z-Fe)xV>~hNRQLo=mO9G@ zZVy~^rv2SSUZ4Ey76fMYy4wc=FAuyj@M?HUrZXIv2Myf5Dex9?$rHARgk3P#dd1>Imz^ekU3A_%Tp6P4|yxBOle``qC5qNjteSr@K9(UYW0TTmHg=b_1 zw+3zxTs%(uzdLn@1g6cqeG%M}2@M2Z9(ZNo)$oa#|8U^-fj3oI?cYLVW|m=l;9Y_D z20j3vl=<@pGUxGun*z7Mt(kvY;7+(?3G+ijFMM*Ap)YWM;AMfCrk^_{^B)S#bbPmu z1l}09#ET%@iFx_CS((tzz)<(A-VK2_GuPh}wuXcqfp^1gS>}C#4+b8mFSPD6 zG4NFQ^ek^{;C5%)U-XE$JMaQ{ZkBmb;DNx)1FwY7$oy9a9uB-d@FsC7*PdnALW1%3 zz`FwP4SWFBESnVc{nB}S;HJPWf!pBh`?%)TjYaC_2ks5r7q~z0vcM}~`(i&7cx~X3 zz#9{1_m3yYotUM)qzL+?FX|CM&Q%lHyBuk(BsM7WRdimxzW~-m%%EM2( z+bg;9ens-h;kbOcxZNYO^5vmwMO$!rb7`?_z|=q;4(5-lEF?`xS}sVcZ$g3IKEAmi2rpQsTWQQE20*n$gwFB7clwOn6U zH!2_W81Jt4%wc-6?#xwoKb)I<&h{FCJrdx~3`J3{(MVWGBANUdW)#w$LjMeAD%9FkMDMz&^YggsBz4;jvW-HaZ@8Zg| zE0wxfsk6#2JwIc@Iwi78KCb1G%8g1qnwx@OtE&uMnV(+Tr{o58Damiu;x#>A5XVMQ zHM&vh9=xWV0=Q@i2l}%q3g}&`g*3(nuYW<$PDyF3yuj-kJ&?%4yIo?z+#}(iuO1L2 zED^v(A_~iH(9}a!l7f4*kVe5wUx<2rGOEoMR6dS`4+-ER5rvHdV=cK{ancZcKoAA_ z5`3hdVNOKU>v(_mxDFNUbv@euNR42pz9-sgU#Vji{8o+N4hi~}eY^Ql!Rp^_Ax1<(qyvcKR&F2s!rF5 zyHw1U-H!De5hlrQLJvq>vRX|GGIff{X9Z9vemQ z)*yI6jo>B;-hqGxZ}DH%={X-KGy58FH7`2zJiS-c7Eg6kPDq8o||T>+1UC zf#B_0T#q+6#TKuUo;s%*&d-nSqaO)+ zrK5G!h&3EIwY>ki$>;FKD71MF=YQ_$o}1$>^vQK9j&o|2H}w!y=~H4CqK6K zU0S>~Kg#sR_UQgO*^vzqf(w4XMsVPZW7l+_GCzjnvo#ewB)!WJvfz3<4%5?k*(<>| zIZ?yAwRjDAr&iot-Ez9{2rYi;FhOnl;jtscXM)}0|<`1=~c!3VPW zzf`cS#VdI9F^5-hq2&&J*wF!)nwmvdS&!cUGh(?$-h( zbu_K!Z>h<@x>Pkak?ltE=gn`F34NRVgls@HF`bLTT;rW${kk2>uUs8OK^XEwne`j1bL;ox^V0y z+nzm^<%-_oTWj=Id{qy@C?g}J^pzm^sT#q-GqX#vTEUeyf(IXz;Dk(2SZ(pz8ok22 z?3vb}cV6O3jbQ(`^?nqkP{W6`&=L+BUzJ~j*6NHDHqI*-e>M5|C#;+r^UF2nO_?_@ zc8?;P=aq*f@&?)HO&5_F?Kw5xqnWvQWb=D!M9ll<8t*Oh%6%_QKK{91OEX(RIezcf?ib27(PX5QyKBXbJMYGw}Cgl+RG#jW|*9VfFkO*Q=QymIr*$)&T++wEuRC^kY}EjZ4VU#x4F>dLes z>iREQyslj}dM}(?SGRkzt7R1?c3!48xvpqwqnk2E1RtwhwlF_s!f0<@U9%2%TV(oV h<+4e+DM#I;I95QB8#k|;$^+;qKnEZy12_C>*7i>=liXGD-_v1=RE)C|D6A+ zCq4D4Z@u-_rK@{-x;x#!Z0L04jgBk3pORNpFf4D#kQXBRRs7{M9rfRq&#rUS|A8fL zD)Cjm@BANIc0cL(yd`en|EG9PYfJvWY(T@q-REvOH|f2k9t&n%b@jYOb7m}_vuLri zU~28;T4!qQ`B%@LbN;ngHdM9m=1!ibI&Z0Mf1PqCKC3btUR)U3;tQPFZp-YnEPq2R zEvI4czq>U2vU6uMiQK#$Ds#)5o!vIdE&YD@?a4Oxd^32^NlLrI{G$A!4exgD*Ex2N z<2ZiDDMaKrQRhx)nR{xc8rV7GwZpz@ziLi2IZjG`#%pP*hZ~DCmEWfvxBY1=*uJ5hh}J&E(KU$Ee4xP03iX8(xspU)uJ0;)dxVQCG zkx^GCYC7&KElZy^^?25T#6=hW)N%5qzTf*k%}D-xqM*GKS-T=UByG&v`ud?geB&Cr zMB1xxI5f8)Inut@F88aRs<2P#$BFZb&g&oCIepSR)OlAmYOP`|6_aXEZ+-RZh0`8) zNA*&@lE)@CMT*aMt9q#}BLZm51e)#Y?>PBqCL+CjMMExnzu@(w!(N@RyL3Xk8;1{w zPM>sFwK`jOp-07}yy(#ZeToa+=X$BrdJamImv2SeoD0&+r-Y{m>#K`YK?S^`M4|h2 zFLlP8(gzc$q%Vtqk(P*dTro!FFpUoQ$Z9_gb}e7;=^-Q)e#o{qjw!3^~q`pNwA=Vk6kQB|l)-9UfU zKVI6=aRw$0&pIc6da+tdlcKD${EFf=lZ#T1yc>RVN>d{J-#?5wI~43(Ffmlx#c@J0 z=j?T{v3aq-rR_glwLg(|I1xL1xN6M1z2_!vE$!eGrH;uS87LVuYvh>h5#14w=&!u# z5LKgt>Mqt1^p&=A9Cc?R1BoSHminBc$BX>e|M2>dYxB zc-tEVo%)Wid};g{b1QwjYMs)f2`Bhszcs#H)6^RUX~Fw}rIXZL2r-rXePZq=U+GXa z_Z8o+>F%V{Rq3gv7|Z8_T`R^cIaZqLU}D!K-QRc6u<5wo)IaRYz~t*x;#-nq$%j0pR4lJrEWY|^&WCzqQK{5`c=VNr(ldTI`#12u&SmXZ?fl~lZa$H zMZVX>5*_G zD>A_;3clArk<~x3_XBtCK-DYWUkzpUo@*iT}SIJkqi( z%=~FxQi{7I^2)cx#-^<(jP?ju9IYvy`*qR4j?>qvel)w%_w1l~3soO-#k!h>nMabw z{O)*FNmIc)-D0t$v9Wa=o?hB_DootOKy@VkV#i1@rC{wphuz;Z>*bb6WKw2EYSEF* z*MreuFci*Bibe98{7&*dO#aL+@e;A@uXHi zE2(5n#LAkS`QnCXM&`^7O+F`iWs6$QoLzUfG`x3eXZO*rD!Flgp1RpLd{;;3_#I1< zVnfpAewAM2yM9q_S!zMf$nHZ5W{=!;iZl0v4n@A=Rf(>%I+Z3ll}Y0of;mHTiYG(n?uyVy#9W58)r|^rsxV)NAf=LbsJ|YYf<80WmDKG@x4yY7vFGtfZih(3 z`4}6IKO+!Qx=s7fO=jQ&2QA?>`BhN5$I+FdeK9dMU!_;Qw?j5f3 z-2J!ug6+<59LMM0dY0W z_L~7!qNibxP9L=C1`Y^kgN>gYaD#K1=b+Kb*beTA0(ATbaJm%!8q7e)Z@$!6rfZ+xLtU4LRn^gf0NN4UC<{a^+*mTYz=--Dll#&)=9J8ole|Dn7g?Vo84dH>9CKkwwb)Lm2WTj?H3^QF1LbbRD?#>bkw@bL;i_AU42xkXWb zTEn5LG`Ea!98f_BR)^hMLT+`MTTd7TWh0>y%67n(wLdIS4OK_bfZ0uM4YTGWYd2v@ zb%vhTn67$r%y9bY4IW7yxUcOaOq+D4);ZmcclPy;mx;`XW*46ez-*l!<&+|ALYgzlaV|MYzyWNV^bKv& z%iE;aoSbgB^0KV>?@rd7JxH^WAuJhA%-M%DJ2g(H$2dPqdWKGyH~|b7^SyMb(@oMP zI={^6i!^tO=jil#&QPRHz@WsPVkt008%%J{KiR;Uk8}u?eu_OthEwengZ zv7t@=<~HeVRyxieybggCa!GfNBtEqanE!2?^v`Y5X)Gv#_R%)!k!{i!pGe2`mNWl^ zpwr9Rq#N3#A8nI+GcXfsLOHujIV zNk1p)@z}Dk&KEg5A#mrj8$IP5cpGW@wyMp7+-vK=CVn#FK_k{n2I9oUuG57Pd>4P{Xd;#K8i^p5M zK$trVSD0T)PYAQF|3UrQ>4!*sEg6Rpv%yMw#)hzBYUBWPGK1;mso)USF~e9aNA0jT{i2U*+^u ziv#>xYtYnv-P8-9u-eIOEI*Ab#v$h4(od`3j4 zMh;xg@kh|Za;3+5J;8z+IUu^Omp;Iel z{Q=2frkPM2%@B{Q5e8u)^E;ixN9NYRVC?m#$QC2$RRC_aS?uZD`Gj~RjX(}S*ZtQ$ zyH@L71u&w<$^w?IuJ>$0h*V#XxoXsP9_wEA^O#ZyE1lXegxS7_gjwzd)=wjbtysu( z-YCpQ>=n*K;zMEDd@PJeA3L%v?T?C1`|Gh|quveLQ^Ic}{*&-|{4LJk7sg2WJmzoR z9AP>dAWTQhqdh~JFe{!a%*M?Wjv!tt%tNVXg|{I7nH+~B4v~YM3Z1fWSokf(JXt}V ztAf9G$zky2!a3lr!ffbnVfy4RTI2I8;X$xDXxYS@Eu#Q3S%9q$3$sD>Sg8LG;&m2B z!DIQruyorU5sF4Gr z7lMt=*J4ABY;1~XBMthS*iic$=f7#GU*-XavA@NmSQu%BLeZ&_`6Gm3tmx+;=E8nPvq%bN5%_qlimT z-uMi1qQQn5+4$s-i#RiQTCR)*WQcl50cvD3LV2PWA)X-2GeMJtdD`zo=MVM#q z)(F3Z_z7X0xr;j#*a-fTcv+Z^#>^B~a#UzWc#2&&WhzDB?_p&2G|Mf8%3Q#1Q9Z?F6WGAB!(O^T3Y<4Wb z){3*hp>!&a(+dmvBE*9%=8nnex)B%5hJwJOA!bk{n`5_9(aRC*2FW2B)3m2XHivFN zG{`-Ex#~3_9~{CLJsY7ble&h8PK_Lx?UrAmy2fqSDE8l7!yX}uCDd7?qX!&(Hj;KV z5wW30HeJ$Ph5cw~c9-I3Ndbyv)2ATV9Ovi)AqWV%GGYDGqKl{JA_{?-uh%-5<+#VE z&RCBtn7;dZdfXWWz)E)1?f5{@GXqA{^vn>Qb(({2mZ4K>WYf!Tw2={=CN|W_rena? zj)nPE6w0J@@-R}O&fbts1@_S8i48TfsUQbzj*r9SDtsVtyHLwC>SH6t!&=m@d!g>| zGW19kddyF=3QE8s9%CY0jfKp{Z53uC^Z^`n4)1%S)89wJ9IXSw9F=dd#Oa7F`?q9- z5%X6Xb@pVoFz0g=i>k*5LK7C+P$LIK=R8n%;RE3sBYrqP)Cznc;BF%AhZ;Gsg5!@6!(x7f zr-%_XviT9tVO89`5mK-yPICmV2F2D(P;4avkI6$AXXXzOX1RgFoY(n!{c~j?jK{(P ztcdGP-GdK=VOXeBBL_r3OLTU$)MBn@buT^;W?*4iYUF_E+(@X$S$MuAZbZCDxEAr9 z!qRES^emnFsrW$9y%(oBNK>aqHkIXyJ_0d)(T0rCz*o_IAOwyYGdM)#farQ8SPFq7 zV}|-aRsjo1P-l2hjwp1R$5p~d5woMzc`C$tQWEbYeqNZ)UlnGh9}547SWj3KX2U{Q z%)#}!VndB=zS1g0pNkkH-um@+5%oA6QDnk=ztu1U`a;Bdr*Q)Sfoba0$j1J9(Vs>f z#$x6T`ljuvkeS_$ngl6waJJpAu zjx!QVsJlj7amBbWLF4t3@e|^s!V|FlS8;zJ1#T3+1M$t4&iP_SMNc}|QzM&E;XE?E zzX(j9L9Q1jOwVd0<0`~g2v0*rH(LBl;f;tn2~EXJ(en@x|$N!PmgyTGJ@lzIaH0bmt#A#T}B-$)GHL{sQ zz}8`}0Ec?xyt>c(#Ykp&14}55#J7M`I4Qujh=&OC4wD*-?-%BMBTbh6g>XmMcfevQ z%d|Kw%xjd`$!1-iFM1_ny$WR0Di@s^*^J7SqO-$#HTA~$ zGjW?_+=G}yY1U=E=+ww3txFlX--^v1#9s*0=V9Rv=y6a+s3+pngpV+v>tD44A@r6E z?i$Gf=;S`4b9Hn>+Sm^eof_HL=ZVe=xHTL5F``o=YkSo1OqLApB<>U*ia2Z)epGa7 zWK)?w6@`&_9U5;KNqAkHL_V9QKaLF+X8}5CJ04xK=fRsjU!GlRzQtx z9Q72Pw@`5>qxcO4E@H&Z3xRu3wML`YxIvoZPM8@Fdl`D&vjqs8JlYZB6!J`bAn4gE z$9GICdPdNzDXh`ayJs*p-eC|DUSe^*W{f`_uCp>W2~*db847=f9_|sn9hjRSow8wF z*?Ta1e!4I_QY<_fF;CdhhV$u4Va}UnaY?L1%yZQ=BCi+bWO-c}S+@kS zV2?-nJwNQgc;WWRI2S6M1AVD*oTGiGB)DE55`Ge~KCd0dUJIVy7oGe^;XK&qAk9Wo zA1KUKS18Q2vr(A#yzET-G{k+|asQ753lvL+LQ8q`J9RdQ_kEE$yfZADdBQBaM3}wg zyN)y}}%oFD#qyERG>hJ&F&6_#m0d zF(k+V$tV<^jT1GqOw(uq;%ec`5YHCo-EU0OhTHb#!n}=+WvE|=c#$w~yJMO*&DNGNS}3viT+~L)z>C^;a-#sFBSca6D~R;{)Mg@k5Oq5WO5}bD+ynVqN8!e-0C- zvZ*wZ{r0cLh#J{c_Oa+cF_wJ*cSs|J&qiE9Kab%9VZ7+n$N|x(f#Yli_f9j#s1`97 z>b#lsCShJ$;EstpZ}9uQF!zj=aICJy2g1jqQzM&8nN>)e-m?t-K#S{LtLeSIoPZ48 zh&r1E_<`_uDZnJz9P|5RN5q?0YmjGtY%t9VsFBU_|9slW=(3Jb7~@Zo2{XLCr0`A* zX_jz*#0yw>Ej|!h6AS}kB9@OZZE;iK20nuw|!w-Z)eJLXB9S(7`Fe9-9 znWm+s;)ojAG<2cpyarN7`*rw0SS)&-a6t6CMd$6g8-?S%F0mAb>T!G^JS!R0$N|w` z5d9UzFIoJuF!%qz72bom9_7_~di1V`aI~oDYa^ns;QCh^Xo9a-DGX6_K=hAAX9fC# zYS?kUgLX*tDD;&mqp-Rd^mUT3r|Ih?qOU@pdV&^?laR8jxcxH`kc{?l#+I_O)i6{~ z(j1o-M5jg$h(5rwFR*yH#bbphAf{jXpNV(|nfrf!6|R9}#a|oS=r+@D?=sp+*jfzFPFdh~w+6#B;(gA?E#OEZl_nRbdujJ!YtP zicXDeF3fJC%`^Bw*e5pB$N|y6^z^txr<;+fo~1d~trVa}4v7AP=p3z|gj2vaaFxR{?#E2T%T=U{haxBbag>2yx#JR#e zxX8Eo9AO^Bj2Gt3>lX;u@h)`=bUG99B^EcKa`h{GAY3PUlW;)v+ePPr%L-xM)xHyU z>N$KMJSjRgazON#z-c-oc?9&6F#YUdS)N8k;1Lo# zMvWX0eJ|2-1v2jVQHVwq$>tK|hcv3i2LgwiHq^*wFWF7>62zY%&wO8W7o8f}d|wO_ zy$JC>+RwrIXQ03eD3Z+=#(t#DZkUzOh8o%IhR+q9{|#|~_R?q`c+j32*);kfZRAU6 zW?YOY&ZLoK9HJ370fZW{p+*jfei&&}8IOKg88x!0>?m!-=j~!cjm)}q|Bul~TFv7+ zI-*84j*int96c&F)X1jM4tBNXWYz|;p++_*vyy0YH3}j;pN#rh8AT+VfMoEpi)qky zaYT)58pPl9ra^Ct4K=c9&_|;4Aofodr?Kn{Yy$d!Kr$$j13H75Fq-MbT@Zz`JRU1t zh4^COd5F)jI0EH$#}Ksog|oL&k@!D4zdg&EMPkxg%^L@z}g#A57ciB64d z?DeU(2m}PSJp|4}tQ)tLD*E3KAIzXg4oHS>&^8DNx}~t8Mh=Lc3syP!KwxA>D@^W> z4}>0A6wmJ=bi<-f*QkF9t6XpXdA;YkH3Z$dkjMN~!oLTHvc3JpN5ahiRG8)dBFy-p zFdO}Ya2n!>^8Cu zn*1!>1Dq-yh8}lLJ&|xqV5HbAgnpt^BL_q;L)zHrJHSvGHL|f8FJ-w}$6NN3MW;p% zY{&X%(C6^sXg)-J4j()etk2;KUoC~%n{t#fjnL=wVMC2<8gZ-G-;H>+@Joo-3vWXF z64yV6ixcEk;r9{muym$rvmfyvg}+Dqr7+8WV{wacd(45UY-lY$5cKar_@SnM2SV5V zuYjOlrU`zR%fz8Z4v1cfwAtZQiVZcg+2K^tW(z(LW{XXgaKPwDo9_tx&cyy7Et(CH zKRV5qM4c2~jd+>xTEzER{D?4rjMmWUuknHKxaic#0ns;#zSi&k85|3#RN7NCq7UfQuU)W~K(znC^j_(1TZ3~i{9194=eAi-5+hBqKa)W~Ld z(?##fSeT7i!m`p!-Xq5fsF6))!lKi@j`nnp5a$kp1?rF>n^rGH+8j>B#D*H#98T5K zW)40Os>FsG*&IRDh|YihTY)@tgTMaan~n8PkqP!R3A~aCNWwABeLlMl8zeUmffdDy1zNgm*YNlpQ=?| z-0=PC`G$VI3*Al)D$gBP?8|9*zIQkG_F`XC9~|s5@al~@{m)&4VV+)6?cAzWYGgi+ zI2d+Q68_L)ZuCY!q`BeP)@=9ht5jYpCk_I)WtGxUu`N5^!Bdt{5w5qG-xfya_GEYy zIpCGuZt+fw_gc(bK#e`8y5U|&nFxB09E_o1o`g}shHfuqyS+bGrzGxUm!`OnJ*e_Vi+o(mo=FO(hxviV{|zx5wm4U_8#>!}J@4{n*lcCLV)1T^ zKeTwi#fL0DW--rVo9g+4)Nl`rdqu58p~W%HZg@4;ewx?DdP>4PIc<2Y#XL`~(%e<0 zzA5nxuUHThMhSPbILG2Vi$_^pM(*tSnQC#B#q%w$wK%@iN~|RF02yJ8#TzZ&YH^dr zdo2Ehoay=Dak$~b79aO`q_#>q1x8)H0{o{H!(oeaEiTgRZokieMZ@#I9(B%XUeQC8 zg^yXBgw4z7X%_deII7uAd03506&Fi9sq$g(viWS$y9SU?`)@e*>O$Mxi)ZuoOGz>PeiPD?5nlwafyc|y%_ zA6f4YHoUT{ySp((^=th16Y5pv4tUDn$!&Z}O>qaeSEs4r?&c}J9_~n}jaO|{5BM4j zo>iwS_wo)Z>Mq-)qH1iz#7IANk^WrNnAoK5RlW|&UHP0k-}QA=A@`OJD&#v)HIzsC z`eu63O*&Hdx$)=KMD-i@T}Gd}y*I1t-D_U+mAfMqGH_hu#Jr9>O=G-sOG}aNgN1q6Lpe{; z0b+dr>QL#pv8{Nh8h>Hg<@ca`4lG!nYsi#;sZDwQ02+nG1llpY(8k|k%U^%w8-F~% zf%e2(8?+gj9oRw%#^IZ79EQevXJnVc?sP21;dh?HR)4R+*Tcq{*fGgpe=HMYf+ z!QpWr9daHThrBjvk5~l?$`>xIJUhnCCpp9|1ts3d(BELO;~L4=)06J zvdUMsDL>oq?qzx25W_lRSQr+gd=6sPKLU%X|B5!{SNq-YOPKs~+mv68@?}z294ldJv548VHiK*M2BKX z8wVRa2d(9EQrxsgJOy^wr)dKgpp04BW2`b;+LWnHajRG+^N(o*4x-FT*s`G|R+$gl zlsVv)8Sze=cGjNnm1TMKxwV~tZBxFy{e#=l&9B=OKaS$2$sB*vO8F;0a9Ag4u@kH-3(^BwxSeVC6^Gty4kN<#v7IYJ6$H1$? z_#4*haD4fR36CLcbvUh!!>MrCRk{0j`!adLsI~kil;_EUrO?o}*79@Ol%J0ylK-|d ztAd^FuWs^*ec2?--_jHQyaUV>bo;;UIoLYe;gTsQ)(o(E7aINS(_vypFt@t4!a-*Nb>f)V}c|JpPTdHvrW!XQor@TL_LjKj0q zIIO%9dlT8aUj}32?|kuR*1}gfO<=iWUEWELY8c{VLu=;OJsYaN%FMW-jeM|Y!)D05 z|IxOFyz(!Z8IS*`OefnRvs0!K2Yb4QUsahIJKN~riO$;wZ2Ln!-Pr$NSIrwIZT$#3 zgSFfmn8$EX>{>h8%iWLi89eoAx7ycB_pt|L7B%J~=eDs<>eUcKAB+FHJncW_y?rt> zu4v=ib24HsWUitZ7KTC`!g9hd#lkNiv-+-Y<29w18*V~Rc*@M~Ns8CDhX0V=1u!46 z{y%i@Y9wmXfz>g*3_FfQo=%~PUTzbs+W{RXvs%N5UJc>Cneh+B-00WvkkqUM3*UvE z-P;&?TL(M#whjyP*!wTq`s{AIQdU(>aoAsyb<@K)dMsVO*A-H$J- zJD^m#6T7JH$p^6y{G^?`po@wmPrVaYN|W8B9jcFcx_l#^NuQr%Qu+z?jkkQQ&WfKb z{c(=3VcyBoFW&W?;yX!dcg9dW+gd-QKJ8Vs05i+`F&K54oZmK9zwf&%&HdY#zSRvA z2d#-`yta8qb;fJ!6F>dKSKayj>hv$G(;`79e_f(`Br@K~dNk1#c4~b1DDuy%9-f}R zCQ*}))G>e1kEmd#(|FyKtksD~#2J?1Yw|fL{8A)0|A9oX9rCBssLVetClR2#{FRBU z74+EOpMNLgcz=KLOkdXW1d`hCa9>t^B0M*3UOF7j&FGMIOCldn>kZ?&aZUQ3%w=PJ zPhA(tx*7GU%m^~!bwCxwkyhx$H} zp1(MewKy^6iI3DT6HWLL7*8a!u1Op|bOvgO`o^q{-+7HM9J_XO$APKm+^X)UP1&t| z2NnEy<|!*a@7t?;a>(g>{^-7!4#YFwM0E4{g3f zGiDd~cFp`v;o0d&5GN18-onYBn8+$eqkd9Lv{54A)T9S@g!`uTz0H?giZ*2{f78<7>gWGgejiWC%5(RA?duXhJrVT{`_i{7---4p?w>f8QPe3fyltF6 zDcU<6Jqqh0f4?oh+zo z|0&BS2X~I{G_c*|YDbNrc~*x+F`j$RG4J>2#G-*&9TWX<=3(|XzVvv}7k5ZRC!hm)c*^pG8Wk+)pE|GS=+xl-NF+9sr;iY!5ryz zSa*5ZGK}bvbxDVROzS(*mwdaE^1Qp;O2gJUB*Li3dRLkn8p?8|zwr3NQIEdjWTf!wxMSk@z^D;Z7mZ|+`h9mE$s67<=_a5Ep(@q$P-!oPGWJ~^<7CfUoIqH<+ zBtzDDt56r#XlY;Py$4$+J=QWIRj-=gdKG0xQuM0nIIx}i4W&zbS#_ve4@~g`EmdDO zMb>}w&5M(Z24~&o-t(=mdwhAzvVapTnBKd8C-n@A$SPXW5}hckXyRx#Xah6nW(14t zT1KbhH3$09;x*}ae(fvmq_D+iW{l0cu_c(S>)PF;|D<{9v*hOFfx&}4lzx&pGlKco zwB$w7fRXvQPTJCh<0YxEswFz8`n(pYb5JDN$-E{I?E%v4`}k5ku7628 zJb}OOSJOQe0+SzHNUy$N!8&+cj7NG$c5Dt8HUu+%${$|bKWkYU)>xruGIwl;VCP`Y z@ceVK;=4Mq0l_|z9mj$tJ;qMY4;S9POX14pPx<|egPn(G;VS=_wROQRKjoiOJUuIG za7$4ZRspugBTn*R2e%kzg-eUi$zQRxq8OF-&(EBhbfnrJ&C2v874>Rd6o@X1W<-7A z=vLeyUDk0@xFr_ec43kaZ>q@e)lwAq_Z!kRzb7YNK_@&feQ&_+m%+vPkDH5+HAnBL{;8QQ@!>)4h==R% z6kqWXcfcXvfcSUKu_4@6hx?+D{&(!^=G0_me%%fm(p$}Wv27Rnec`gBS?@Jthd1s; z?4ggoRT8T@I_b0K;y0R?_YIE4P#ka8t;~9}xn`la@pQ`H-Rx~VKQgao;flAH51f!Z zx+7jw@q<>fGM9DAyrff2{*ckB^HQ;Exqn_y^*yutX81~nImNr0^Pg|Ve&t(g$vkC* zHdgq{E6zTCw%h)D-;lE>&vI%ey)|XngEt{38vJ5Y^Zp1s(myybID8?#ME0_1(ZWgV z+54!!dVMq2I}P~N81+wjwE3;q-P^wR^^Y%T-v1?we(SH9l=Wb9*{#DKtXPI`l78Ew zyZFVCwX%8PUCm{_tb3bpt#2;y`R=>3`EYjnh{*m(`hc9#!DNLkIB7J$cPsqg{L@$3 zlqh`bCgiTu3gh~L36z*r}1 zXmiBpthhg`pjm(8^ZR;0b4_*AfUq;qx6AJo@w z(!b0~JNA!^*UD25KlS2|KA&njZ z#Z>TDOaC5>pTpWN0X7xzU^5~*);E*dPagn}>zQ`}_;leq-Y@flB=T`6&ecVup%_A`>+&)&!C3oI&hkBd>ImELN$VE%)&2&V`B6Z_%`92c&`AMHcP-< zmn?G|*pyug&W0Aj@|0!25zMWI`mezV-UCdT;JT!P_q+^U0hvQb{aZ^XbGB1&0h@Wy zg_{Ec8%H*JuBDUBDCP&MPHc;n$S@ORo>hQMhpcR&rIV>gu-stjWV1D`2sGULH@xEs zuMlb6+tdG{>SD&O3V^RRUHWRbxCnQZqzzZW#{Iqh+mjnc%~aO*?xx6Geiry7o5il^`Y zfXC<0NNb28?t!=*aW3MjAJW{_EO$^B9t1izz@NdpTb&q|@3Cay0lZv~dYE;3q<-PX ziS%fv7&`Yu+(0sLrt|{DoXFe@W*~PCV*Jd}6Ewpa+j`=K-w}Mo^R0+Yz+{c%og>~{ zxF2x_PFerTu?N%@!RyN*#*Z@%fyi~50={Q3`L z3*y7~4+)0a8?mroN=F7BKGfumS}CBvDg z|LxK;&=0_81=s5QEA;d4>9Ag>uXJuc*`afrRLHfRfd{y`lFzxGj!F0|(pBJ0ogZK5 z{00IWFd0h*9?JCz(wtY9>-4qydG_2eM(XtSPP63Ss?#?(9pIS_EYj(l^urxl8BV{# z-_80+;Y2#_Oh%$L9d~YZ7TJPxyR!=EN)+b3`x!V#w}I9YSkVhebBJrOWZ=nRZz7!n z9;efHIs1{W0DF6a<<1eL@u&A@8P46U&)nuN5w|zq>kP&2mJZm<4Cg-QBBWV>M|v4p zleb73(-7$gob{6C&Nu@PAK#5MgWeGE#FvjxN{EzWt8W+&2}ZO(M0ndjvn zNnDLI^LQUB_g%cflJhu&-;NX-*A@Ezs@!cu{X3JdoO9)@1vj|0!~9tp*O$z>Zbtbv zSI?TWXwl@_lIq3P?t8=hvpS5OKd1VdF^d+>o9zxN@<)>{n=xKTurk`Ow^Fcu4$jXF=5o#OtCIvY4wnDen#m;>+&VXmP^ggHmKPi5IK;+?`dh`;bS zuII@&lEJO!h;Sa_e+m~O=6__-5hrn2n5#%n3l0?m=Sv984rm=~i$UvP>I{kn5%eS9 zc=xagYP3gTM7-F<&<_ilW`l+4oFky_#0LWB6>X@I1EOQDm@J7d` zVqk0Q=q!ZQ&%p0_GHrR-LS`fS3UjD+WhDRv=9vc_YX8Mr)*t3UiYya14}Om!VIKCb z=fNqSlc}O#gm|Iw&4@YCY0pNk5@rJ)73MtXDI7zbV==$pSQdI*f169f`>#1KLqD99 z`bHR6Nr4K)teiUcwspd^(GyVYb?>Fey7wz!AVHOQzyrHb3dbLT6U;oWp5@5g0?LKy zkUcd|)6>Haks8@NO^;s@EXz8ouyCkZHYOZKoGZ)`(g*J};=0I@3~q8^VMKbrLL2sk zc8coE>@Y%xSx&2L-1lM)7@Ko?Z;V^iya0U2Q;Yu+7 zpMsU~VHkG{S3&2Q2kNuIPYToL)52`%8Z06HlLq&4HCWV2d?0*;g@vh+1EPN}I_>9U zF%Qf9PIPKy^RUcZ8I``MbOG(}#|OfBqEm}AVg5^IF$D9h(3xUHjcgv@xj^(v#I>{+ zryE44Mm9c|ST=l2HakX*3>$CzTPhhGLRXkWn8L!v_(0%$O81hVs;eW$Sd%Hpc_=;@p(++{6qqAl`tZGw-8GGW%CSBGq(14O4r zHk0ve(I+6DD9noVXvu25UUX{YK&oVTJ#&|o`maCLuBWqCw^xrF9Outg54w5d{9WTx zPOg!JQHY5#*|->jb% z0HZK60=^TC=qOC5VPQ5xU&nwwdwYiHbQ%+mf-e&0=v*Sq89YyzF|T{Dar(7I4@rWT z5+1jBv#{fLyypO*0C|t4e_}EJYf}#|Z0L*3MjRGqBaRC*PK8~ZMqK4QeL{|bPZJ&m z?k${$c&sqz7q4B=z6?B1cs%$zVV12I<_N76E(bp;jCmDzcn>liRUqSc!j<5?!c|~C z8;Ulw!QTsW;-)2eIy=x=cs@8ROrN}d!mm}!C#*Sk#GOmz}S}-$d#0q#Z zh0LBU7k)Y!yHVj1=o^LEv)6=KG5`ORW!afO3s-@EwrpndSwS@7MU`tUW|}&N$azS3 z33!7r8~2oO9r!oGOTljl*MpA=uK@o`m}PlBg8t*|4X@Ui$~p*P>;D5u@Jh`m!t4#xtbpr}|4&M;0-u`V@n*#53U7oy zLAVBdzc4S<@S+UMve9n|ZvyWn$C0rWiN8q3W^fvI$1JcN+*_CzXZj1Xac2l~PLvA2 z0zOB$27Hk)=fqs$dhnCNP2l*`RssIwDJ$kxn|4@CBYFw5)qRE8=%K=F^ekcaa)Iz} zaIG*e?C@6-eLjPjb%5jgY|@>Q!3tIgv+%vbtl$A*R=mO3BW@JtCxk`e8@q41f)K<6A`_I{FZ4=}GJ(LM=0 zSD0T&R|)?vj)`@hF-no2%qYMM>5cMD^(x{fVSbanFU$+_DY8H33HffqJrV2QXz;@Z zGp&1%m_H&7^PX_M|L3=t{w<9H=Yh+_h+j5Lv%n<8=Ue(zVSbZcXz?EORISGc!Yt9L zkprUND0&^A6|xtZY6H#Xxge;F+A6Y+h*{9(CDm{{jJaT`%;V!KqH%H`yHf>4Bd}nLyc^P^c1WrR>1G{4#K?R z&wuBl&adx*!rbK-3CDRqLQE3;PS@|oLjm$+(OF=YFu&{PTKXNrb%@sr^ZR|hFuy^0 zC4`N50r7K|{;D7ApBen#e^WB}z5ljl^atTTA?6hm+SBPFj>h z@EO<7iDSa7vmd0uPl$gMrc=Khz3_33?S%Qn^b}zp#B>zqIs9}>XPO;g*{+t}o6m}5 zMjwa~$zWwsi}{jd+RQRmTPv#fGD-qu`q5w6r zY3Mm(&x6TH!aUgIr8qW%w;NSjJVTgw6r9IERBdiyl8rf`e&x-y6;^*R$;1SDKVcyfilfLE^KJSW7jci`wlSi9h zd6j)FHq^+b1MTF%wF&X5!n|K9TR2_^D3Ap2f4WqdH%~n#%){l!g?X>b3&M2zYvCju zyzCH82EQT9d!arT<`MO`!nFU#DYE}(qL96jox_h}M2&3r4GMN#1*{-NxFh0#Fz<8X zV_j&&Bj$d>0}&4p=HYmea0xFQ6-$CQQk4kvW~(ALNM`TFqEjQA&2pCLbUKRmGI zPK|6PZ_Ki}g*M#(Q)I#zxl))v9$pb<1v@PMoiJ}5u0}_JS~ffNT}!y>f2|??L>z@Bze?Z0Pge=E_Z+ekyt5m%9tsqEjQA21RABfdkJ4ca0cM*M~_8}zOz97W<&$zX%_3A1PVgg+{v zLk2}}2w^OIRogJcbVg>t$hN-nEeWi)QN_^@7TQoF^O3Kt-@7#eNu%u;;VG*y<1yV7FFGsLTL<(j z!5m7K<-Xx+;a?)Y*mW0q5hL(gb{x&%vOIZ%;EZnFel|t!g+}GMMC%~ z1oIeyKG~6x!lS^`gk#|8!X@1P%CIQexb+SMg{hGPqK~J|%lJUxR{|@bMmCRgEe9JL zy=q`XjcjbD(xwAG5Z=cUrz46?1dvgOxB`pWX$+8-QX`w^L|0li1!6;uY;3rC%rzlk z>sF)p6TPWm{5iztWo5;}>{$vHGh<6dr$#mt_I%MFX1PJaoCjR!XEb6iWLA2y@KuPf z;`(Ps9byJET%3@hEG_99=*g@&DmojadklRDI8Ssoh!c{wr6=8qvq@PTkC7IK`9 zu8;&PSS(COONHs^ZeflJzKirDToHK<%Jzo3Ky>=yj1NJl9|m*%5Qhj(C`U{Dz<pcgUD@7rX z4dQI+AdIB`olKpb?c?d*d$swSh#4_t3==K^j}tBf^Vpd-MNj>X)J!A4Q1 zMmE>Kr_zQyOa%QRDcDdO#A9W1Uqb~1#UVi8UY(ApkprSbazflV5cJFda}}I{g?b8N z_KwV5pqGHV3&+6viyJ!U zieBH~GVmNMwC7LQaPqz zNd|`oYRO4r{Y<1H=Fpp46_{;qRY(JexEDs~f`v@mfH3nJOj&I+*wZ>93W0ke?rbr0 zxsTEPr?XHVB`=trkJkCUgW$sw!jz{IT>-eA(XqE~)xp%+D02@6N5I^s#yMs5m8?I6 z8NrGgu#joMg90)Oz9vkE9|(sK>-hm2PP*?z=R`Xu97f!Za{@LwV0~W{m~$&Cx_++% zCj*Tb&JyNeTqMk_n}j(@?-gccYlXvL{==j{F_NFkVg7c{`(Ah_h!#ij4pCyA8 zD~vTA#lZRo4sZ#$i|A}bo^S{}M7Rumj_`Q!cwvrcITmxSNS-zacs`viTidK^yKA5Z=Z@r_{)13#tUGz4$;lf`vBJ$br4y{L_0qW0Vl1D#T;SZ-wC{iq zgaI-qsFCC5__!W|`HCDXM%2h=Pq%_L58(r0ve-}~o6}NBNSp1u7#upo+q_GJ5$Oke zQBUFaKTk4(i1iT*GSa}ToJJwUtdrag@zcUR5QnjtSv^^FYGgC1FA|-TI|qxIL$kDw z@u$dynIKWiXpuJZb{b@3lWW=BCN|W_#wO3Q(K|PEfEwA@=zFD1i`FB9y~JvB$i}D$ zY=(Wa*ia*zVLwKj_V_?JjK%Oli}zdni7@A%cez>$@3xGZEZ%N0D>fBuw0Nz>t1Vt> zalOTLT>r*Vt(CFB;u?#qEUvJ)+~P8eV-^=#oY%_ef3!6r6G}#+o292&oMN#(+f%tk z3`HhFnlSVAZ41y@?sU;P*^9_=WYGCI$>40)w;@1h19*!8opQD>5DtU&`*@*q@~#k_ z9eG%olX0Ukd)p|?xy3sU=#wK7|3DI>kk~IA1OHvP1gvkcLxD1I!0#0v59Zq_>8Kps zPk1W0P`Cm-R=5&eE?fnk;c;Bw|HhXp&`}LCZW5jk);HThUjXL6BGP6tc)f5f_(kC* z;BCTn;CF?Wg7*p6gZ)@1^s@q-#^Wz0RwB_`GFE~4dIsvN!6Sv&fXjr}f-e-_0G=(p z5v(suqwFSdo#>mv_X=+X>wE2Bqu=ZEtYo}`4E;JoWHf=_5Pc_D-*^XoH~1^j_kj6U zLN;_SI3fHYIEZ5~>YsqS3-1H#i@?zLgNH$n)93&aC1P|CJVp2rxJvji_-f&!;G2Yx zfsbQRf4~RADKc-UkprST_#!gL6TQU-THIm698e_DNDA~3BWh&hh^H8hqmg1mjcoh` zEkDI#Lyc_w@VA3nulIj)w+}{wBJcb$j(7^wG)Uj~3L9!<LeGi~HL~%OD}HqUUl1dTWaB8$ za`cASP$L^h{ON8+=xwo~MmBzU_RuuwE3u&lj<-4*WjXp*jHr=~qnPC=AvV;=ra>i^ zpLVj{QzIKcWvzbD|L$T$k!&1|w;W}O4K=cHRNm?co0Hg3BO5UqC^98){_Jpe2@{--``3vhh=A`T4US z^>YtM@z0i{rIw?=h$Cub|zOycBWbKFjUuik&Ec*wgp++_hT5b6mEH>20#?Knd&l%A5{+}Wf#?e~K(Rt#C8reA7U^$v3Hq^+*&qmA7 zWnx2(EPmq7Cd<*4VnmH>9BsB7-7YrN$fiMCEkAdO4K=dyv)%IZn6|(d=+_aWMG*C?iH)O!mZNdvh#J{AIz$`UlTHvDYGmW*u;pi#*c|5Xe938V9@!<{}Mrvf^Ck>mm`S+zDS$)M#8_ZO)?g`QO%$L0)=>&X}!hLz-D4dLD+PkIYo(7nFcmxnjZt@f+I zD%subS0mLa?xyAbk-UkVVKGYzYwyA{yuVlzO` zDLHGRSGjN!jy25L6#mRp9o&^G{8L8rY%BtQq$%Mg7S~(MTfB|UTFq|wZvV(su1ti3 zl!9&zC7u^X;H}Oo)!ht9zkF_7ekU)Zi5bFsEaqLsMn9mr;qXf%-G3#k9QXBm{Ch@o zQXuFT!3p!Xfq9u(p~W$a$7^=?-0PoGo$0yI?|T#0kK-2R?VZM*e(bjB`cd1$2Q3@^ zTaU5l-I#c1L-6I1yqSzazXncts?PQFDvS9J3gbb)22SjkTKYLi(?j#x46RM*&g!%2wRPnvBctf zi&t5^*5XYTZ?|}-#d|H@XYoPaENOak)XH$M!!~-*;%*l69z|o5XYnYD%PgL1ag|~A zU%zBehF!m8Pk5;@f{lL3p6L1|d&2r9d&2r9d%{gtS^bhd(e+FAgb%!rn1%KuADXAKTR3l zZ0WCPc1JznpAtXSGwp$$vJ%#>_!BO)^q9rtE!J=NJI(Vmo4T-m!=LaHORu*$zRF6h zwRjU5cat$}w|J+;doA8)@j)_=6A+GC%qwO_4|?ovVBMJL=@rPaIM3oy7MEE()#56P z^*aj1r+!DF@KVcWWsn<~WayU^3hS2?@~R}lRu&L$vRJ>PP;~u{!rq?!0on-bcN7Zi zcN7ZicNCK2SokCfQy@$ZLqzz0c}bzR`QLp~`-T)e+$ITeQ(O1R46`4-ob2YB{NE!Hm(6n%}QZzP}Y*>APD$>O+v zy`UKB*9!_Cpi!<@SifFSSifFSnD2-&Hhk=Y;V^liS2ow;B8y8r=H$V7Yb!&)pm2~^ zSihi9crk5+>nzqUC=^}4pm4BPb^~pM^$QAx^$QAxck>x6#^^&9$ny&Cw^+ZRP;~u* zLSg-aLb-UaUr;ElUr;ElUr;Dqm=6C1p8uF(_FuoCP%`uj3Wfh4Ur@;38wmOZg~F@o zRCuk$o5=F}VY|gU!EuwZ*UH#u@j;7^TFi$@m>(iRi@RBzV{x9vd=-%VaEUu*Clbz7 zi>oZ2Pv$*&2(=b3wRok)Yb@SKF7nE5CCd-4Ch`bx{yBS?80i^(LLTMu0rF^%50lUI z_&E72k5m4yy3RGWiXw{R^j-=z*6_5J1hBkHg&J#%2|<(v3N%v4jS?kF0ksMS&=L?a zh!><_1r+dVDq5kuBARLtOo*`%5)(pVKio9M5NHe^s8NHlK}^v2`v2$b*_|HBNoHqu z=A7T2nVsFccONrDke)8-p-j(^bP>}tC0)vNrKBe_JxkJ+gSawVR#HsQ$)wIsJbCay za!K-J$&uvITsK=}X5HwLoC-Hhy}TrWi6MDgIkg%IY(T^akp%D%PzP4hs(qW znLu)aM6)GYEYS*y>LuDE(Pu6?@nU;kB2nk&l1yxrXuFFVi;wFZm4_w&ONmZP^qoW( zB#BWVt5Ei7Xpu zoJbB$+?&(5^lW1EgoZ`(i02fVmOH#J0)Euo-BtV{<=-DCwU}%Z(^pQ}1{`$)$?j8C zBaa7Z&wioyd%0Pxt9`xLr{6R7OFjFCJo`_KJ^xMP@*)y$hjP&H)_XkmJN1VT3Or_joMvl1R8ApGM^GT|_5u;{uII=Uho_Bw-eP%NN&A_eeY>$QO32Gx zP;sLE@M~SB1Ig}_0xu66CXWY)`oo*O$c`sE<3J`ag+`1Nc+CJ8E|C{A2%Hn@u?Gx> z0V`OJKzR^$Kjcc9OO`PdhkCN77vP^fuG#s}MMXC}3JIozu%Htgo(d>8{0IWexMAJ^ zCRn@9)!W^?iRm(9U-)e7c(rHG$DzLcLO^?K2pz#SfGwp7ye7o-XFh`Zvr>3*%bo-9 zekiC=k;1#lOb1+nOs(hIO?dGHp3MU?%@+&O{I;1o0RrWQ=MUev zQTuNIs<*`3FQ%1aDI1tcqu|KJS^k)RWh4$^Uu%8M$)@O0!{-v1N zW*sh>{4*JM90PA7GuCcj551k9BC(ViW)t}@;Hf`uoN*IRBT$b`_Ke{T9PU2xnTsM3 z1=bi~-NtyI;*f}dn+;l;Zkf8@L95g!`!HqRU_6`6lw+^HTJe*;FB)SXWaZ#j-b9xJTgicpWu zeM)%oS)v2LuTY?(K;D^TItU9|0jx)wz*Pt=Be2al*l;&aV{E`sl*a*yVjf_@Ci(i1 zK%Vy$c*y{76yZjDFTn460MY+9<82SY=lr0xWNF|zZqXLA{ha92{7u8L&x{t9k=)3p@F+WDFcWezU2Y;8DK4d zv)F>6sDm6t!LM;qLwttV0bQ2yl=HdawUpr5|GW^!NP&Dt;R74s)mW6%!M8&_0$UC6 zOi3n%b2LXI^UYg4^Ie`fN0;(DL>B(wS1b~B=d9sn!P8&eJsyuEpMe+uWLz!D1eXfZ z=Z$^-_PD(>p8e0ppZt4dDjIc2{kdXzQ^C{2?R}4TLwIoy8xA1u9YmtQ-wkkq2W<3! z{rmT9urlz%yTXpCyae=^4qPsJ*h&w`Ga3uI0}BkW`WC=JJ%IQ@^LQV5ym5xN9K02L z^-M)QEHc0*0K1dY!#?(alMJw9qI{@fdG{IK&UfUAE_-ZxzXzOYfFG3NHKJU_Ny~8U zVFTnf&)Pf0!2=$TBkB2lxD@}V_Qg21^fb3R&j8n=nr$edhldb!mn?!_9@fzqPTI=d zl)W68kJ~ZX9A(7M&WiT82GUXP<~HVNeZDrbIUR79(Y_$r96ZGLB+R}Oc|Ih}E=9Jl z-C6D?CJt`S<|gO&o?X*>_R8L~H{6`fshW2q+jkJ2V3SjJAe)UGxMoF+-Na*nJKzL@ z?tmkPdb!+97ww+-K}~l6vRRS#aAQ1wQ?8WDbi;)yoj2Njq@u58)L=wLjJ@iUfY+rW>xT!ethE f_-W*9mFb52;y>S(er~$q!hBrpCT~MoDC+6|>lu$A diff --git a/components/esp8266/lib/libpp_dbg.a b/components/esp8266/lib/libpp_dbg.a index 9ba53cd420aac104f02a6bc6ca8fbabdec0068ad..9aa2c92bfa05be097c0d7d2228856272356d7bfb 100644 GIT binary patch delta 68095 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 Dc>N!S From 377bd1dd01063a237b93f4e55e4a6c43b5b4b516 Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Mon, 18 Dec 2023 16:28:18 +0800 Subject: [PATCH 42/59] Dropped fragmented aggregated packets (CVE-2020-26142) --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libpp.a | Bin 248218 -> 248934 bytes components/esp8266/lib/libpp_dbg.a | Bin 267110 -> 268090 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 8b7e15491..472c4493d 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,7 +1,7 @@ gwen: core: db51cd0 net80211: 58ed843 - pp: ff7160f + pp: 12d8a11 espnow: db51cd0 smartconfig: 3.0.0/283eacca diff --git a/components/esp8266/lib/libpp.a b/components/esp8266/lib/libpp.a index 806d3ed5024c817e5652c2f5ef4d02565d36c2a9..f95ae130f02401c4ec5251a67b82a05b85246a88 100644 GIT binary patch delta 25138 zcmc(neSB2K+4s+ELIRu(EMW<65jL9uiv|oKK!hMmj1X+p$crLk32y<)i-@#FOWmjx z(L2@1B8OVEv{FkI1w{?5s6i>xTU1nPQBl$QUR1 z|FwAF0w>_dgn|E0>D;!S{J-o#@>jiUw_lL{dV2EC%$)6!4(rtReI4IxZ%zcS=vkGs z{lB|hely)U0BhDF~e7Wm@uAA07j+5p%B}g16=G@`j?)A)3 z3%rv9RdphJSMwXM94!9Jd!O$5*CQRi`aTfNaz0NBK37y6+Vw%}IbBwyfucd@9917x zk=T&?v$NC(t<8>eRx}dLi&i@FWl2+w8u`ZWpx1YJUwJ830&FoOa z@X9{%%9fa_eZO>2)`YR@FEqO{@Zg}!hp6Ytm1Cz4$vKu@zWYRVMRW0sJ>v0}_<8k5 z&yJ{O?K=yDR<*AV&U>si7Tq!n$<)AxZ;FZ@kBupQE^(rvwzN-CX-n~QC(1iH^%W{! zIi!5|#^!cTZDC+sf%D+#LBVtK)KLF6JlQSwBy~r69R1Ywpb5lCHU7Q~4 zH018q$kE3~1k#W9IeNUMRQ2DUUKSXT95^UT{f3suUOo|NZ7zSY$I+seLFpsMm#7Vt z8*VDigR{G}^DH%QR!==sF0^&jG6D`8TT5>LRWPvky@RiOvg`OG*}LLdM;*s$x*RS~m%_`3y9#x({vV6Dlyn*Vh^yl`pv?fC(y}ZK%)fsLz#;_65ahj83 z2aoG88MG8}GIHm6or+Z`dAMgf&-)Hv{pGzkUg3G4x2JVQODr&2`6pp*_UC(5Z9^dA zm=nC|`Qpy~tEN9uHLP}eV9#~VxRzEY_~?KQfj!?i&liV+Yk=d9I<*kqbuvD0t=$qB z_gANOXJF59XWXZ)j(Ub;ELC}1TSrENX~pG^dJ;;)i7H3^iDemwTh$-M=>FDVCoy}w zlX{HmxbZlDw+6dUEnjgwqm$!|y|;CK`kMZOM_e9>KKp~i(Jp89pI>~=)t8UG?vh() z4(i;T;jErncge)O^y0isr)%69@tdO3qM|9G>PSs>G!!`&k3?#UpS`kgZQA2`>so`8 zCI-d^vnT%fWOeb)SEiM2ow>TTG=Eu!Q&duN{ZOpoW!u{ok8@U~ImH_t0FJ6rcXn$m zZN&TT#QgMGTi>1d=fm@Uo_)#nt)*S7hlVb>&YMuEhN;C~qEPi6TH9J2aB|WV9h5tx zvv_4Lp$oN`$01W&qxsIU=$Scp1k{hHjGg4YQmAqgbC`*4bfQ;vP_xLFe)(y`MXH7v z%vLj)X8g2O{g8ke9@{p<)2O^MruF@|RYsK)+j6`Li`#kp`@L}qO=ej`c18uSr-{tG z7&!9kxN>GMma@@KtSp!~uRP$4SQ#!4s1fObaivZ)+l@p@8ltO)p%T&6`GR0r~L1E#a`i*059Vwuv%Jtt4bx~Olp@rje_p9I)G+h%)=N?!BHXmn!Ev7NE(oZs$j z4mcTKom8K*WX~5TxygC=c2^lqhl|u^rA{{8TCDC@s@N+kQQb1IzZY)|HvOPPjZxn1 z!_+!8**jyn`dA%IsvmUsa!0881UD8H!^VR2azyOV&O?X{?Ged+5u=Fv5IYOMg;XKZ z<`5$GRbBT(qzu%_6A|fi1dNS+DWIRP7duxXb0sot;7{PO!v6!tKkeuQpr`2Fys=q< z0Zp4nr~~O)#vq0#gJjdKWhyh>n?6bf)s@~oarJll)egoW9V!l%QAA)5$?P2)UyfKJ zycsD2ZO9!FY4<21?!?;u8Sl>P0)4#`W7Ppl+V(?q=uGeJa zBAe?9Q`7x0AXoGviU@3)Y$kjRm_^j%0A6|YIfo(wb+Rm0!nqk4ma$+ZfOkeVe21ks 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
b0Bksd7Z@&)g~Menh_K15k+QPDd2ga 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 delta 25146 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 a8e#SLg7zH#L;rIECg*7nD-Q>bfBio>zh!s; diff --git a/components/esp8266/lib/libpp_dbg.a b/components/esp8266/lib/libpp_dbg.a index 9aa2c92bfa05be097c0d7d2228856272356d7bfb..d0007ae0c9369d8e00d658d5dcd2e9cd63705c13 100644 GIT binary patch delta 27995 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 From d4dca4ed002206347e4d5cbb7ebdef1f7d0f7e99 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 27 Dec 2023 14:20:05 +0800 Subject: [PATCH 43/59] fix(tcpip_adapter): Fixed that IPv6 DNS can not work --- components/tcpip_adapter/tcpip_adapter_lwip.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index bf3f8bad9..3faabd07e 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -825,8 +825,14 @@ esp_err_t tcpip_adapter_set_dns_info(tcpip_adapter_if_t tcpip_if, tcpip_adapter_ return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; } +#if CONFIG_LWIP_IPV6 + if (!IP_IS_V4(&dns->ip) && !IP_IS_V6(&dns->ip)) { + IP_SET_TYPE_VAL(dns->ip, IPADDR_TYPE_V4); + } +#else ESP_LOGD(TAG, "set dns if=%d type=%d dns=%x", tcpip_if, type, ip_2_ip4(&(dns->ip))->addr); IP_SET_TYPE_VAL(dns->ip, IPADDR_TYPE_V4); +#endif if (tcpip_if == TCPIP_ADAPTER_IF_STA || tcpip_if == TCPIP_ADAPTER_IF_ETH) { dns_setserver(type, &(dns->ip)); From 0cac4f8cf3750a2d9380ebcf46e310be78af79f1 Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Tue, 16 Apr 2024 16:43:50 +0800 Subject: [PATCH 44/59] fix(wpa_supplicant): (PEAP client) Update Phase 2 auth requirements --- .../wpa_supplicant/src/eap_peer/eap_peap.c | 37 +++++++++++++++++-- .../src/eap_peer/eap_tls_common.c | 10 ++++- .../src/eap_peer/eap_tls_common.h | 5 +++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/components/wpa_supplicant/src/eap_peer/eap_peap.c b/components/wpa_supplicant/src/eap_peer/eap_peap.c index 54e0ac0c6..38890f4bb 100644 --- a/components/wpa_supplicant/src/eap_peer/eap_peap.c +++ b/components/wpa_supplicant/src/eap_peer/eap_peap.c @@ -66,6 +66,7 @@ struct eap_peap_data { u8 cmk[20]; int soh; /* Whether IF-TNCCS-SOH (Statement of Health; Microsoft NAP) * is enabled. */ + enum { NO_AUTH, FOR_INITIAL, ALWAYS } phase2_auth; }; @@ -114,6 +115,19 @@ eap_peap_parse_phase1(struct eap_peap_data *data, wpa_printf(MSG_DEBUG, "EAP-PEAP: Require cryptobinding"); } + if (os_strstr(phase1, "phase2_auth=0")) { + data->phase2_auth = NO_AUTH; + wpa_printf(MSG_DEBUG, + "EAP-PEAP: Do not require Phase 2 authentication"); + } else if (os_strstr(phase1, "phase2_auth=1")) { + data->phase2_auth = FOR_INITIAL; + wpa_printf(MSG_DEBUG, + "EAP-PEAP: Require Phase 2 authentication for initial connection"); + } else if (os_strstr(phase1, "phase2_auth=2")) { + data->phase2_auth = ALWAYS; + wpa_printf(MSG_DEBUG, + "EAP-PEAP: Require Phase 2 authentication for all cases"); + } #ifdef EAP_TNC if (os_strstr(phase1, "tnc=soh2")) { data->soh = 2; @@ -145,6 +159,7 @@ eap_peap_init(struct eap_sm *sm) data->force_peap_version = -1; data->peap_outer_success = 2; data->crypto_binding = OPTIONAL_BINDING; + data->phase2_auth = FOR_INITIAL; if (config && config->phase1 && eap_peap_parse_phase1(data, config->phase1) < 0) { @@ -449,6 +464,18 @@ eap_tlv_validate_cryptobinding(struct eap_sm *sm, return 0; } +static bool peap_phase2_sufficient(struct eap_sm *sm, + struct eap_peap_data *data) +{ + if ((data->phase2_auth == ALWAYS || + (data->phase2_auth == FOR_INITIAL && + !tls_connection_resumed(sm->ssl_ctx, data->ssl.conn) && + !data->ssl.client_cert_conf) || + data->phase2_eap_started) && + !data->phase2_eap_success) + return false; + return true; +} /** * eap_tlv_process - Process a received EAP-TLV message and generate a response @@ -565,6 +592,11 @@ eap_tlv_process(struct eap_sm *sm, struct eap_peap_data *data, " - force failed Phase 2"); resp_status = EAP_TLV_RESULT_FAILURE; ret->decision = DECISION_FAIL; + } else if (!peap_phase2_sufficient(sm, data)) { + wpa_printf(MSG_INFO, + "EAP-PEAP: Server indicated Phase 2 success, but sufficient Phase 2 authentication has not been completed"); + resp_status = EAP_TLV_RESULT_FAILURE; + ret->decision = DECISION_FAIL; } else { resp_status = EAP_TLV_RESULT_SUCCESS; ret->decision = DECISION_UNCOND_SUCC; @@ -939,8 +971,7 @@ eap_peap_decrypt(struct eap_sm *sm, struct eap_peap_data *data, /* EAP-Success within TLS tunnel is used to indicate * shutdown of the TLS channel. The authentication has * been completed. */ - if (data->phase2_eap_started && - !data->phase2_eap_success) { + if (!peap_phase2_sufficient(sm, data)) { wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase 2 " "Success used to indicate success, " "but Phase 2 EAP was not yet " @@ -1194,7 +1225,7 @@ eap_peap_has_reauth_data(struct eap_sm *sm, void *priv) { struct eap_peap_data *data = priv; return tls_connection_established(sm->ssl_ctx, data->ssl.conn) && - data->phase2_success; + data->phase2_success && data->phase2_auth != ALWAYS; } diff --git a/components/wpa_supplicant/src/eap_peer/eap_tls_common.c b/components/wpa_supplicant/src/eap_peer/eap_tls_common.c index 9c93f6d0c..31299b7f6 100755 --- a/components/wpa_supplicant/src/eap_peer/eap_tls_common.c +++ b/components/wpa_supplicant/src/eap_peer/eap_tls_common.c @@ -84,7 +84,7 @@ static void eap_tls_params_from_conf1(struct tls_connection_params *params, static int eap_tls_params_from_conf(struct eap_sm *sm, struct eap_ssl_data *data, struct tls_connection_params *params, - struct eap_peer_config *config) + struct eap_peer_config *config, int phase2) { os_memset(params, 0, sizeof(*params)); if (sm->workaround && data->eap_type != EAP_TYPE_FAST) { @@ -119,6 +119,12 @@ static int eap_tls_params_from_conf(struct eap_sm *sm, return -1; } + if (!phase2) + data->client_cert_conf = params->client_cert || + params->client_cert_blob || + params->private_key || + params->private_key_blob; + return 0; } @@ -196,7 +202,7 @@ int eap_peer_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data, data->eap = sm; data->eap_type = eap_type; data->ssl_ctx = sm->ssl_ctx; - if (eap_tls_params_from_conf(sm, data, ¶ms, config) < 0) /* no phase2 */ + if (eap_tls_params_from_conf(sm, data, ¶ms, config, data->phase2) < 0) /* no phase2 */ return -1; if (eap_tls_init_connection(sm, data, config, ¶ms) < 0) diff --git a/components/wpa_supplicant/src/eap_peer/eap_tls_common.h b/components/wpa_supplicant/src/eap_peer/eap_tls_common.h index 1a5e0f89e..50390c4ce 100644 --- a/components/wpa_supplicant/src/eap_peer/eap_tls_common.h +++ b/components/wpa_supplicant/src/eap_peer/eap_tls_common.h @@ -73,6 +73,11 @@ struct eap_ssl_data { * eap_type - EAP method used in Phase 1 (EAP_TYPE_TLS/PEAP/TTLS/FAST) */ u8 eap_type; + + /** + * client_cert_conf: Whether client certificate has been configured + */ + bool client_cert_conf; }; From c611c41d0a4c425cba58c1b7ad5ea5bcfda99992 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Mon, 21 Oct 2024 20:31:18 +0800 Subject: [PATCH 45/59] feat: Add fm25q16a patch --- components/esp8266/ld/esp8266.project.ld.in | 18 + components/esp8266/source/startup.c | 17 +- components/spi_flash/CMakeLists.txt | 12 +- components/spi_flash/Kconfig | 14 + components/spi_flash/component.mk | 9 +- components/spi_flash/include/spi_flash.h | 4 + components/spi_flash/src/patch/common.c | 218 +++++++ components/spi_flash/src/patch/fm25q16a.c | 549 ++++++++++++++++++ .../spi_flash/src/patch/spi_flash_patch.h | 58 ++ components/spi_flash/src/patch/th25q16hb.c | 294 +--------- 10 files changed, 923 insertions(+), 270 deletions(-) create mode 100644 components/spi_flash/src/patch/common.c create mode 100644 components/spi_flash/src/patch/fm25q16a.c create mode 100644 components/spi_flash/src/patch/spi_flash_patch.h diff --git a/components/esp8266/ld/esp8266.project.ld.in b/components/esp8266/ld/esp8266.project.ld.in index 54d7bb48b..a1c4ba09e 100644 --- a/components/esp8266/ld/esp8266.project.ld.in +++ b/components/esp8266/ld/esp8266.project.ld.in @@ -94,6 +94,15 @@ SECTIONS _iram_end = ABSOLUTE(.); } > iram0_0_seg + .patch.text : + { + . = ALIGN (4); + _iram_patch_text_start = ABSOLUTE(.); + *(.flash.patch.literal .flash.patch.text) + _iram_patch_text_end = ABSOLUTE(.); + _iram_text_end = ABSOLUTE(.); + } > iram0_0_seg + ASSERT(((_iram_end - ORIGIN(iram0_0_seg)) <= LENGTH(iram0_0_seg)), "IRAM0 segment data does not fit.") @@ -155,6 +164,15 @@ SECTIONS _bss_end = ABSOLUTE(.); } > dram0_0_seg + .patch.bss : + { + . = ALIGN (4); + _iram_patch_bss_start = ABSOLUTE(.); + *(.flash.patch.bss) + _iram_patch_bss_end = ABSOLUTE(.); + *(.flash.patch.rodata) + } > dram0_0_seg + ASSERT(((_bss_end - ORIGIN(dram0_0_seg)) <= LENGTH(dram0_0_seg)), "DRAM segment data does not fit.") diff --git a/components/esp8266/source/startup.c b/components/esp8266/source/startup.c index 42fc18825..ecd020578 100644 --- a/components/esp8266/source/startup.c +++ b/components/esp8266/source/startup.c @@ -93,10 +93,6 @@ static void user_init_entry(void *param) esp_set_cpu_freq(ESP_CPU_FREQ_160M); #endif -#ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 - assert(th25q16hb_apply_patch_0() == 0); -#endif - app_main(); vTaskDelete(NULL); @@ -109,6 +105,7 @@ void call_start_cpu(size_t start_addr) extern int _bss_start, _bss_end; extern int _iram_bss_start, _iram_bss_end; + extern int _iram_patch_bss_start, _iram_patch_bss_end; #ifdef CONFIG_BOOTLOADER_FAST_BOOT REG_SET_BIT(DPORT_CTL_REG, DPORT_CTL_DOUBLE_CLK); @@ -155,6 +152,18 @@ void call_start_cpu(size_t start_addr) for (p = &_iram_bss_start; p < &_iram_bss_end; p++) *p = 0; + + for (p = &_iram_patch_bss_start; p < &_iram_patch_bss_end; p++) + *p = 0; + +#ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 + assert(th25q16hb_apply_patch_0() == 0); +#endif + +#ifdef CONFIG_ENABLE_FM25Q16A_PATCH_0 + assert(fm25q16a_apply_patch_0() == 0); +#endif + __asm__ __volatile__( "rsil a2, 2\n" "movi a1, _chip_interrupt_tmp\n" diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index 255eff8e7..7b6adf802 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -5,8 +5,16 @@ if(BOOTLOADER_BUILD) set(srcs "${srcs}" "port/port.c") set(priv_requires "bootloader_support") else() - if(CONFIG_ENABLE_TH25Q16HB_PATCH_0) - list(APPEND srcs "src/patch/th25q16hb.c") + + if (CONFIG_ENABLE_SPI_FLASH_PATCH) + list(APPEND srcs "src/patch/common.c") + if(CONFIG_ENABLE_TH25Q16HB_PATCH_0) + list(APPEND srcs "src/patch/th25q16hb.c") + endif() + + if(CONFIG_ENABLE_FM25Q16A_PATCH_0) + list(APPEND srcs "src/patch/fm25q16a.c") + endif() endif() set(priv_requires "esp8266" "freertos" "bootloader_support") diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index 3d97ba109..e67bd5f10 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -1,12 +1,26 @@ menu "SPI Flash" menu "Patch" + config ENABLE_SPI_FLASH_PATCH + bool "Enable TH25Q16HB Patch 0" + default n + config ENABLE_TH25Q16HB_PATCH_0 bool "Enable TH25Q16HB Patch 0" + depends on ENABLE_SPI_FLASH_PATCH default n help WARNING: If you don't use TH25Q16HB, you must not enable this option. Although you use TH25Q16HB, you should ask your flash manufacturer if your flash need use this patch. + + config ENABLE_FM25Q16A_PATCH_0 + bool "Enable FM25Q16A Patch 0" + depends on ENABLE_SPI_FLASH_PATCH + default n + help + WARNING: If you don't use FM25Q16A, you must not enable this option. + Although you use FM25Q16A, you should ask your flash manufacturer + if your flash need use this patch. endmenu endmenu diff --git a/components/spi_flash/component.mk b/components/spi_flash/component.mk index c4dfc940a..6e574d959 100644 --- a/components/spi_flash/component.mk +++ b/components/spi_flash/component.mk @@ -16,7 +16,14 @@ ifdef IS_BOOTLOADER_BUILD COMPONENT_SRCDIRS += port COMPONENT_OBJS += port/port.o else -ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 +ifdef CONFIG_ENABLE_SPI_FLASH_PATCH COMPONENT_SRCDIRS += src/patch +ifdef CONFIG_ENABLE_TH25Q16HB_PATCH_0 +COMPONENT_SRCDIRS += src/patch/th25q16hb.c +endif + +ifdef CONFIG_ENABLE_FM25Q16A_PATCH_0 +COMPONENT_SRCDIRS += src/patch/fm25q16a.c" +endif endif endif diff --git a/components/spi_flash/include/spi_flash.h b/components/spi_flash/include/spi_flash.h index 7dfeb6a24..13e10f09a 100644 --- a/components/spi_flash/include/spi_flash.h +++ b/components/spi_flash/include/spi_flash.h @@ -213,6 +213,10 @@ int esp_patition_copy_ota1_to_ota0(const void *partition_info); int th25q16hb_apply_patch_0(void); #endif +#ifdef CONFIG_ENABLE_FM25Q16A_PATCH_0 +int fm25q16a_apply_patch_0(); +#endif + #ifdef __cplusplus } #endif diff --git a/components/spi_flash/src/patch/common.c b/components/spi_flash/src/patch/common.c new file mode 100644 index 000000000..84aa395d0 --- /dev/null +++ b/components/spi_flash/src/patch/common.c @@ -0,0 +1,218 @@ +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_attr.h" +#include "spi_flash.h" +#include "priv/esp_spi_flash_raw.h" +#include "FreeRTOS.h" + +#include "esp8266/eagle_soc.h" +#include "esp8266/pin_mux_register.h" +#include "esp8266/spi_register.h" +#include "esp8266/spi_struct.h" + +#include "driver/gpio.h" +#include "esp_attr.h" + +#include "spi_flash_patch.h" + +#define SPI_FLASH SPI0 +#define SPI_BLOCK_SIZE 32 +#define ADDR_SHIFT_BITS 8 + +extern void Cache_Read_Disable_2(void); +extern void Cache_Read_Enable_2(); +extern void vPortEnterCritical(void); +extern void vPortExitCritical(void); + +void FLASH_PATCH_TEXT_ATTR patch_delay(int ms) +{ + for (volatile int i = 0; i < ms; i++) { + for (volatile int j = 0; j < 7800; j++) { + } + } +} + +void FLASH_PATCH_TEXT_ATTR spi_enter(spi_state_t *state) +{ + vPortEnterCritical(); + Cache_Read_Disable_2(); + + Wait_SPI_Idle(&g_rom_flashchip); + + state->io_mux_reg = READ_PERI_REG(PERIPHS_IO_MUX_CONF_U); + state->spi_clk_reg = SPI_FLASH.clock.val; + state->spi_ctrl_reg = SPI_FLASH.ctrl.val; + state->spi_user_reg = SPI_FLASH.user.val; + + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user.flash_mode = 0; + SPI_FLASH.user.usr_miso_highpart = 0; + + CLEAR_PERI_REG_MASK(PERIPHS_IO_MUX_CONF_U, SPI0_CLK_EQU_SYS_CLK); + + SPI_FLASH.user.cs_setup = 1; + SPI_FLASH.user.cs_hold = 1; + SPI_FLASH.user.usr_mosi = 1; + + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user.flash_mode = 0; + + SPI_FLASH.ctrl.fread_qio = 0; + SPI_FLASH.ctrl.fread_dio = 0; + SPI_FLASH.ctrl.fread_quad = 0; + SPI_FLASH.ctrl.fread_dual = 0; + + SPI_FLASH.clock.val = 0; + SPI_FLASH.clock.clkcnt_l = 3; + SPI_FLASH.clock.clkcnt_h = 1; + SPI_FLASH.clock.clkcnt_n = 3; + + SPI_FLASH.ctrl.fastrd_mode = 1; + + while (SPI_FLASH.cmd.usr) { + ; + } +} + +void FLASH_PATCH_TEXT_ATTR spi_exit(spi_state_t *state) +{ + while (SPI_FLASH.cmd.usr) { + ; + } + + WRITE_PERI_REG(PERIPHS_IO_MUX_CONF_U, state->io_mux_reg); + + SPI_FLASH.ctrl.val = state->spi_ctrl_reg; + SPI_FLASH.clock.val = state->spi_clk_reg; + SPI_FLASH.user.val = state->spi_user_reg; + + Cache_Read_Enable_2(); + vPortExitCritical(); +} + +static void FLASH_PATCH_TEXT_ATTR spi_trans_block(bool write_mode, + uint32_t cmd, + uint32_t cmd_bits, + uint32_t addr, + uint32_t addr_bits, + uint8_t *data, + uint32_t data_bytes, + uint32_t dummy_bits) +{ + if ((uint32_t)data & 0x3) { + ROM_PRINTF(FLASH_PATCH_STR("ERROR: data=%p\n"), data); + return; + } + + if (cmd_bits) { + SPI_FLASH.user.usr_command = 1; + SPI_FLASH.user2.usr_command_value = cmd; + SPI_FLASH.user2.usr_command_bitlen = cmd_bits - 1; + } else { + SPI_FLASH.user.usr_command = 0; + SPI_FLASH.user2.usr_command_bitlen = 0; + } + + if (addr_bits) { + SPI_FLASH.user.usr_addr = 1; + SPI_FLASH.addr = addr << ADDR_SHIFT_BITS; + SPI_FLASH.user1.usr_addr_bitlen = addr_bits - 1; + } else { + SPI_FLASH.user.usr_addr = 0; + SPI_FLASH.user1.usr_addr_bitlen = 0; + } + + if (dummy_bits) { + SPI_FLASH.user.usr_dummy = 1; + SPI_FLASH.user1.usr_dummy_cyclelen = dummy_bits - 1; + } else { + SPI_FLASH.user.usr_dummy = 0; + SPI_FLASH.user1.usr_dummy_cyclelen = 0; + } + + if (data_bytes) { + if (write_mode) { + int words = (data_bytes + 3) / 4; + uint32_t *p = (uint32_t *)data; + + SPI_FLASH.user.usr_mosi = 1; + SPI_FLASH.user.usr_miso = 0; + SPI_FLASH.user1.usr_mosi_bitlen = data_bytes * 8 - 1; + SPI_FLASH.user1.usr_miso_bitlen = 0; + for (int i = 0; i < words; i++) { + SPI_FLASH.data_buf[i] = p[i]; + } + } else { + int words = (data_bytes + 3) / 4; + + SPI_FLASH.user.usr_mosi = 0; + SPI_FLASH.user.usr_miso = 1; + SPI_FLASH.user1.usr_miso_bitlen = data_bytes * 8 - 1; + SPI_FLASH.user1.usr_mosi_bitlen = 0; + + for (int i = 0; i < words; i++) { + SPI_FLASH.data_buf[i] = 0; + } + } + } else { + SPI_FLASH.user.usr_mosi = 0; + SPI_FLASH.user1.usr_mosi_bitlen = 0; + SPI_FLASH.user.usr_miso = 0; + SPI_FLASH.user1.usr_miso_bitlen = 0; + } + + SPI_FLASH.cmd.usr = 1; + while (SPI_FLASH.cmd.usr) { + ; + } + + if (!write_mode && data_bytes) { + int words = (data_bytes + 3) / 4; + uint32_t *p = (uint32_t *)data; + + for (int i = 0; i < words; i++) { + p[i] = SPI_FLASH.data_buf[i]; + } + } +} + +void FLASH_PATCH_TEXT_ATTR spi_trans(bool write_mode, + uint32_t cmd, + uint32_t cmd_bits, + uint32_t addr, + uint32_t addr_bits, + uint8_t *data, + uint32_t data_bytes, + uint32_t dummy_bits) + +{ + if (!data_bytes || data_bytes <= SPI_BLOCK_SIZE) { + return spi_trans_block(write_mode, cmd, cmd_bits, addr, + addr_bits, data, data_bytes, dummy_bits); + } + + for (int i = 0; i < data_bytes; i += SPI_BLOCK_SIZE) { + uint32_t n = MIN(SPI_BLOCK_SIZE, data_bytes - i); + + spi_trans_block(write_mode, cmd, cmd_bits, addr + i, + addr_bits, data + i, n, dummy_bits); + } +} diff --git a/components/spi_flash/src/patch/fm25q16a.c b/components/spi_flash/src/patch/fm25q16a.c new file mode 100644 index 000000000..dbd290afd --- /dev/null +++ b/components/spi_flash/src/patch/fm25q16a.c @@ -0,0 +1,549 @@ +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_attr.h" +#include "spi_flash.h" +#include "priv/esp_spi_flash_raw.h" +#include "FreeRTOS.h" + +#include "esp8266/eagle_soc.h" +#include "esp8266/pin_mux_register.h" +#include "esp8266/spi_register.h" +#include "esp8266/spi_struct.h" +#include "driver/gpio.h" +#include "spi_flash_patch.h" + +#define DEBUG(fmt,...) fm_printf(fmt, ##__VA_ARGS__) +#define INFO(fmt,...) fm_printf(fmt, ##__VA_ARGS__) +#define ERROR(fmt,...) fm_printf(fmt, ##__VA_ARGS__) + +#ifndef IRAM_FUNC_ATTR +#define IRAM_FUNC_ATTR +#endif + +#define fm_printf ROM_PRINTF + +extern bool IRAM_FUNC_ATTR spi_user_cmd(spi_cmd_dir_t mode, spi_cmd_t *p_cmd); +extern uint32_t IRAM_FUNC_ATTR spi_flash_get_id(void); + +static void FLASH_PATCH_TEXT_ATTR fm_send_spi_cmd(uint8_t cmd, uint8_t cmd_len, uint32_t addr, uint8_t addr_len, void* mosi_data, int mosi_len, void* miso_data, int miso_len, uint8_t dummy_bits) +{ + bool write_mode = false; + uint32_t data_bytes = 0; + + if (mosi_len > 0) { + write_mode = true; + data_bytes = mosi_len / 8; + } else if (miso_len > 0) { + write_mode = false; + data_bytes = miso_len / 8; + } + uint32_t data[(data_bytes+3)/4]; + if (write_mode && mosi_data) { + memcpy(data, mosi_data, data_bytes); + } + + spi_trans(write_mode, cmd, cmd_len, addr, addr_len, (uint8_t *)data, data_bytes, dummy_bits); + + if (!write_mode && miso_data) { + memcpy(miso_data, data, data_bytes); + } +} + +static void FLASH_PATCH_TEXT_ATTR fm_cam_cmd_start() +{ + fm_send_spi_cmd(0x66, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + fm_send_spi_cmd(0x3C, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + fm_send_spi_cmd(0xC3, 1*8, 0, 0, NULL, 0, NULL, 0, 0); +} + +static void FLASH_PATCH_TEXT_ATTR fm_cam_cmd_end() +{ + fm_send_spi_cmd(0xff, 1*8, 0, 0, NULL, 0, NULL, 0, 0); +} + +static void FLASH_PATCH_TEXT_ATTR fm_cam_pre_cmd_generic(uint8_t (*send_list)[5], int lines) +{ + int i; + fm_cam_cmd_start(); + for(i = 0; i < lines; i++) { + fm_send_spi_cmd(0x0, 0, 0, 0, &send_list[i][0], 5*8, NULL, 0, 0); + } + + fm_cam_cmd_end(); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR read_after_erase_pre_send_list[7][5] = { + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x80,0x01}, + {0x32,0x00,0x00,0x84,0x47}, + {0x32,0x00,0x00,0x88,0x47}, + {0x32,0x00,0x00,0x8c,0x04}, + {0x32,0x00,0x00,0x90,0x19}, + {0x32,0x00,0x00,0x94,0x03}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_read_after_erase_pre(void) +{ + // cmd NO.7 in the doc + uint8_t send_list[7][5]; + + memcpy(send_list, read_after_erase_pre_send_list, 7 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR step_prog_pre_send_list[8][5] = { + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x64,0xb7}, + {0x32,0x00,0x00,0x80,0x13}, + {0x32,0x00,0x00,0x84,0x4f}, + {0x32,0x00,0x00,0x88,0x78}, + {0x32,0x00,0x00,0x8c,0x10}, + {0x32,0x00,0x00,0x90,0x40}, + {0x32,0x00,0x00,0x94,0xff}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_step_prog_pre(void) +{ + // cmd NO.6 in the doc + uint8_t send_list[8][5]; + + memcpy(send_list, step_prog_pre_send_list, 8 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR step_erase_pre_send_list[8][5] = { + {0x32,0x00,0x00,0x64,0x77}, + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x80,0x01}, + {0x32,0x00,0x00,0x84,0x46}, + {0x32,0x00,0x00,0x88,0x7e}, + {0x32,0x00,0x00,0x8c,0x06}, + {0x32,0x00,0x00,0x90,0x31}, + {0x32,0x00,0x00,0x94,0x01}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_step_erase_pre(void) +{ + // cmd NO.5 in the doc + uint8_t send_list[8][5]; + + memcpy(send_list, step_erase_pre_send_list, 8 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR preprog_pre_send_list[8][5] = { + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x64,0xf1}, + {0x32,0x00,0x00,0x80,0x53}, + {0x32,0x00,0x00,0x84,0x5c}, + {0x32,0x00,0x00,0x88,0x7c}, + {0x32,0x00,0x00,0x8c,0x04}, + {0x32,0x00,0x00,0x90,0x1f}, + {0x32,0x00,0x00,0x94,0xff}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_preprog_pre(void) +{ + // cmd NO.4 in the doc + uint8_t send_list[8][5]; + + memcpy(send_list, preprog_pre_send_list, 8 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR prog_pre_send_list[7][5] = { + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x80,0x53}, + {0x32,0x00,0x00,0x84,0x7c}, + {0x32,0x00,0x00,0x88,0x7f}, + {0x32,0x00,0x00,0x8c,0x10}, + {0x32,0x00,0x00,0x90,0xff}, + {0x32,0x00,0x00,0x94,0xff}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_prog_pre(void) +{ + // cmd NO.3 in the doc + uint8_t send_list[7][5]; + + memcpy(send_list, prog_pre_send_list, 7 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR uid_pre_send_list[7][5] = { + {0x32,0x00,0x03,0xc0,0x48}, + {0x32,0x00,0x00,0x80,0x00}, + {0x32,0x00,0x00,0x84,0x47}, + {0x32,0x00,0x00,0x88,0x47}, + {0x32,0x00,0x00,0x8c,0x04}, + {0x32,0x00,0x00,0x90,0x19}, + {0x32,0x00,0x00,0x94,0x03}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_uid_pre(void) +{ + // cmd NO.2 in the doc + uint8_t send_list[7][5]; + + memcpy(send_list, uid_pre_send_list, 7 * 5); + + fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR read_pre_send_list[7][5] = { + {0x32,0x00,0x03,0xc0,0x88}, + {0x32,0x00,0x00,0x80,0x00}, + {0x32,0x00,0x00,0x84,0x47}, + {0x32,0x00,0x00,0x88,0x47}, + {0x32,0x00,0x00,0x8c,0x04}, + {0x32,0x00,0x00,0x90,0x19}, + {0x32,0x00,0x00,0x94,0x03}, +}; + +static void FLASH_PATCH_TEXT_ATTR fm_cam_read_pre(void) +{ + // cmd NO.1 in the doc + uint8_t send_list[7][5]; + + memcpy(send_list, read_pre_send_list, 7 * 5); + + // fm_cam_pre_cmd_generic(send_list, sizeof(send_list) / sizeof(send_list[0])); + fm_cam_pre_cmd_generic(send_list, 7); +} + +static void FLASH_PATCH_TEXT_ATTR fm_soft_reset() +{ + fm_send_spi_cmd(0x66, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + // ets_delay_us(100); + fm_send_spi_cmd(0x99, 1*8, 0, 0, NULL, 0, NULL, 0, 0); +} + +static bool FLASH_PATCH_TEXT_ATTR fm_flash_wait_idle() +{ + uint8_t status = 0x1; + while ((status&0x1) == 0x1) { + fm_send_spi_cmd(0x05, 1*8, 0, 0, NULL, 0, &status, 1*8, 0); + } + return true; +} + +static bool FLASH_PATCH_TEXT_ATTR fm_erase_sector(uint32_t addr) +{ + // write en + fm_send_spi_cmd(0x06, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + fm_send_spi_cmd(0x20, 1*8, addr, 24, 0, 0, 0, 0, 0); + return fm_flash_wait_idle(); +} + +static bool FLASH_PATCH_TEXT_ATTR fm_cam_erase_and_fix(uint8_t (*buf)[32]) +{ + INFO(FLASH_PATCH_STR("Start erase and program cam buf\n")); + // cmd 4. + fm_cam_preprog_pre(); + if (!fm_erase_sector(0x0)) { + ERROR(FLASH_PATCH_STR("ERR in ERASE %d\n"), __LINE__); + return false; + } + int retry = 40; + while (retry > 0) { + WDT_FEED(); + // cmd 5. + fm_cam_step_erase_pre(); + if (!fm_erase_sector(0x0)) { + ERROR(FLASH_PATCH_STR("ERR in ERASE %d\n"), __LINE__); + return false; + } + // cmd 6. + fm_cam_step_prog_pre(); + if (!fm_erase_sector(0x0)) { + ERROR(FLASH_PATCH_STR("ERR in ERASE %d\n"), __LINE__); + return false; + } + + // + INFO(FLASH_PATCH_STR("Start programming 5 page\n")); + fm_cam_prog_pre(); + int line = 0; + for (line = 0; line < 5; line++) { + // write en + fm_send_spi_cmd(0x06, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + // prog + fm_send_spi_cmd(0x02, 8, 0x20*line, 24, &buf[line][0], 32*8, 0, 0, 0); + fm_flash_wait_idle(); + WDT_FEED(); + // for (uint32_t loop = 0; loop < 32; loop++) { + // DEBUG(FLASH_PATCH_STR("%02x "), buf[line][loop]); + // } + // INFO(FLASH_PATCH_STR("\n")); + } + INFO(FLASH_PATCH_STR("Programming Done\n")); + + // cmd 7. + fm_cam_read_after_erase_pre(); + int found_error = false; + for (line = 0; line < 5; line++) { + uint32_t cam_rd[8]; + fm_send_spi_cmd(0x03, 8, 0x20 * line, 24, 0, 0, cam_rd, 32*8, 0); + int idx = 0; + for (idx = 0;idx < 8; idx++) { + uint32_t* p = buf[line]; + if (cam_rd[idx] != p[idx]) { + found_error = true; + ERROR(FLASH_PATCH_STR("erase check error, retry...%d, %d, 0x%08x\n"), retry, idx, cam_rd[idx]); + break; + } + } + retry -= 1; + } + + if (!found_error) { + for (line = 5; line < 20; line++) { + uint32_t cam_rd[8]; + fm_send_spi_cmd(0x03, 8, 0x20 * line, 24, 0, 0, cam_rd, 32*8, 0); + int idx = 0; + for (idx = 0;idx < 8; idx++) { + if (cam_rd[idx] != 0x000000ff) { + found_error = true; + ERROR(FLASH_PATCH_STR("erase check error, retry...%d, %d, 0x%08x\n"), retry, idx, cam_rd[idx]); + break; + } + } + retry -= 1; + } + } + + if (! found_error) { + INFO(FLASH_PATCH_STR("Erase Pass !!!\n")); + break; + } + } + if (retry <= 0) { + ERROR(FLASH_PATCH_STR("Erase fail !!!\n")); + return false; + } + + // cmd 3. + INFO(FLASH_PATCH_STR("Start programming\n")); + fm_cam_prog_pre(); + int line = 0; + for (line = 5; line < 20; line++) { + // write en + fm_send_spi_cmd(0x06, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + // prog + fm_send_spi_cmd(0x02, 8, 0x20*line, 24, &buf[line][0], 32*8, 0, 0, 0); + fm_flash_wait_idle(); + WDT_FEED(); + for (uint32_t loop = 0; loop < 32; loop++) { + DEBUG(FLASH_PATCH_STR("%02x "), buf[line][loop]); + } + INFO(FLASH_PATCH_STR("\n")); + } + + // read buffer + INFO(FLASH_PATCH_STR("Prog done, read and check")); + fm_cam_read_pre(); + for (line = 0;line < 20; line++) { + WDT_FEED(); + uint8_t cam_check[32]; + fm_send_spi_cmd(0x03, 8, 0x20*line, 24, 0, 0, cam_check, 32*8, 0); + int j = 0; + for (j = 0; j < 32; j++) { + DEBUG(FLASH_PATCH_STR("%02x "), cam_check[j]); + if ((j + 1) % 16 == 0) { + DEBUG(FLASH_PATCH_STR("\n")); + } + } + if (memcmp(cam_check, buf[line], 32) != 0) { + ERROR(FLASH_PATCH_STR("CAM BUF[%d] check error\n"), line); + ets_delay_us(50000); + return false; + } + } + + INFO(FLASH_PATCH_STR("CAM prog done !!!\n")); + return true; +} + +static const uint8_t FLASH_PATCH_RODATA_ATTR cam_buf_default_rodata[20][32] = { + {0x55, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x53, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x53, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x23, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xe3, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x23, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +static bool FLASH_PATCH_TEXT_ATTR esp_fm_check_uid() +{ + uint8_t cam_buf_default[20][32]; + + memcpy(cam_buf_default, cam_buf_default_rodata, 20*32); + + // cmd 2. + fm_cam_uid_pre(); + uint8_t uid[6]; + fm_send_spi_cmd(0x03, 1*8, 0x25, 3*8, NULL, 0, uid, 6*8, 0); + if(uid[0] == ~uid[1] && uid[2] == ~uid[3] && uid[4] == ~uid[5]) { + INFO(FLASH_PATCH_STR("UID check correct, update buf\n")); + cam_buf_default[3][12] = uid[0] & 0xff; + cam_buf_default[5][0] = uid[2] & 0xff; + cam_buf_default[6][0] = uid[2] & 0xff; + cam_buf_default[7][0] = uid[4] & 0xff; + cam_buf_default[9][0] = uid[4] & 0xff; + } else { + INFO(FLASH_PATCH_STR("UID check error, use default buf\n")); + } + WDT_FEED(); + bool res = fm_cam_erase_and_fix(cam_buf_default); + WDT_FEED(); + fm_soft_reset(); + return res; +} + +static bool FLASH_PATCH_TEXT_ATTR fm_cam_check_buf_valid(uint8_t (*buf)[32]) +{ + bool res = false; + // cmd 1. + fm_cam_read_pre(); + + int i = 0, j = 0; + for (i = 0; i < 20; i++) { + // read buf + fm_send_spi_cmd(0x03, 8, 0x20 * i, 24, 0, 0, &buf[i][0], 32*8, 0); + for (j = 0; j < 32; j++) { + DEBUG(FLASH_PATCH_STR("%02x "), buf[i][j]); + if ((j + 1) % 16 == 0) { + DEBUG(FLASH_PATCH_STR("\n")); + INFO(FLASH_PATCH_STR("\r")); + } + } + } + + if (buf[0][0] == 0x55 && buf[0][4] == 0xaa \ + && buf[4][0] == 0x00 \ + && buf[10][0] == 0x1 && buf[10][20] == 0xff\ + && buf[11][0] == 0x1 && buf[11][20] == 0xff\ + && buf[12][0] == 0x1 && buf[12][20] == 0xff\ + && buf[13][0] == 0x1 && buf[13][20] == 0xff\ + && buf[14][0] == 0x1 && buf[14][20] == 0xff\ + && buf[15][0] == 0x1 && buf[15][20] == 0xff\ + && buf[16][0] == 0x1 && buf[16][20] == 0xff\ + && buf[17][0] == 0x1 && buf[17][20] == 0xff + ) { + INFO(FLASH_PATCH_STR("CAM buffer check valid !!!\n")); + res = true; + } else { + INFO(FLASH_PATCH_STR("CAM buffer check IN-Valid !!!\n")); + res = false; + } + // while(1); + return res; +} + +static bool FLASH_PATCH_TEXT_ATTR fm_fix_cam() +{ + uint8_t check_buf[20][32]; + + if (fm_cam_check_buf_valid(check_buf) == false) { + ERROR(FLASH_PATCH_STR("Cam buf not valid\n")); + return esp_fm_check_uid(); + } else { + INFO(FLASH_PATCH_STR("Cam buf valid\n")); + + if ((check_buf[3][0] & 0x08) == 0x08) { + INFO(FLASH_PATCH_STR("Bit3 == 1, already fixed....\n")); + fm_soft_reset(); + } else { + check_buf[3][0] |= 0x08; + bool res = fm_cam_erase_and_fix(check_buf); + fm_soft_reset(); + return res; + } + } + + return true; +} + +static uint32_t FLASH_PATCH_TEXT_ATTR fm_flash_id(void) +{ + uint32_t data[6]; + uint8_t* id = (uint8_t*)data; +#if 1 + fm_send_spi_cmd(0x9f, 8, 0, 0, 0, 0, id, 24*8, 0); + return (id[0]<<16 | id[1]<<8 | id[2]); +#else + uint32_t flash_id = spi_flash_get_id(); + memcpy(id, &flash_id, 3); + return (id[0]<<16 | id[1]<<8 | id[2]); +#endif +} + +int FLASH_PATCH_TEXT_ATTR fm25q16a_apply_patch_0() +{ + bool res = false; + + spi_state_t state; + spi_enter(&state); + uint32_t flash_id = fm_flash_id(); + DEBUG(FLASH_PATCH_STR("Flash id: 0x%x\n"), flash_id); + + WDT_FEED(); + if (flash_id == 0xa14015) { + INFO(DRAM_STR("Found FM25Q16A, check CAM buf\n")); + res = fm_fix_cam(); + } else if ((flash_id&0xffffff) == 0x0 || (flash_id&0xffffff) == 0xffffff) { + INFO(FLASH_PATCH_STR("Found ID error, recover default CAM buf\n")); + res = esp_fm_check_uid(); + } else { + INFO(FLASH_PATCH_STR("Normal flash, continue...\n")); + res = true; + } + WDT_FEED(); + + spi_exit(&state); + + if (res != true) { + fm_printf(FLASH_PATCH_STR("fix fail\n")); + return 1; + } + + fm_printf(FLASH_PATCH_STR("fix done\n")); + return 0; +} diff --git a/components/spi_flash/src/patch/spi_flash_patch.h b/components/spi_flash/src/patch/spi_flash_patch.h new file mode 100644 index 000000000..3052418d7 --- /dev/null +++ b/components/spi_flash/src/patch/spi_flash_patch.h @@ -0,0 +1,58 @@ +// Copyright 2024-2026 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _SPI_FLASH_PATCH_H +#define _SPI_FLASH_PATCH_H + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FLASH_PATCH_TEXT_ATTR __attribute__((section(".flash.patch.text"))) +#define FLASH_PATCH_RODATA_ATTR __attribute__((section(".flash.patch.rodata"))) +#define FLASH_PATCH_BSS_ATTR __attribute__((section(".flash.patch.bss"))) + +#define FLASH_PATCH_STR(str) (__extension__({static const FLASH_PATCH_RODATA_ATTR char __c[] = (str); (const char *)&__c;})) + +#if 1 +typedef int (*__ets_printf_t)(const char *fmt, ...); +#define ROM_PRINTF(_fmt, ...) ((__ets_printf_t)(0x400024cc))(_fmt, ##__VA_ARGS__) +#else +#define ROM_PRINTF(_fmt, ...) +#endif + +typedef struct spi_state { + uint32_t io_mux_reg; + uint32_t spi_clk_reg; + uint32_t spi_ctrl_reg; + uint32_t spi_user_reg; +} spi_state_t; + +void spi_enter(spi_state_t *state); +void spi_exit(spi_state_t *state); + +void spi_trans(bool write_mode, uint32_t cmd, uint32_t cmd_bits, uint32_t addr, uint32_t addr_bits, uint8_t *data, + uint32_t data_bytes, uint32_t dummy_bits); +void patch_delay(int ms); + +#ifdef __cplusplus +} +#endif + +#endif /* _SPI_FLASH_H */ diff --git a/components/spi_flash/src/patch/th25q16hb.c b/components/spi_flash/src/patch/th25q16hb.c index 425096f9b..4548e2c60 100644 --- a/components/spi_flash/src/patch/th25q16hb.c +++ b/components/spi_flash/src/patch/th25q16hb.c @@ -28,279 +28,55 @@ #include "esp8266/spi_register.h" #include "esp8266/spi_struct.h" -#define SPI_FLASH SPI0 +#include "spi_flash_patch.h" + #define SPI_BLOCK_SIZE 32 -#define ADDR_SHIFT_BITS 8 -#if 0 -typedef int (*__ets_printf_t)(const char *fmt, ...); -#define ROM_PRINTF(_fmt, ...) ((__ets_printf_t)(0x400024cc))(_fmt, ##__VA_ARGS__) -#else -#define ROM_PRINTF(_fmt, ...) -#endif +void spi_trans(bool write_mode, uint32_t cmd, uint32_t cmd_bits, uint32_t addr, uint32_t addr_bits, uint8_t *data, + uint32_t data_bytes, uint32_t dummy_bits); +void patch_delay(int ms); #define TOCHAR(_v) #_v -#define PRINT_STEP(_s) ROM_PRINTF("Step %d\n", (_s)); -#define JUMP_TO_STEP(_s) { ROM_PRINTF("Jump to " TOCHAR(_s) "\n"); goto _s; } -#define GOTO_FAILED(_s) { ROM_PRINTF("ERROR: " TOCHAR(_s) " failed\n"); ret = -EIO; JUMP_TO_STEP(step17); } +#define PRINT_STEP(_s) ROM_PRINTF(FLASH_PATCH_STR("Step %d\n"), (_s)); +#define JUMP_TO_STEP(_s) { ROM_PRINTF(FLASH_PATCH_STR("%d line Jump to " TOCHAR(_s) "\n"), __LINE__); goto _s; } +#define GOTO_FAILED(_s) { ROM_PRINTF(FLASH_PATCH_STR("ERROR: " TOCHAR(_s) " failed\n")); ret = -EIO; JUMP_TO_STEP(step17); } #define write_u8_dummy(_c, _a, _d8,_d) {uint32_t __data = _d8; spi_trans(1, (_c), 8, (_a), 24, (uint8_t *)&__data, 1, (_d));} #define write_u8(_c, _a, _d8) write_u8_dummy((_c), (_a), (_d8), 0) -extern void Cache_Read_Disable_2(void); -extern void Cache_Read_Enable_2(); -extern void vPortEnterCritical(void); -extern void vPortExitCritical(void); extern uint32_t spi_flash_get_id(void); -static void delay(int ms) -{ - for (volatile int i = 0; i < ms; i++) { - for (volatile int j = 0; j < 7800; j++) { - } - } -} - -#if 0 -static void dump_hex(const uint8_t *ptr, int n) -{ - const uint8_t *s1 = ptr; - const int line_bytes = 16; - - ROM_PRINTF("\nHex:\n"); - for (int i = 0; i < n ; i += line_bytes) - { - int m = MIN(n - i, line_bytes); - - ROM_PRINTF("\t"); - for (int j = 0; j < m; j++) - { - ROM_PRINTF("%02x ", s1[i + j]); - } - - ROM_PRINTF("\n"); - } - - ROM_PRINTF("\n"); -} - -static void dump_hex_compare(const uint8_t *s1, const uint8_t *s2, int n) -{ - const int line_bytes = 16; - - ROM_PRINTF("\nHex:\n"); - for (int i = 0; i < n ; i += line_bytes) - { - int m = MIN(n - i, line_bytes); - - ROM_PRINTF("\t"); - for (int j = 0; j < m; j++) - { - ROM_PRINTF("%02x:%02x ", s1[i + j], s2[i + j]); - } - - ROM_PRINTF("\n"); - } - - ROM_PRINTF("\n"); -} -#endif - -typedef struct spi_state { - uint32_t io_mux_reg; - uint32_t spi_clk_reg; - uint32_t spi_ctrl_reg; - uint32_t spi_user_reg; -} spi_state_t; - -static void spi_enter(spi_state_t *state) -{ - vPortEnterCritical(); - Cache_Read_Disable_2(); - - Wait_SPI_Idle(&g_rom_flashchip); - - state->io_mux_reg = READ_PERI_REG(PERIPHS_IO_MUX_CONF_U); - state->spi_clk_reg = SPI_FLASH.clock.val; - state->spi_ctrl_reg = SPI_FLASH.ctrl.val; - state->spi_user_reg = SPI_FLASH.user.val; - - SPI_FLASH.user.usr_command = 1; - SPI_FLASH.user.flash_mode = 0; - SPI_FLASH.user.usr_miso_highpart = 0; - - CLEAR_PERI_REG_MASK(PERIPHS_IO_MUX_CONF_U, SPI0_CLK_EQU_SYS_CLK); - - SPI_FLASH.user.cs_setup = 1; - SPI_FLASH.user.cs_hold = 1; - SPI_FLASH.user.usr_mosi = 1; - - SPI_FLASH.user.usr_command = 1; - SPI_FLASH.user.flash_mode = 0; - - SPI_FLASH.ctrl.fread_qio = 0; - SPI_FLASH.ctrl.fread_dio = 0; - SPI_FLASH.ctrl.fread_quad = 0; - SPI_FLASH.ctrl.fread_dual = 0; - - SPI_FLASH.clock.val = 0; - SPI_FLASH.clock.clkcnt_l = 3; - SPI_FLASH.clock.clkcnt_h = 1; - SPI_FLASH.clock.clkcnt_n = 3; - - SPI_FLASH.ctrl.fastrd_mode = 1; - - while (SPI_FLASH.cmd.usr) { - ; - } -} - -static void spi_exit(spi_state_t *state) -{ - while (SPI_FLASH.cmd.usr) { - ; - } - - WRITE_PERI_REG(PERIPHS_IO_MUX_CONF_U, state->io_mux_reg); - - SPI_FLASH.ctrl.val = state->spi_ctrl_reg; - SPI_FLASH.clock.val = state->spi_clk_reg; - SPI_FLASH.user.val = state->spi_user_reg; - - Cache_Read_Enable_2(); - vPortExitCritical(); -} +static uint8_t FLASH_PATCH_BSS_ATTR buffer1024[1024]; -static void spi_trans_block(bool write_mode, - uint32_t cmd, - uint32_t cmd_bits, - uint32_t addr, - uint32_t addr_bits, - uint8_t *data, - uint32_t data_bytes, - uint32_t dummy_bits) -{ - if ((uint32_t)data & 0x3) { - ROM_PRINTF("ERROR: data=%p\n", data); - return; - } - - if (cmd_bits) { - SPI_FLASH.user.usr_command = 1; - SPI_FLASH.user2.usr_command_value = cmd; - SPI_FLASH.user2.usr_command_bitlen = cmd_bits - 1; - } else { - SPI_FLASH.user.usr_command = 0; - } - - if (addr_bits) { - SPI_FLASH.user.usr_addr = 1; - SPI_FLASH.addr = addr << ADDR_SHIFT_BITS; - SPI_FLASH.user1.usr_addr_bitlen = addr_bits - 1; - } else { - SPI_FLASH.user.usr_addr = 0; - } - - if (dummy_bits) { - SPI_FLASH.user.usr_dummy = 1; - SPI_FLASH.user1.usr_dummy_cyclelen = dummy_bits - 1; - } else { - SPI_FLASH.user.usr_dummy = 0; - } - - if (write_mode && data_bytes) { - int words = (data_bytes + 3) / 4; - uint32_t *p = (uint32_t *)data; - - SPI_FLASH.user.usr_mosi = 1; - SPI_FLASH.user.usr_miso = 0; - SPI_FLASH.user1.usr_mosi_bitlen = data_bytes * 8 - 1; - - for (int i = 0; i < words; i++) { - SPI_FLASH.data_buf[i] = p[i]; - } - } else if (!write_mode && data_bytes) { - int words = (data_bytes + 3) / 4; - - SPI_FLASH.user.usr_mosi = 0; - SPI_FLASH.user.usr_miso = 1; - SPI_FLASH.user1.usr_miso_bitlen = data_bytes * 8 - 1; - - for (int i = 0; i < words; i++) { - SPI_FLASH.data_buf[i] = 0; - } - } else { - SPI_FLASH.user.usr_mosi = 0; - SPI_FLASH.user.usr_miso = 0; - } - - SPI_FLASH.cmd.usr = 1; - while (SPI_FLASH.cmd.usr) { - ; - } - - if (!write_mode && data_bytes) { - int words = (data_bytes + 3) / 4; - uint32_t *p = (uint32_t *)data; - - for (int i = 0; i < words; i++) { - p[i] = SPI_FLASH.data_buf[i]; - } - } -} - -static void spi_trans(bool write_mode, - uint32_t cmd, - uint32_t cmd_bits, - uint32_t addr, - uint32_t addr_bits, - uint8_t *data, - uint32_t data_bytes, - uint32_t dummy_bits) - -{ - if (!data_bytes || data_bytes <= SPI_BLOCK_SIZE) { - return spi_trans_block(write_mode, cmd, cmd_bits, addr, - addr_bits, data, data_bytes, dummy_bits); - } - - for (int i = 0; i < data_bytes; i += SPI_BLOCK_SIZE) { - uint32_t n = MIN(SPI_BLOCK_SIZE, data_bytes - i); - - spi_trans_block(write_mode, cmd, cmd_bits, addr + i, - addr_bits, data + i, n, dummy_bits); - } -} - -static void write_cmd(uint32_t cmd) +static void FLASH_PATCH_TEXT_ATTR write_cmd(uint32_t cmd) { spi_trans(1, cmd, 8, 0, 0, NULL, 0, 0); } -static void write_buffer(uint32_t addr, uint8_t *buffer, int size) +static void FLASH_PATCH_TEXT_ATTR write_buffer(uint32_t addr, uint8_t *buffer, int size) { for (int i = 0; i < size; i += SPI_BLOCK_SIZE) { int n = MIN(size - i, SPI_BLOCK_SIZE); write_cmd(0x6); spi_trans(1, 0x42, 8, addr + i, 24, buffer + i, n, 0); - delay(3); + patch_delay(3); } } -static void read_buffer(uint32_t addr, uint8_t *buffer, int n) +static void FLASH_PATCH_TEXT_ATTR read_buffer(uint32_t addr, uint8_t *buffer, int n) { spi_trans(0, 0x48, 8, addr, 24, buffer, n, 8); } -static void erase_sector(uint32_t addr) +static void FLASH_PATCH_TEXT_ATTR erase_sector(uint32_t addr) { write_cmd(0x6); spi_trans(1, 0x44, 8, addr, 24, NULL, 0, 0); - delay(8); + patch_delay(8); } -int th25q16hb_apply_patch_0(void) +int FLASH_PATCH_TEXT_ATTR th25q16hb_apply_patch_0(void) { int ret = 0; uint32_t flash_id; @@ -309,19 +85,13 @@ int th25q16hb_apply_patch_0(void) uint8_t *buffer256_0; uint8_t *buffer256_1; uint8_t *buffer256_2; - uint8_t *buffer1024; flash_id = spi_flash_get_id(); if (flash_id != 0x1560eb) { - ROM_PRINTF("WARN: id=0x%x, is not TH25Q16HB\n", flash_id); + ROM_PRINTF(FLASH_PATCH_STR("WARN: id=0x%x, is not TH25Q16HB\n"), flash_id); return 0; } - buffer1024 = heap_caps_malloc(1024, MALLOC_CAP_8BIT); - if (!buffer1024) { - return -ENOMEM; - } - buffer256_0 = buffer1024; buffer256_1 = buffer1024 + 256; buffer256_2 = buffer1024 + 512; @@ -351,7 +121,7 @@ int th25q16hb_apply_patch_0(void) if (buffer256_0[0] == 0xff && buffer256_0[1] == 0xff && buffer256_0[2] == 0xff) { - ROM_PRINTF("INFO: check done 0\n"); + ROM_PRINTF(FLASH_PATCH_STR("INFO: check done 0\n")); } else if (buffer256_0[0] == 0x55 && buffer256_0[1] == 0xff && buffer256_0[2] == 0xff) { @@ -365,20 +135,20 @@ int th25q16hb_apply_patch_0(void) buffer256_0[2] == 0x55) { JUMP_TO_STEP(step17); } else { - ROM_PRINTF("ERROR: 0xbed=0x%x 0xbee=0x%x 0xbef=0x%x\n", + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0xbed=0x%x 0xbee=0x%x 0xbef=0x%x\n"), buffer256_0[0], buffer256_0[1], buffer256_0[2]); GOTO_FAILED(5-1); } - +JUMP_TO_STEP(step17); // Step 5-2 read_buffer(0x50d, buffer256_0, 1); buffer256_0[0] &= 0x7f; if (buffer256_0[0] == 0x7c) { JUMP_TO_STEP(step17); } else if (buffer256_0[0] == 0x3c) { - ROM_PRINTF("INFO: check done 1\n"); + ROM_PRINTF(FLASH_PATCH_STR("INFO: check done 1\n")); } else { - ROM_PRINTF("ERROR: 0x50d=0x%x\n", buffer256_0[0]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x50d=0x%x\n"), buffer256_0[0]); GOTO_FAILED(5-2); } @@ -392,7 +162,7 @@ int th25q16hb_apply_patch_0(void) for (int i = 0; i < 1024; i++) { if (buffer1024[i] != 0xff) { check_done = false; - ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: buffer1024[%d]=0x%x\n"), i, buffer1024[i]); break; } } @@ -465,7 +235,7 @@ int th25q16hb_apply_patch_0(void) read_buffer(0x0, buffer256_0, 1); read_buffer(0x23, buffer256_1, 1); if (buffer256_0[0] != 0x13 || buffer256_1[0] != 0x14) { - ROM_PRINTF("ERROR: 0x0=0x%x 0x23=0x%x\n", buffer256_0[0], buffer256_1[0]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x0=0x%x 0x23=0x%x\n"), buffer256_0[0], buffer256_1[0]); GOTO_FAILED(8); } @@ -473,7 +243,7 @@ int th25q16hb_apply_patch_0(void) PRINT_STEP(9); read_buffer(0x140, buffer256_0, 2); if (buffer256_0[0] != 0 || buffer256_0[1] != 0xff) { - ROM_PRINTF("ERROR: 0x140=0x%x 0x141=0x%x\n", buffer256_0[0], buffer256_0[1]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x140=0x%x 0x141=0x%x\n"), buffer256_0[0], buffer256_0[1]); GOTO_FAILED(9-1); } @@ -507,7 +277,7 @@ int th25q16hb_apply_patch_0(void) for (int i = 0; i < 1024; i++) { if (buffer1024[i] != 0xff) { check_done = false; - ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: buffer1024[%d]=0x%x\n"), i, buffer1024[i]); break; } } @@ -585,7 +355,7 @@ int th25q16hb_apply_patch_0(void) read_buffer(0x400, buffer256_0, 1); read_buffer(0x423, buffer256_1, 1); if (buffer256_0[0] != 0x13 || buffer256_1[0] != 0x14) { - ROM_PRINTF("ERROR: 0x400=0x%x 0x423=0x%x\n", buffer256_0[0], buffer256_1[0]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x400=0x%x 0x423=0x%x\n"), buffer256_0[0], buffer256_1[0]); break; } } @@ -597,7 +367,7 @@ int th25q16hb_apply_patch_0(void) for (count = 0; count < 3; count++) { read_buffer(0x540, buffer256_0, 2); if (buffer256_0[0] != 0 || buffer256_0[1] != 0xff) { - ROM_PRINTF("ERROR: 0x540=0x%x 0x541=0x%x\n", buffer256_0[0], buffer256_0[1]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x540=0x%x 0x541=0x%x\n"), buffer256_0[0], buffer256_0[1]); break; } } @@ -610,7 +380,7 @@ int th25q16hb_apply_patch_0(void) read_buffer(0x50d, buffer256_0, 1); buffer256_0[0] &= 0x7f; if (buffer256_0[0] != 0x7c) { - ROM_PRINTF("ERROR: 0x50d=0x%x\n", buffer256_0[0]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: 0x50d=0x%x\n"), buffer256_0[0]); break; } } @@ -652,7 +422,7 @@ int th25q16hb_apply_patch_0(void) for (int i = 0; i < 1024; i++) { if (buffer1024[i] != 0xff) { check_done = false; - ROM_PRINTF("ERROR: buffer1024[%d]=0x%x\n", i, buffer1024[i]); + ROM_PRINTF(FLASH_PATCH_STR("ERROR: buffer1024[%d]=0x%x\n"), i, buffer1024[i]); break; } } @@ -699,10 +469,8 @@ int th25q16hb_apply_patch_0(void) spi_exit(&state); - heap_caps_free(buffer1024); - if (!ret) { - ROM_PRINTF("INFO: Patch for TH25Q16HB is done\n"); + ROM_PRINTF(FLASH_PATCH_STR("INFO: Patch for TH25Q16HB is done\n")); } return ret; From 323b1f8be0e49e5aba79b7c608938b4d02a7ae94 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Tue, 5 Nov 2024 16:05:05 +0800 Subject: [PATCH 46/59] Log: Disable SPI FLASH patch log --- components/spi_flash/Kconfig | 7 ++++++- components/spi_flash/src/patch/spi_flash_patch.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index e67bd5f10..1ab135ce2 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -2,7 +2,7 @@ menu "SPI Flash" menu "Patch" config ENABLE_SPI_FLASH_PATCH - bool "Enable TH25Q16HB Patch 0" + bool "Enable SPI Flash Patch" default n config ENABLE_TH25Q16HB_PATCH_0 @@ -22,5 +22,10 @@ menu "SPI Flash" WARNING: If you don't use FM25Q16A, you must not enable this option. Although you use FM25Q16A, you should ask your flash manufacturer if your flash need use this patch. + + config ENABLE_SPI_FLASH_PATCH_DEBUG + bool "Enable SPI flash patch debug" + depends on ENABLE_SPI_FLASH_PATCH + default n endmenu endmenu diff --git a/components/spi_flash/src/patch/spi_flash_patch.h b/components/spi_flash/src/patch/spi_flash_patch.h index 3052418d7..24243184e 100644 --- a/components/spi_flash/src/patch/spi_flash_patch.h +++ b/components/spi_flash/src/patch/spi_flash_patch.h @@ -30,7 +30,7 @@ extern "C" { #define FLASH_PATCH_STR(str) (__extension__({static const FLASH_PATCH_RODATA_ATTR char __c[] = (str); (const char *)&__c;})) -#if 1 +#if CONFIG_ENABLE_SPI_FLASH_PATCH_DEBUG typedef int (*__ets_printf_t)(const char *fmt, ...); #define ROM_PRINTF(_fmt, ...) ((__ets_printf_t)(0x400024cc))(_fmt, ##__VA_ARGS__) #else From 224e7a1c56b7e066036eb7f1ead19673c51df15c Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Mon, 18 Nov 2024 10:12:02 +0800 Subject: [PATCH 47/59] Fixed the ssid len error issue when the ssid string does not have an end delimiter --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 485200 -> 485280 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 544100 -> 544180 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 472c4493d..99cc075df 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: 58ed843 + net80211: bb3a27f pp: 12d8a11 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index c36ff388c9406f7e75e7def4c73ebf4514c69e51..161de1f3699844e3fb921b739b20da552c806f16 100644 GIT binary patch delta 5584 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 I@XxmY0859dY5)KL delta 5401 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 delta 5607 zcmbW5e^gXu8pq%F!rZIQ05bzJ{1TZ#<%g&+D8G~%$qY;o#2*8u0s?_3$sR5I(L&m3 zD=9@-AJOtaTV{1msZ^sLTd6IxverDg2VL{mx*avzMD2vF%0AD%?`@F&YTtA2JkR&@ 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 From f3aaa5831e74f4abe80f921ebea866b171cf9816 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 20 Nov 2024 14:22:28 +0800 Subject: [PATCH 48/59] fix(flash): fixed a potential system hang issue on fm25q16a --- components/spi_flash/src/patch/common.c | 1 + components/spi_flash/src/patch/fm25q16a.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/spi_flash/src/patch/common.c b/components/spi_flash/src/patch/common.c index 84aa395d0..cc00d9428 100644 --- a/components/spi_flash/src/patch/common.c +++ b/components/spi_flash/src/patch/common.c @@ -106,6 +106,7 @@ void FLASH_PATCH_TEXT_ATTR spi_exit(spi_state_t *state) Cache_Read_Enable_2(); vPortExitCritical(); + patch_delay(1); } static void FLASH_PATCH_TEXT_ATTR spi_trans_block(bool write_mode, diff --git a/components/spi_flash/src/patch/fm25q16a.c b/components/spi_flash/src/patch/fm25q16a.c index dbd290afd..541f6bfd8 100644 --- a/components/spi_flash/src/patch/fm25q16a.c +++ b/components/spi_flash/src/patch/fm25q16a.c @@ -237,7 +237,7 @@ static void FLASH_PATCH_TEXT_ATTR fm_cam_read_pre(void) static void FLASH_PATCH_TEXT_ATTR fm_soft_reset() { fm_send_spi_cmd(0x66, 1*8, 0, 0, NULL, 0, NULL, 0, 0); - // ets_delay_us(100); + // patch_delay(1); fm_send_spi_cmd(0x99, 1*8, 0, 0, NULL, 0, NULL, 0, 0); } @@ -378,7 +378,7 @@ static bool FLASH_PATCH_TEXT_ATTR fm_cam_erase_and_fix(uint8_t (*buf)[32]) } if (memcmp(cam_check, buf[line], 32) != 0) { ERROR(FLASH_PATCH_STR("CAM BUF[%d] check error\n"), line); - ets_delay_us(50000); + patch_delay(50); return false; } } From 31637024ff259ea364e5ca0874661aa7d093cbe7 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Tue, 26 Nov 2024 18:38:13 +0800 Subject: [PATCH 49/59] fix: FM25Q16B fails to run due to FM25Q16A patch --- components/spi_flash/src/patch/fm25q16a.c | 192 +++++++++++++++++----- 1 file changed, 151 insertions(+), 41 deletions(-) diff --git a/components/spi_flash/src/patch/fm25q16a.c b/components/spi_flash/src/patch/fm25q16a.c index 541f6bfd8..81406d86d 100644 --- a/components/spi_flash/src/patch/fm25q16a.c +++ b/components/spi_flash/src/patch/fm25q16a.c @@ -43,7 +43,9 @@ extern bool IRAM_FUNC_ATTR spi_user_cmd(spi_cmd_dir_t mode, spi_cmd_t *p_cmd); extern uint32_t IRAM_FUNC_ATTR spi_flash_get_id(void); -static void FLASH_PATCH_TEXT_ATTR fm_send_spi_cmd(uint8_t cmd, uint8_t cmd_len, uint32_t addr, uint8_t addr_len, void* mosi_data, int mosi_len, void* miso_data, int miso_len, uint8_t dummy_bits) +static bool FLASH_PATCH_TEXT_ATTR double_check_fm25q16a(void); + +static void FLASH_PATCH_TEXT_ATTR fm_send_spi_cmd(uint8_t cmd, uint8_t cmd_len, uint32_t addr, uint8_t addr_len, const void* mosi_data, int mosi_len, void* miso_data, int miso_len, uint8_t dummy_bits) { bool write_mode = false; uint32_t data_bytes = 0; @@ -234,19 +236,36 @@ static void FLASH_PATCH_TEXT_ATTR fm_cam_read_pre(void) fm_cam_pre_cmd_generic(send_list, 7); } +static bool FLASH_PATCH_TEXT_ATTR fm_flash_wait_idle() +{ + uint8_t status = 0x1; + while ((status&0x1) == 0x1) { + fm_send_spi_cmd(0x05, 1*8, 0, 0, NULL, 0, &status, 1*8, 0); + } + return true; +} + static void FLASH_PATCH_TEXT_ATTR fm_soft_reset() { fm_send_spi_cmd(0x66, 1*8, 0, 0, NULL, 0, NULL, 0, 0); // patch_delay(1); fm_send_spi_cmd(0x99, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + fm_flash_wait_idle(); } -static bool FLASH_PATCH_TEXT_ATTR fm_flash_wait_idle() +static const uint8_t FLASH_PATCH_RODATA_ATTR uid_cmd[4] = {0x00, 0x03, 0xc0, 0x40}; +static const uint8_t FLASH_PATCH_RODATA_ATTR uid_data[4] = {0x00, 0x01, 0x00, 0x8F}; + +static bool FLASH_PATCH_TEXT_ATTR fm_set_uid_flag(void) { - uint8_t status = 0x1; - while ((status&0x1) == 0x1) { - fm_send_spi_cmd(0x05, 1*8, 0, 0, NULL, 0, &status, 1*8, 0); - } + fm_soft_reset(); + fm_cam_cmd_start(); + fm_send_spi_cmd(0x32, 8, 0, 0, uid_cmd, 4 * 8, NULL, 0, 0); + fm_cam_cmd_end(); + fm_send_spi_cmd(0x06, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + fm_send_spi_cmd(0x02, 8, 0, 0, uid_data, 4 * 8, 0, 0, 0); + fm_flash_wait_idle(); + return true; } @@ -309,7 +328,7 @@ static bool FLASH_PATCH_TEXT_ATTR fm_cam_erase_and_fix(uint8_t (*buf)[32]) fm_send_spi_cmd(0x03, 8, 0x20 * line, 24, 0, 0, cam_rd, 32*8, 0); int idx = 0; for (idx = 0;idx < 8; idx++) { - uint32_t* p = buf[line]; + uint32_t* p = (uint32_t*)buf[line]; if (cam_rd[idx] != p[idx]) { found_error = true; ERROR(FLASH_PATCH_STR("erase check error, retry...%d, %d, 0x%08x\n"), retry, idx, cam_rd[idx]); @@ -383,6 +402,7 @@ static bool FLASH_PATCH_TEXT_ATTR fm_cam_erase_and_fix(uint8_t (*buf)[32]) } } + fm_set_uid_flag(); INFO(FLASH_PATCH_STR("CAM prog done !!!\n")); return true; } @@ -440,47 +460,77 @@ static bool FLASH_PATCH_TEXT_ATTR esp_fm_check_uid() static bool FLASH_PATCH_TEXT_ATTR fm_cam_check_buf_valid(uint8_t (*buf)[32]) { bool res = false; - // cmd 1. - fm_cam_read_pre(); - - int i = 0, j = 0; - for (i = 0; i < 20; i++) { - // read buf - fm_send_spi_cmd(0x03, 8, 0x20 * i, 24, 0, 0, &buf[i][0], 32*8, 0); - for (j = 0; j < 32; j++) { - DEBUG(FLASH_PATCH_STR("%02x "), buf[i][j]); - if ((j + 1) % 16 == 0) { - DEBUG(FLASH_PATCH_STR("\n")); - INFO(FLASH_PATCH_STR("\r")); + uint8_t count = 3; + + while (!res && count--) { + // cmd 1. + fm_cam_read_pre(); + + int i = 0, j = 0; + for (i = 0; i < 20; i++) { + // read buf + fm_send_spi_cmd(0x03, 8, 0x20 * i, 24, 0, 0, &buf[i][0], 32*8, 0); + for (j = 0; j < 32; j++) { + DEBUG(FLASH_PATCH_STR("%02x "), buf[i][j]); + if ((j + 1) % 16 == 0) { + DEBUG(FLASH_PATCH_STR("\n")); + INFO(FLASH_PATCH_STR("\r")); + } } } - } - if (buf[0][0] == 0x55 && buf[0][4] == 0xaa \ - && buf[4][0] == 0x00 \ - && buf[10][0] == 0x1 && buf[10][20] == 0xff\ - && buf[11][0] == 0x1 && buf[11][20] == 0xff\ - && buf[12][0] == 0x1 && buf[12][20] == 0xff\ - && buf[13][0] == 0x1 && buf[13][20] == 0xff\ - && buf[14][0] == 0x1 && buf[14][20] == 0xff\ - && buf[15][0] == 0x1 && buf[15][20] == 0xff\ - && buf[16][0] == 0x1 && buf[16][20] == 0xff\ - && buf[17][0] == 0x1 && buf[17][20] == 0xff - ) { - INFO(FLASH_PATCH_STR("CAM buffer check valid !!!\n")); - res = true; - } else { - INFO(FLASH_PATCH_STR("CAM buffer check IN-Valid !!!\n")); - res = false; + if (buf[0][0] == 0x55 && buf[0][4] == 0xaa \ + && buf[4][0] == 0x00 \ + && buf[10][0] == 0x1 && buf[10][20] == 0xff\ + && buf[11][0] == 0x1 && buf[11][20] == 0xff\ + && buf[12][0] == 0x1 && buf[12][20] == 0xff\ + && buf[13][0] == 0x1 && buf[13][20] == 0xff\ + && buf[14][0] == 0x1 && buf[14][20] == 0xff\ + && buf[15][0] == 0x1 && buf[15][20] == 0xff\ + && buf[16][0] == 0x1 && buf[16][20] == 0xff\ + && buf[17][0] == 0x1 && buf[17][20] == 0xff + ) { + INFO(FLASH_PATCH_STR("CAM buffer check valid !!!\n")); + res = true; + } else { + INFO(FLASH_PATCH_STR("CAM buffer check IN-Valid !!!\n")); + res = false; + } } // while(1); return res; } +static bool FLASH_PATCH_TEXT_ATTR fm_cam_check_uid(void) +{ + bool res = false; + uint8_t count = 3; + uint8_t uid[8]; + + while (!res && count--) { + fm_send_spi_cmd(0x4B, 8, 0, 0, 0, 0, &uid[0], 8*8, 4*8); + DEBUG(FLASH_PATCH_STR("uid 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n"), uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7]); + if ((((uid[1]&0xf0) == 0x0) && ((uid[0]&0x70) == 0)) + || (((uid[1]&0xf0) != 0x0))) { + res = true; + } + } + + return res; +} + static bool FLASH_PATCH_TEXT_ATTR fm_fix_cam() { uint8_t check_buf[20][32]; + if (fm_cam_check_uid()) { + return true; + } + + if (!double_check_fm25q16a()) { + return true; + } + if (fm_cam_check_buf_valid(check_buf) == false) { ERROR(FLASH_PATCH_STR("Cam buf not valid\n")); return esp_fm_check_uid(); @@ -489,6 +539,7 @@ static bool FLASH_PATCH_TEXT_ATTR fm_fix_cam() if ((check_buf[3][0] & 0x08) == 0x08) { INFO(FLASH_PATCH_STR("Bit3 == 1, already fixed....\n")); + fm_set_uid_flag(); fm_soft_reset(); } else { check_buf[3][0] |= 0x08; @@ -515,25 +566,83 @@ static uint32_t FLASH_PATCH_TEXT_ATTR fm_flash_id(void) #endif } +static bool FLASH_PATCH_TEXT_ATTR double_check_fm25q16a(void) +{ + uint32_t addr = 0x10; + uint8_t value[6]; + fm_send_spi_cmd(0x9f, 8, 0, 0, 0, 0, NULL, 0, 0); + + fm_cam_cmd_start(); + fm_send_spi_cmd(0x90, 8, addr, 3 * 8, 0, 0, value, sizeof(value) * 8, 0); + fm_cam_cmd_end(); + DEBUG(FLASH_PATCH_STR("fm25q16a confirm: ")); + for (uint loop = 0; loop < sizeof(value); loop++) { + DEBUG(FLASH_PATCH_STR(" 0x%02x"), value[loop]); + if (value[loop] != 0xA1) { + DEBUG(FLASH_PATCH_STR("\r\n")); + return false; + } + } + DEBUG(FLASH_PATCH_STR("\r\n")); + + return true; +} + +static bool FLASH_PATCH_TEXT_ATTR is_fm25q16a(void) +{ + uint8_t status = 0x0; + uint8_t count = 3; + uint8_t value = 0; + + while (((status&0x20) == 0x0) && count--) { + fm_send_spi_cmd(0x50, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + value = 0x80; + fm_send_spi_cmd(0x01, 1*8, 0, 0, &value, 1*8, NULL, 0, 0); + fm_send_spi_cmd(0x50, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + value = 0x03; + fm_send_spi_cmd(0x31, 1*8, 0, 0, &value, 1*8, NULL, 0, 0); + fm_send_spi_cmd(0x06, 1*8, 0, 0, NULL, 0, NULL, 0, 0); + value = 0x02; + fm_send_spi_cmd(0x31, 1*8, 0, 0, &value, 1*8, NULL, 0, 0); + + fm_flash_wait_idle(); + fm_send_spi_cmd(0x35, 1*8, 0x0, 0, NULL, 0, &status, 1*8, 0); + + fm_soft_reset(); + } + + if ((status&0x20) == 0) { + DEBUG(FLASH_PATCH_STR("It's FM25Q16A\n")); + return true; + } + + DEBUG(FLASH_PATCH_STR("It's FM25Q16B\n")); + return false; +} + int FLASH_PATCH_TEXT_ATTR fm25q16a_apply_patch_0() { - bool res = false; + bool res = true; spi_state_t state; spi_enter(&state); + uint32_t flash_id = fm_flash_id(); DEBUG(FLASH_PATCH_STR("Flash id: 0x%x\n"), flash_id); WDT_FEED(); if (flash_id == 0xa14015) { - INFO(DRAM_STR("Found FM25Q16A, check CAM buf\n")); - res = fm_fix_cam(); + fm_soft_reset(); + INFO(DRAM_STR("Found FM25Q16A or FM25Q16B\n")); + if(is_fm25q16a()) { + INFO(DRAM_STR("Found FM25Q16A, check CAM buf\n")); + res = fm_fix_cam(); + } } else if ((flash_id&0xffffff) == 0x0 || (flash_id&0xffffff) == 0xffffff) { INFO(FLASH_PATCH_STR("Found ID error, recover default CAM buf\n")); res = esp_fm_check_uid(); } else { INFO(FLASH_PATCH_STR("Normal flash, continue...\n")); - res = true; } WDT_FEED(); @@ -541,7 +650,8 @@ int FLASH_PATCH_TEXT_ATTR fm25q16a_apply_patch_0() if (res != true) { fm_printf(FLASH_PATCH_STR("fix fail\n")); - return 1; + // we should keep running + return 0; } fm_printf(FLASH_PATCH_STR("fix done\n")); From ab04069df35c3fac079760dc637581774235779e Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Mon, 3 Mar 2025 10:59:17 +0800 Subject: [PATCH 50/59] Change the addr3 to broadcast for ESP-NOW data Closes https://github.com/espressif/esp-idf/issues/10341 --- components/esp8266/lib/VERSION | 2 +- components/esp8266/lib/libnet80211.a | Bin 485280 -> 485352 bytes components/esp8266/lib/libnet80211_dbg.a | Bin 544180 -> 544252 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp8266/lib/VERSION b/components/esp8266/lib/VERSION index 99cc075df..11b4ea06c 100644 --- a/components/esp8266/lib/VERSION +++ b/components/esp8266/lib/VERSION @@ -1,6 +1,6 @@ gwen: core: db51cd0 - net80211: bb3a27f + net80211: fbfeb71 pp: 12d8a11 espnow: db51cd0 diff --git a/components/esp8266/lib/libnet80211.a b/components/esp8266/lib/libnet80211.a index 161de1f3699844e3fb921b739b20da552c806f16..37e226052f967899da06097a8fce1cfbf7281ddf 100644 GIT binary patch delta 4539 zcmc&%3vg7`89wLSyN4vZOZH_pn+I%`OcI1VHk)jAkst<9U?2@x#YYfUt5Ag&rzlEg zZf4pk9a|m3ae90}Off=gofNp>b!3RI5S$T7P_axII<#d*u$4)vI&>P?{{QBNO;R?I z*YwPM_k92N|K~sddF1A9Yft{Ep8SnDX;l@`nqW;d+#G1sYSIWZxQbHTec^SUX3W;(h(7xj!b@_C1Nvv@Y^(vsL zn<2qi=;$ZvmEa)c9v#~M2K@CTQLi&BOHPsjXoKW3`oF8ha;>0Ut3+3rN4wgU@SoYp zMf{?H@XLcn`1Vu(N92z>xu|`jLCm%8k>_@Dn^=0NNo=uai1YTa{&Mhns_!fQbGqnp zW#|{IOQ~pel?!WD849A*V-uSed&N4(?nFSo?-8nJW~)?>W_?aYne#?{LryMB_-e-& zA+gHn&8l6jcHX7;xbCN7W!2m07X6DI`ft)7qzU8c8JS|)Y>TL$F;SnJ7NYuto_4C2 zheA~BD^AxNC&tn+x#)KON#B~clZm40+r<0T9y#S5ZWkM>ZxYkOKJiRATYOpV5hY=l z*c0}Otzox#7`OxU3)L?1Ir#1XEgnu6YpM&xO3{KeS-Y;75#nu8n*3 zf=BQ6`zp|rY$Ib+#L}vYD`g7xO8~-VjbGGwYB@e{XD*olK zi>1@E@plQC)4Yl;v3^_9&^~jBVaoc z_QFr&GiN!0Mp#40F*AfwQ5Gn@x$-^7i_P>1W-9n7o@bd%`!xRp?>XvPduwetNe{Xf zcE?|6&T3I3jq}xjLT3j2fmt;2#+>RBW#cCQi%MWT`Q!7J+`85#aJAMsV|(Kz?qvZ7 zSd-m#Wks>KrW?oF`C3Y+ z%ZXnzuV`ABCj8&3vcE_5$QH&tM$7AbH%Ns>~^qKh|NsI`zr|+ib8rq2#~|3jhd63jeh|)d^wX3K|s4t z{&NRECtk_Uka>mFE>G{|apQ|!d<)Y&5JpSk0S#-XQxlZ@5;_m&fVLHY9MkVIIlA_l z9QW@pCdVB8Cdb@UpcEX7>%Gb3dBB$saEQ;U)WaDQ3V1;723A|3_=aL)zqn3DBhrn zCdZrAY;wF!5s0TjctC40b<~hsO&xbEvFq~mo4nB%!q|ajdNKyi0heqI$@>lN^Id(fSTo(d+l$=Ys6 zHA6i1j8{I5$`kTgR9476*mcX}(AiJ8Vd)AXWzbC(1?{<(58WLW_l`ycV! z1(WCrx&8nzaKu*s5Z=kj_h{mMydgHvV#MZg&%M5?n&cZ9STYb^xL*BtzdE0nohm}_ zY}ayq(aIDFjE)KHRjV%;95#N~1X;$ZqpWS5Y*@rx7k~ERo8D+z#Ck0`<-w}p(Co>< z5|Vq3uh!B(td>oU%81cB8kN3VV01XeXrtpkW@SG3k)0y@gfrA@{Cl3V!I@KDSut#d zBWu`qg4rJNRaTBNm5pVGQp|*v*qJwU={oQXjE_%Iw<23xhAeVm^@m%O#dkn`krY$w zohL7B!oAIKtX}cduv;KHPkN1e`sk~+obpgbitz&naU<(3hM?j9n3J1e9QyvaPh1d? zYK6E4-CqBANQ;; z6pf_XM_leM^{Dhcy!4^i@|6#n-zg*RySdl>{WN)@5`Qp8>3O=mK$e^xdI*V2a?e?% H=birolqZDV delta 4440 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 delta 4395 zcmcgveNa?Y6uKuE5iw=57PzH zij)Emhsp!N!@+|(dR@{&bTwlbLOp-Q=91r_g|zi1&` zJ3S40Zq-7O_B(l221{1%O`A)hDlJi-Fef2RKJqh5hpI+BY|$lxQ>TX-U31u*wNf-F z+b+*!vU8?nkq6&pM!3n+4b!r&4EyC{ViNS*Zi7v(uV80-NmyJu_q%>4#aG=+<`%2NFi zopM;$L6y(*Jrs85Cc?hy7T9d>f;{gGIOsFMyFLr_dJ~}2n-BMUQ(=uS6$WvA2z__@ zG9U@pUatl|^_t*$pBZMNeO%R!dsD!NS~li5fw;0Y8Ct!`(Bso(mN;!O!!>F6r;wan z$dAuz-MWeSq}n`dscY0EHH^);JY)2jej8gf z-s<;_mPpwH1DQQ_{adqYSS zFv?dNT~X9##jgu`hV6^#U#vfoVdoN!4fT&=^Uk;;WfXobb{XUavoxmDKT#w1(l$#| zpOabQj(5yRRQ=VgRd02BV!9f&2Oh!_I(1xJSRSt7p2mk_YY6)XtJtTKoOS&}>;_MPhGsh+DU2gTb%i>(b z%4}h4O8AiVjr=7&vq7X&kMCn2&Xt3GLR6!}FT(ylVc|3U21NPfeVXL6uA$(5&F9Ck zIIla-zL?_|S)Q|x>5R1vi_Yn8`V(#4p|)B$Q z_SnkouGip)Rk?h&1nb|$tF*p74|aPBKpRK{W;`C)z*b=2kBRB9+N_TANn3`E%Lo6aQhdk zys&mWg^$<$q3mh>SqSjGsS_t0YY?eC6OL^N&UvC_{+O!s?oUKc z-8kX+62arS#<^bA`B|w4CmaLh_DB#H2M~JIF^HT$%&*!qTs5vOD(BZ;iZPT=I5wy{ zH#ygr$^#$ZAMrySq5-BrrPUV>3m2cNC&Y**KZxF^Y=t}Yv>FDtK1|oa(rr~#3-94P z5mJH=(`nEWtU~=za010w>Kkf?j83C8)r@_DqD}|>7Up*5O9c|za`S$4w{;rnX6Qt5 zHHzklcofBI_zlI4C?-Wj=XN7)glo6w(;d*h-2zQ$b2e=EhwWqA^Q9g5kXvs54%6GZ zjPxsTb>&N@1VXCi=A$S!ptug2yYguryn?d``!S#c`?m*9bvdM4k_b5=A9D&0S_(6} kbL0VSg7)-BEcCZF!Sa%|)ae=d^CjbF(-LYgklr!<1NtH;ng9R* From d515353777ad74419e8b677cac05f315684a2566 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Fri, 7 Mar 2025 16:15:01 +0800 Subject: [PATCH 51/59] feat: Improve th25q16hb flash patch --- components/spi_flash/src/patch/th25q16hb.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/components/spi_flash/src/patch/th25q16hb.c b/components/spi_flash/src/patch/th25q16hb.c index 4548e2c60..d3f255a67 100644 --- a/components/spi_flash/src/patch/th25q16hb.c +++ b/components/spi_flash/src/patch/th25q16hb.c @@ -88,8 +88,25 @@ int FLASH_PATCH_TEXT_ATTR th25q16hb_apply_patch_0(void) flash_id = spi_flash_get_id(); if (flash_id != 0x1560eb) { - ROM_PRINTF(FLASH_PATCH_STR("WARN: id=0x%x, is not TH25Q16HB\n"), flash_id); - return 0; + uint32_t data = 0; + bool is_th25q16hb = false; + if (flash_id == 0x0) { + spi_trans(0, 0x5A, 8, 0x10, 24, &data, 1, 0); + if (data == 0xEB) { + spi_trans(0, 0x5A, 8, 0x14, 24, &data, 1, 0); + if (data == 0x60) { + spi_trans(0, 0x5A, 8, 0x34, 24, &data, 4, 0); + if (data == 0xFFFFFF00) { + is_th25q16hb = true; + } + } + } + } + + if (!is_th25q16hb) { + ROM_PRINTF(FLASH_PATCH_STR("WARN: id=0x%x, is not TH25Q16HB\n"), flash_id); + return 0; + } } buffer256_0 = buffer1024; From 0325f833aac7b34d7627619499949331a98fc3ab Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 17 Apr 2025 15:42:27 +0800 Subject: [PATCH 52/59] chore(coap): Modify libcoap URL from github to jihulab --- .gitmodules | 2 +- components/coap/libcoap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index d36acbfe5..5051ce92e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,4 +16,4 @@ [submodule "components/coap/libcoap"] path = components/coap/libcoap - url = ../../obgm/libcoap.git + url = https://jihulab.com/esp-mirror/obgm/libcoap.git diff --git a/components/coap/libcoap b/components/coap/libcoap index cfec0d072..0dbccca06 160000 --- a/components/coap/libcoap +++ b/components/coap/libcoap @@ -1 +1 @@ -Subproject commit cfec0d072c5b99ed3e54828ca50ea2f6b91e1f50 +Subproject commit 0dbccca06c3ea75a20ef0a51633eb424a35b5e2a From d6a19654b4b10067ebff4cd959c1ed6f7f01c26e Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Thu, 17 Apr 2025 14:44:35 +0800 Subject: [PATCH 53/59] feat: Optimize dram size for libsodium test code --- components/libsodium/libsodium/test/default/sign.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/libsodium/libsodium/test/default/sign.c b/components/libsodium/libsodium/test/default/sign.c index 814f96729..4faacf368 100644 --- a/components/libsodium/libsodium/test/default/sign.c +++ b/components/libsodium/libsodium/test/default/sign.c @@ -14,7 +14,7 @@ typedef struct TestData_ { const char *m; } TestData; -static TestData test_data[] = { +static const TestData test_data[] = { {{0x9d,0x61,0xb1,0x9d,0xef,0xfd,0x5a,0x60,0xba,0x84,0x4a,0xf4,0x92,0xec,0x2c,0xc4,0x44,0x49,0xc5,0x69,0x7b,0x32,0x69,0x19,0x70,0x3b,0xac,0x03,0x1c,0xae,0x7f,0x60,},{0xd7,0x5a,0x98,0x01,0x82,0xb1,0x0a,0xb7,0xd5,0x4b,0xfe,0xd3,0xc9,0x64,0x07,0x3a,0x0e,0xe1,0x72,0xf3,0xda,0xa6,0x23,0x25,0xaf,0x02,0x1a,0x68,0xf7,0x07,0x51,0x1a,},{0xe5,0x56,0x43,0x00,0xc3,0x60,0xac,0x72,0x90,0x86,0xe2,0xcc,0x80,0x6e,0x82,0x8a,0x84,0x87,0x7f,0x1e,0xb8,0xe5,0xd9,0x74,0xd8,0x73,0xe0,0x65,0x22,0x49,0x01,0x55,0x5f,0xb8,0x82,0x15,0x90,0xa3,0x3b,0xac,0xc6,0x1e,0x39,0x70,0x1c,0xf9,0xb4,0x6b,0xd2,0x5b,0xf5,0xf0,0x59,0x5b,0xbe,0x24,0x65,0x51,0x41,0x43,0x8e,0x7a,0x10,0x0b,},""}, {{0x4c,0xcd,0x08,0x9b,0x28,0xff,0x96,0xda,0x9d,0xb6,0xc3,0x46,0xec,0x11,0x4e,0x0f,0x5b,0x8a,0x31,0x9f,0x35,0xab,0xa6,0x24,0xda,0x8c,0xf6,0xed,0x4f,0xb8,0xa6,0xfb,},{0x3d,0x40,0x17,0xc3,0xe8,0x43,0x89,0x5a,0x92,0xb7,0x0a,0xa7,0x4d,0x1b,0x7e,0xbc,0x9c,0x98,0x2c,0xcf,0x2e,0xc4,0x96,0x8c,0xc0,0xcd,0x55,0xf1,0x2a,0xf4,0x66,0x0c,},{0x92,0xa0,0x09,0xa9,0xf0,0xd4,0xca,0xb8,0x72,0x0e,0x82,0x0b,0x5f,0x64,0x25,0x40,0xa2,0xb2,0x7b,0x54,0x16,0x50,0x3f,0x8f,0xb3,0x76,0x22,0x23,0xeb,0xdb,0x69,0xda,0x08,0x5a,0xc1,0xe4,0x3e,0x15,0x99,0x6e,0x45,0x8f,0x36,0x13,0xd0,0xf1,0x1d,0x8c,0x38,0x7b,0x2e,0xae,0xb4,0x30,0x2a,0xee,0xb0,0x0d,0x29,0x16,0x12,0xbb,0x0c,0x00,},"\x72"}, {{0xc5,0xaa,0x8d,0xf4,0x3f,0x9f,0x83,0x7b,0xed,0xb7,0x44,0x2f,0x31,0xdc,0xb7,0xb1,0x66,0xd3,0x85,0x35,0x07,0x6f,0x09,0x4b,0x85,0xce,0x3a,0x2e,0x0b,0x44,0x58,0xf7,},{0xfc,0x51,0xcd,0x8e,0x62,0x18,0xa1,0xa3,0x8d,0xa4,0x7e,0xd0,0x02,0x30,0xf0,0x58,0x08,0x16,0xed,0x13,0xba,0x33,0x03,0xac,0x5d,0xeb,0x91,0x15,0x48,0x90,0x80,0x25,},{0x62,0x91,0xd6,0x57,0xde,0xec,0x24,0x02,0x48,0x27,0xe6,0x9c,0x3a,0xbe,0x01,0xa3,0x0c,0xe5,0x48,0xa2,0x84,0x74,0x3a,0x44,0x5e,0x36,0x80,0xd7,0xdb,0x5a,0xc3,0xac,0x18,0xff,0x9b,0x53,0x8d,0x16,0xf2,0x90,0xae,0x67,0xf7,0x60,0x98,0x4d,0xc6,0x59,0x4a,0x7c,0x15,0xe9,0x71,0x6e,0xd2,0x8d,0xc0,0x27,0xbe,0xce,0xea,0x1e,0xc4,0x0a,},"\xaf\x82"}, From 891ad98a6e7be698b3228ed4a633f97c3ffe0a2c Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 17 Apr 2025 20:15:57 +0800 Subject: [PATCH 54/59] tools(ci): Support multiple CI sdkconfig files --- .../get-started/hello_world/sdkconfig.ci.2MB | 1 + .../get-started/hello_world/sdkconfig.ci.4MB | 1 + tools/ci/build_examples.sh | 110 +++++++++++------- tools/ci/build_examples_cmake.sh | 99 ++++++++++------ 4 files changed, 129 insertions(+), 82 deletions(-) create mode 100644 examples/get-started/hello_world/sdkconfig.ci.2MB create mode 100644 examples/get-started/hello_world/sdkconfig.ci.4MB diff --git a/examples/get-started/hello_world/sdkconfig.ci.2MB b/examples/get-started/hello_world/sdkconfig.ci.2MB new file mode 100644 index 000000000..b6c645057 --- /dev/null +++ b/examples/get-started/hello_world/sdkconfig.ci.2MB @@ -0,0 +1 @@ +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y diff --git a/examples/get-started/hello_world/sdkconfig.ci.4MB b/examples/get-started/hello_world/sdkconfig.ci.4MB new file mode 100644 index 000000000..df4c6ef13 --- /dev/null +++ b/examples/get-started/hello_world/sdkconfig.ci.4MB @@ -0,0 +1 @@ +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y diff --git a/tools/ci/build_examples.sh b/tools/ci/build_examples.sh index b30f5c51f..fbeaf65ae 100755 --- a/tools/ci/build_examples.sh +++ b/tools/ci/build_examples.sh @@ -118,53 +118,75 @@ build_example () { local EXAMPLE_DIR=$(dirname "${MAKE_FILE}") local EXAMPLE_NAME=$(basename "${EXAMPLE_DIR}") - local EXAMPLE_BUILD_DIR="${ID}_${EXAMPLE_NAME}" + local EXAMPLE_BUILD_DIRS=() - if [[ -f "example_builds/${EXAMPLE_BUILD_DIR}/build/ci_build_success" ]]; then - echo "Project ${EXAMPLE_BUILD_DIR} has been built and skip building ..." + # count number of CI sdkconfig files + SDKCONFIG_CI_FILES=$( find ${EXAMPLE_DIR}/ -type f -name sdkconfig.ci.* | sort ) + if [[ -z ${SDKCONFIG_CI_FILES} ]]; then + EXAMPLE_BUILD_DIRS[0]="${ID}_${EXAMPLE_NAME}" else - echo "Building ${EXAMPLE_BUILD_DIR}..." - mkdir -p "example_builds/${EXAMPLE_BUILD_DIR}" - cp -r "${EXAMPLE_DIR}/"* "example_builds/${EXAMPLE_BUILD_DIR}/" - pushd "example_builds/${EXAMPLE_BUILD_DIR}" - # be stricter in the CI build than the default IDF settings - export EXTRA_CFLAGS="-Werror -Werror=deprecated-declarations" - export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} - - # sdkconfig files are normally not checked into git, but may be present when - # a developer runs this script locally - rm -f sdkconfig - - # If sdkconfig.ci file is present, append it to sdkconfig.defaults, - # replacing environment variables - if [[ -f "$SDKCONFIG_DEFAULTS_CI" ]]; then - cat $SDKCONFIG_DEFAULTS_CI | $IDF_PATH/tools/ci/envsubst.py >> sdkconfig.defaults - fi - - # build non-verbose first - local BUILDLOG=${LOG_PATH}/ex_${EXAMPLE_BUILD_DIR}_log.txt - touch ${BUILDLOG} - - local FLASH_ARGS=build/download.config - - make clean >>${BUILDLOG} 2>&1 && - make defconfig >>${BUILDLOG} 2>&1 && - make all -j4 >>${BUILDLOG} 2>&1 && - make ota >>${BUILDLOG} 2>&1 && - make print_flash_cmd >${FLASH_ARGS}.full 2>>${BUILDLOG} && - touch build/ci_build_success || - { - RESULT=$?; FAILED_EXAMPLES+=" ${EXAMPLE_NAME}" ; - } - - tail -n 1 ${FLASH_ARGS}.full > ${FLASH_ARGS} || : - test -s ${FLASH_ARGS} || die "Error: ${FLASH_ARGS} file is empty" - - cat ${BUILDLOG} - popd - - grep -i "error\|warning" "${BUILDLOG}" 2>&1 >> "${LOG_SUSPECTED}" || : + COUNT=0 + for CI_FILE in ${SDKCONFIG_CI_FILES} + do + EXAMPLE_BUILD_DIRS[COUNT]="${ID}_${EXAMPLE_NAME}_${CI_FILE##*.}" + COUNT=$(( $COUNT + 1 )) + done fi + + for EXAMPLE_BUILD_DIR in ${EXAMPLE_BUILD_DIRS[*]} + do + if [[ -f "example_builds/${EXAMPLE_BUILD_DIR}/build/ci_build_success" ]]; then + echo "Project ${EXAMPLE_BUILD_DIR} has been built and skip building ..." + else + echo "Building ${EXAMPLE_BUILD_DIR}..." + mkdir -p "example_builds/${EXAMPLE_BUILD_DIR}" + cp -r "${EXAMPLE_DIR}/"* "example_builds/${EXAMPLE_BUILD_DIR}/" + + if [[ -n ${SDKCONFIG_CI_FILES} ]]; then + cp "example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci.${EXAMPLE_BUILD_DIR##*_}" "example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci" + rm example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci.* + fi + + pushd "example_builds/${EXAMPLE_BUILD_DIR}" + # be stricter in the CI build than the default IDF settings + export EXTRA_CFLAGS="-Werror -Werror=deprecated-declarations" + export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} + + # sdkconfig files are normally not checked into git, but may be present when + # a developer runs this script locally + rm -f sdkconfig + + # If sdkconfig.ci file is present, append it to sdkconfig.defaults, + # replacing environment variables + if [[ -f "$SDKCONFIG_DEFAULTS_CI" ]]; then + cat $SDKCONFIG_DEFAULTS_CI | $IDF_PATH/tools/ci/envsubst.py >> sdkconfig.defaults + fi + + # build non-verbose first + local BUILDLOG=${LOG_PATH}/ex_${EXAMPLE_BUILD_DIR}_log.txt + touch ${BUILDLOG} + + local FLASH_ARGS=build/download.config + + make clean >>${BUILDLOG} 2>&1 && + make defconfig >>${BUILDLOG} 2>&1 && + make all -j4 >>${BUILDLOG} 2>&1 && + make ota >>${BUILDLOG} 2>&1 && + make print_flash_cmd >${FLASH_ARGS}.full 2>>${BUILDLOG} && + touch build/ci_build_success || + { + RESULT=$?; FAILED_EXAMPLES+=" ${EXAMPLE_NAME}" ; + } + + tail -n 1 ${FLASH_ARGS}.full > ${FLASH_ARGS} || : + test -s ${FLASH_ARGS} || die "Error: ${FLASH_ARGS} file is empty" + + cat ${BUILDLOG} + popd + + grep -i "error\|warning" "${BUILDLOG}" 2>&1 >> "${LOG_SUSPECTED}" || : + fi + done } EXAMPLE_NUM=0 diff --git a/tools/ci/build_examples_cmake.sh b/tools/ci/build_examples_cmake.sh index 914dbcc2e..24063bad9 100755 --- a/tools/ci/build_examples_cmake.sh +++ b/tools/ci/build_examples_cmake.sh @@ -132,47 +132,70 @@ build_example () { local EXAMPLE_DIR=$(dirname "${CMAKELISTS}") local EXAMPLE_NAME=$(basename "${EXAMPLE_DIR}") - local EXAMPLE_BUILD_DIR="${ID}_${EXAMPLE_NAME}" + local EXAMPLE_BUILD_DIRS=() - if [[ -f "example_builds/${EXAMPLE_BUILD_DIR}/build/ci_build_success" ]]; then - echo "Project ${EXAMPLE_NAME} has been built and skip building ..." + # count number of CI sdkconfig files + SDKCONFIG_CI_FILES=$( find ${EXAMPLE_DIR}/ -type f -name sdkconfig.ci.* | sort ) + if [[ -z ${SDKCONFIG_CI_FILES} ]]; then + EXAMPLE_BUILD_DIRS[0]="${ID}_${EXAMPLE_NAME}" else - echo "Building ${EXAMPLE_BUILD_DIR}..." - mkdir -p "example_builds/${EXAMPLE_BUILD_DIR}" - cp -r "${EXAMPLE_DIR}/"* "example_builds/${EXAMPLE_BUILD_DIR}" - pushd "example_builds/${EXAMPLE_BUILD_DIR}" - # be stricter in the CI build than the default IDF settings - export EXTRA_CFLAGS="-Werror -Werror=deprecated-declarations" - export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} - - prepare_build ${EXAMPLE_NAME} - - # sdkconfig files are normally not checked into git, but may be present when - # a developer runs this script locally - rm -f sdkconfig - - # If sdkconfig.ci file is present, append it to sdkconfig.defaults, - # replacing environment variables - if [[ -f "$SDKCONFIG_DEFAULTS_CI" ]]; then - cat $SDKCONFIG_DEFAULTS_CI | $IDF_PATH/tools/ci/envsubst.py >> sdkconfig.defaults - fi - - # build non-verbose first - local BUILDLOG=${LOG_PATH}/ex_${EXAMPLE_BUILD_DIR}_log.txt - touch ${BUILDLOG} - - idf.py build >>${BUILDLOG} 2>&1 && - cp build/flash_project_args build/download.config && # backwards compatible download.config filename - touch build/ci_build_success || - { - RESULT=$?; FAILED_EXAMPLES+=" ${EXAMPLE_NAME}" ; - } - - cat ${BUILDLOG} - popd - - grep -i "error\|warning" "${BUILDLOG}" 2>&1 | grep -v "error.c.obj" >> "${LOG_SUSPECTED}" || : + COUNT=0 + for CI_FILE in ${SDKCONFIG_CI_FILES} + do + echo "${COUNT} ${CI_FILE}" + EXAMPLE_BUILD_DIRS[COUNT]="${ID}_${EXAMPLE_NAME}_${CI_FILE##*.}" + COUNT=$(( $COUNT + 1 )) + done fi + + for EXAMPLE_BUILD_DIR in ${EXAMPLE_BUILD_DIRS[*]} + do + if [[ -f "example_builds/${EXAMPLE_BUILD_DIR}/build/ci_build_success" ]]; then + echo "Project ${EXAMPLE_BUILD_DIR} has been built and skip building ..." + else + echo "Building ${EXAMPLE_BUILD_DIR}..." + mkdir -p "example_builds/${EXAMPLE_BUILD_DIR}" + cp -r "${EXAMPLE_DIR}/"* "example_builds/${EXAMPLE_BUILD_DIR}/" + + if [[ -n ${SDKCONFIG_CI_FILES} ]]; then + cp "example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci.${EXAMPLE_BUILD_DIR##*_}" "example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci" + rm example_builds/${EXAMPLE_BUILD_DIR}/sdkconfig.ci.* + fi + + pushd "example_builds/${EXAMPLE_BUILD_DIR}" + # be stricter in the CI build than the default IDF settings + export EXTRA_CFLAGS="-Werror -Werror=deprecated-declarations" + export EXTRA_CXXFLAGS=${EXTRA_CFLAGS} + + prepare_build ${EXAMPLE_NAME} + + # sdkconfig files are normally not checked into git, but may be present when + # a developer runs this script locally + rm -f sdkconfig + + # If sdkconfig.ci file is present, append it to sdkconfig.defaults, + # replacing environment variables + if [[ -f "$SDKCONFIG_DEFAULTS_CI" ]]; then + cat $SDKCONFIG_DEFAULTS_CI | $IDF_PATH/tools/ci/envsubst.py >> sdkconfig.defaults + fi + + # build non-verbose first + local BUILDLOG=${LOG_PATH}/ex_${EXAMPLE_BUILD_DIR}_log.txt + touch ${BUILDLOG} + + idf.py build >>${BUILDLOG} 2>&1 && + cp build/flash_project_args build/download.config && # backwards compatible download.config filename + touch build/ci_build_success || + { + RESULT=$?; FAILED_EXAMPLES+=" ${EXAMPLE_NAME}" ; + } + + cat ${BUILDLOG} + popd + + grep -i "error\|warning" "${BUILDLOG}" 2>&1 | grep -v "error.c.obj" >> "${LOG_SUSPECTED}" || : + fi + done } EXAMPLE_NUM=0 From 2cfa9d40c23b5c34ba839ac678f6cb33f94a43cb Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Tue, 15 Apr 2025 16:50:21 +0800 Subject: [PATCH 55/59] refactor: Rename old mbedtls to mbedtls_v2 --- .gitmodules | 4 +- components/mbedtls/CMakeLists.txt | 76 +- components/mbedtls/Kconfig | 664 +----------------- components/mbedtls/mbedtls_v2/CMakeLists.txt | 81 +++ components/mbedtls/mbedtls_v2/Kconfig | 660 +++++++++++++++++ .../{ => mbedtls_v2}/Makefile.projbuild | 0 .../mbedtls/{ => mbedtls_v2}/component.mk | 0 components/mbedtls/{ => mbedtls_v2}/mbedtls | 0 .../port/dynamic/esp_mbedtls_dynamic_impl.c | 0 .../port/dynamic/esp_mbedtls_dynamic_impl.h | 0 .../port/dynamic/esp_ssl_cli.c | 0 .../port/dynamic/esp_ssl_srv.c | 0 .../port/dynamic/esp_ssl_tls.c | 0 .../{ => mbedtls_v2}/port/esp8266/aes.c | 0 .../{ => mbedtls_v2}/port/esp8266/arc4.c | 0 .../{ => mbedtls_v2}/port/esp8266/base64.c | 0 .../{ => mbedtls_v2}/port/esp8266/md5.c | 0 .../{ => mbedtls_v2}/port/esp8266/sha1.c | 0 .../{ => mbedtls_v2}/port/esp8266/sha256.c | 0 .../{ => mbedtls_v2}/port/esp8266/sha512.c | 0 .../mbedtls/{ => mbedtls_v2}/port/esp_aes.c | 0 .../{ => mbedtls_v2}/port/esp_hardware.c | 0 .../mbedtls/{ => mbedtls_v2}/port/esp_mem.c | 0 .../mbedtls/{ => mbedtls_v2}/port/esp_sha1.c | 0 .../{ => mbedtls_v2}/port/esp_sha256.c | 0 .../{ => mbedtls_v2}/port/esp_sha512.c | 0 .../{ => mbedtls_v2}/port/esp_timing.c | 0 .../{ => mbedtls_v2}/port/include/aes_alt.h | 0 .../{ => mbedtls_v2}/port/include/arc4_alt.h | 0 .../port/include/esp8266/esp_aes.h | 0 .../port/include/esp8266/esp_arc4.h | 0 .../port/include/esp8266/esp_base64.h | 0 .../port/include/esp8266/esp_md5.h | 0 .../{ => mbedtls_v2}/port/include/esp_mem.h | 0 .../port/include/mbedtls/esp_config.h | 2 + .../port/include/mbedtls/esp_debug.h | 0 .../{ => mbedtls_v2}/port/include/md5_alt.h | 0 .../{ => mbedtls_v2}/port/include/sha1_alt.h | 0 .../port/include/sha256_alt.h | 0 .../port/include/sha512_alt.h | 0 .../{ => mbedtls_v2}/port/mbedtls_debug.c | 0 .../{ => mbedtls_v2}/port/net_sockets.c | 0 .../{ => mbedtls_v2}/test/CMakeLists.txt | 0 .../{ => mbedtls_v2}/test/component.mk | 0 .../mbedtls/{ => mbedtls_v2}/test/test_aes.c | 0 .../{ => mbedtls_v2}/test/test_aes_perf.c | 0 .../test/test_apb_dport_access.c | 0 .../test/test_apb_dport_access.h | 0 .../mbedtls/{ => mbedtls_v2}/test/test_arc4.c | 0 .../{ => mbedtls_v2}/test/test_base64.c | 0 .../mbedtls/{ => mbedtls_v2}/test/test_crc.c | 0 .../mbedtls/{ => mbedtls_v2}/test/test_ecp.c | 0 .../{ => mbedtls_v2}/test/test_mbedtls.c | 0 .../{ => mbedtls_v2}/test/test_mbedtls_mpi.c | 0 .../{ => mbedtls_v2}/test/test_mbedtls_sha.c | 0 .../mbedtls/{ => mbedtls_v2}/test/test_md5.c | 0 .../mbedtls/{ => mbedtls_v2}/test/test_rsa.c | 0 .../mbedtls/{ => mbedtls_v2}/test/test_sha.c | 0 58 files changed, 756 insertions(+), 731 deletions(-) create mode 100644 components/mbedtls/mbedtls_v2/CMakeLists.txt create mode 100644 components/mbedtls/mbedtls_v2/Kconfig rename components/mbedtls/{ => mbedtls_v2}/Makefile.projbuild (100%) rename components/mbedtls/{ => mbedtls_v2}/component.mk (100%) rename components/mbedtls/{ => mbedtls_v2}/mbedtls (100%) rename components/mbedtls/{ => mbedtls_v2}/port/dynamic/esp_mbedtls_dynamic_impl.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/dynamic/esp_mbedtls_dynamic_impl.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/dynamic/esp_ssl_cli.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/dynamic/esp_ssl_srv.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/dynamic/esp_ssl_tls.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/aes.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/arc4.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/base64.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/md5.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/sha1.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/sha256.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp8266/sha512.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_aes.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_hardware.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_mem.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_sha1.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_sha256.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_sha512.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/esp_timing.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/aes_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/arc4_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/esp8266/esp_aes.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/esp8266/esp_arc4.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/esp8266/esp_base64.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/esp8266/esp_md5.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/esp_mem.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/mbedtls/esp_config.h (99%) rename components/mbedtls/{ => mbedtls_v2}/port/include/mbedtls/esp_debug.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/md5_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/sha1_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/sha256_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/include/sha512_alt.h (100%) rename components/mbedtls/{ => mbedtls_v2}/port/mbedtls_debug.c (100%) rename components/mbedtls/{ => mbedtls_v2}/port/net_sockets.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/CMakeLists.txt (100%) rename components/mbedtls/{ => mbedtls_v2}/test/component.mk (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_aes.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_aes_perf.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_apb_dport_access.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_apb_dport_access.h (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_arc4.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_base64.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_crc.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_ecp.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_mbedtls.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_mbedtls_mpi.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_mbedtls_sha.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_md5.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_rsa.c (100%) rename components/mbedtls/{ => mbedtls_v2}/test/test_sha.c (100%) diff --git a/.gitmodules b/.gitmodules index 5051ce92e..e5959514a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,8 +2,8 @@ path = components/json/cJSON url = ../../DaveGamble/cJSON.git -[submodule "components/mbedtls/mbedtls"] - path = components/mbedtls/mbedtls +[submodule "components/mbedtls/mbedtls_v2/mbedtls"] + path = components/mbedtls/mbedtls_v2/mbedtls url = ../../espressif/mbedtls.git [submodule "components/lwip/lwip"] diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index ea61a5217..fb421f383 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -1,74 +1,6 @@ -idf_build_get_property(target IDF_TARGET) -idf_component_register(INCLUDE_DIRS "port/include" "port/include/${target}" "mbedtls/include" - REQUIRES lwip) - -# Only build mbedtls libraries -set(ENABLE_TESTING CACHE BOOL OFF) -set(ENABLE_PROGRAMS CACHE BOOL OFF) - -# Needed to for include_next includes to work from within mbedtls -include_directories("${COMPONENT_DIR}/port/include" "${COMPONENT_DIR}/port/include/${target}") - -# Import mbedtls library targets -add_subdirectory(mbedtls) - -# Use port specific implementation of net_socket.c instead of one from mbedtls -get_target_property(src_tls mbedtls SOURCES) -list(REMOVE_ITEM src_tls net_sockets.c) -set_property(TARGET mbedtls PROPERTY SOURCES ${src_tls}) - -set(mbedtls_targets mbedtls mbedcrypto mbedx509) - -# Add port files to mbedtls targets -set(mbedtls_target_sources "${COMPONENT_DIR}/port/mbedtls_debug.c" - "${COMPONENT_DIR}/port/net_sockets.c") - -if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) -set(mbedtls_target_sources ${mbedtls_target_sources} - "${COMPONENT_DIR}/port/dynamic/esp_mbedtls_dynamic_impl.c" - "${COMPONENT_DIR}/port/dynamic/esp_ssl_cli.c" - "${COMPONENT_DIR}/port/dynamic/esp_ssl_srv.c" - "${COMPONENT_DIR}/port/dynamic/esp_ssl_tls.c") -endif() - -target_sources(mbedtls PRIVATE ${mbedtls_target_sources}) - -target_sources(mbedcrypto PRIVATE "${COMPONENT_DIR}/port/esp_hardware.c" - "${COMPONENT_DIR}/port/esp_mem.c" - "${COMPONENT_DIR}/port/esp_timing.c" - "${COMPONENT_DIR}/port/esp_aes.c" - "${COMPONENT_DIR}/port/esp_sha1.c" - "${COMPONENT_DIR}/port/esp_sha256.c" - "${COMPONENT_DIR}/port/esp_sha512.c" - "${COMPONENT_DIR}/port/${target}/aes.c" - "${COMPONENT_DIR}/port/${target}/arc4.c" - "${COMPONENT_DIR}/port/${target}/base64.c" - "${COMPONENT_DIR}/port/${target}/md5.c" - "${COMPONENT_DIR}/port/${target}/sha1.c" - "${COMPONENT_DIR}/port/${target}/sha256.c" - "${COMPONENT_DIR}/port/${target}/sha512.c") - - -foreach(target ${mbedtls_targets}) - target_compile_definitions(${target} PUBLIC -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DCONFIG_SSL_USING_MBEDTLS) -endforeach() - -if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) - set(WRAP_FUNCTIONS - mbedtls_ssl_handshake_client_step - mbedtls_ssl_handshake_server_step - mbedtls_ssl_read - mbedtls_ssl_write - mbedtls_ssl_session_reset - mbedtls_ssl_free - mbedtls_ssl_setup - mbedtls_ssl_send_alert_message - mbedtls_ssl_close_notify) - - foreach(wrap ${WRAP_FUNCTIONS}) - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") - endforeach() +if (CONFIG_MBEDTLS_V2) +set(MBEDTLS_V2 1) +message("Use MBEDTLS_V2") endif() -# Link mbedtls libraries to component library -target_link_libraries(${COMPONENT_LIB} INTERFACE ${mbedtls_targets}) +include("${IDF_PATH}/components/mbedtls/mbedtls_v2/CMakeLists.txt") \ No newline at end of file diff --git a/components/mbedtls/Kconfig b/components/mbedtls/Kconfig index 0bec091bd..243182542 100644 --- a/components/mbedtls/Kconfig +++ b/components/mbedtls/Kconfig @@ -1,660 +1,10 @@ -menu "mbedTLS" +choice MBEDTLS_VERSION + prompt "Mbedtls Version" + default MBEDTLS_V2 - choice MBEDTLS_MEM_ALLOC_MODE - prompt "Memory allocation strategy" - default MBEDTLS_INTERNAL_MEM_ALLOC - help - Allocation strategy for mbedTLS, essentially provides ability to - allocate all required dynamic allocations from, + config MBEDTLS_V2 + bool "MbedTls V2.x" - - Internal DRAM memory only - - External SPIRAM memory only - - Either internal or external memory based on default malloc() - behavior in ESP-IDF - - Custom allocation mode, by overwriting calloc()/free() using - mbedtls_platform_set_calloc_free() function - Recommended mode here is always internal, since that is most preferred - from security perspective. But if application requirement does not - allow sufficient free internal memory then alternate mode can be - selected. - - config MBEDTLS_INTERNAL_MEM_ALLOC - bool "Internal memory" - - config MBEDTLS_EXTERNAL_MEM_ALLOC - bool "External SPIRAM" - depends on ESP32_SPIRAM_SUPPORT - - config MBEDTLS_DEFAULT_MEM_ALLOC - bool "Default alloc mode" - - config MBEDTLS_CUSTOM_MEM_ALLOC - bool "Custom alloc mode" - - endchoice #MBEDTLS_MEM_ALLOC_MODE - - config MBEDTLS_SSL_MAX_CONTENT_LEN - int "TLS maximum message content length" - default 16384 - range 512 16384 - depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - Maximum TLS message length (in bytes) supported by mbedTLS. - - 16384 is the default and this value is required to comply - fully with TLS standards. - - However you can set a lower value in order to save RAM. This - is safe if the other end of the connection supports Maximum - Fragment Length Negotiation Extension (max_fragment_length, - see RFC6066) or you know for certain that it will never send a - message longer than a certain number of bytes. - - If the value is set too low, symptoms are a failed TLS - handshake or a return value of MBEDTLS_ERR_SSL_INVALID_RECORD - (-0x7200). - - config MBEDTLS_ASYMMETRIC_CONTENT_LEN - bool "Asymmetric in/out fragment length" - default y - help - If enabled, this option allows customizing TLS in/out fragment length - in asymmetric way. Please note that enabling this with default values - saves 12KB of dynamic memory per TLS connection. - - config MBEDTLS_SSL_IN_CONTENT_LEN - int "TLS maximum incoming fragment length" - default 16384 - range 512 16384 - depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - This defines maximum incoming fragment length, overriding default - maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). - - config MBEDTLS_SSL_OUT_CONTENT_LEN - int "TLS maximum outgoing fragment length" - default 4096 - range 512 16384 - depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - This defines maximum outgoing fragment length, overriding default - maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). - - config MBEDTLS_DYNAMIC_BUFFER - bool "Using dynamic TX/RX buffer" - default n - select MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - Using dynamic TX/RX buffer. After enabling this option, mbedTLS will - allocate TX buffer when need to send data and then free it if all data - is sent, allocate RX buffer when need to receive data and then free it - when all data is used or read by upper layer. - - By default, when SSL is initialized, mbedTLS also allocate TX and - RX buffer with the default value of "MBEDTLS_SSL_OUT_CONTENT_LEN" or - "MBEDTLS_SSL_IN_CONTENT_LEN", so to save more heap, users can set - the options to be an appropriate value. - - config MBEDTLS_DYNAMIC_FREE_PEER_CERT - bool "Free SSL peer certificate after its usage" - default n - depends on MBEDTLS_DYNAMIC_BUFFER - help - Free peer certificate after its usage in handshake process. - - config MBEDTLS_DYNAMIC_FREE_CONFIG_DATA - bool "Free certificate, key and DHM data after its usage" - default n - depends on MBEDTLS_DYNAMIC_BUFFER - help - Free certificate, private key and DHM data after its usage in handshake process. - - The option will decrease heap cost when handshake, but also lead to problem: - - Becasue all certificate, private key and DHM data are freed so users should register - certificate and private key to ssl config object again. - - config MBEDTLS_DEBUG - bool "Enable mbedTLS debugging" - default n - help - Enable mbedTLS debugging functions at compile time. - - If this option is enabled, you can include - "mbedtls/esp_debug.h" and call mbedtls_esp_enable_debug_log() - at runtime in order to enable mbedTLS debug output via the ESP - log mechanism. - - choice MBEDTLS_DEBUG_LEVEL - bool "Set mbedTLS debugging level" - depends on MBEDTLS_DEBUG - default MBEDTLS_DEBUG_LEVEL_VERBOSE - help - Set mbedTLS debugging level - - config MBEDTLS_DEBUG_LEVEL_WARN - bool "Warning" - config MBEDTLS_DEBUG_LEVEL_INFO - bool "Info" - config MBEDTLS_DEBUG_LEVEL_DEBUG - bool "Debug" - config MBEDTLS_DEBUG_LEVEL_VERBOSE - bool "Verbose" - endchoice - - config MBEDTLS_DEBUG_LEVEL - int - default 1 if MBEDTLS_DEBUG_LEVEL_WARN - default 2 if MBEDTLS_DEBUG_LEVEL_INFO - default 3 if MBEDTLS_DEBUG_LEVEL_DEBUG - default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE - - config MBEDTLS_HAVE_TIME - bool "Enable mbedtls time" - depends on !ESP32_TIME_SYSCALL_USE_NONE - default y - help - System has time.h and time(). - The time does not need to be correct, only time differences are used. - - config MBEDTLS_HAVE_TIME_DATE - bool "Enable mbedtls certificate expiry check" - depends on MBEDTLS_HAVE_TIME - default n - help - System has time.h and time(), gmtime() and the clock is correct. - The time needs to be correct (not necesarily very accurate, but at least - the date should be correct). This is used to verify the validity period of - X.509 certificates. - - It is suggested that you should get the real time by "SNTP". - - choice MBEDTLS_TLS_MODE - bool "TLS Protocol Role" - default MBEDTLS_TLS_SERVER_AND_CLIENT - help - mbedTLS can be compiled with protocol support for the TLS - server, TLS client, or both server and client. - - Reducing the number of TLS roles supported saves code size. - - config MBEDTLS_TLS_SERVER_AND_CLIENT - bool "Server & Client" - select MBEDTLS_TLS_SERVER - select MBEDTLS_TLS_CLIENT - config MBEDTLS_TLS_SERVER_ONLY - bool "Server" - select MBEDTLS_TLS_SERVER - config MBEDTLS_TLS_CLIENT_ONLY - bool "Client" - select MBEDTLS_TLS_CLIENT - config MBEDTLS_TLS_DISABLED - bool "None" - - endchoice - - config MBEDTLS_TLS_SERVER - bool - select MBEDTLS_TLS_ENABLED - config MBEDTLS_TLS_CLIENT - bool - select MBEDTLS_TLS_ENABLED - config MBEDTLS_TLS_ENABLED - bool - - menu "TLS Key Exchange Methods" - depends on MBEDTLS_TLS_ENABLED - - config MBEDTLS_PSK_MODES - bool "Enable pre-shared-key ciphersuites" - default n - help - Enable to show configuration for different types of pre-shared-key TLS authentatication methods. - - Leaving this options disabled will save code size if they are not used. - - config MBEDTLS_KEY_EXCHANGE_PSK - bool "Enable PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default n - help - Enable to support symmetric key PSK (pre-shared-key) TLS key exchange modes. - - config MBEDTLS_KEY_EXCHANGE_DHE_PSK - bool "Enable DHE-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default y - help - Enable to support Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_ECDHE_PSK - bool "Enable ECDHE-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES && MBEDTLS_ECDH_C - default y - help - Enable to support Elliptic-Curve-Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_RSA_PSK - bool "Enable RSA-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default y - help - Enable to support RSA PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_RSA - bool "Enable RSA-only based ciphersuite modes" - default y - help - Enable to support ciphersuites with prefix TLS-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_DHE_RSA - bool "Enable DHE-RSA based ciphersuite modes" - default y - help - Enable to support ciphersuites with prefix TLS-DHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE - bool "Support Elliptic Curve based ciphersuites" - depends on MBEDTLS_ECP_C - default y - help - Enable to show Elliptic Curve based ciphersuite mode options. - - Disabling all Elliptic Curve ciphersuites saves code size and - can give slightly faster TLS handshakes, provided the server supports - RSA-only ciphersuite modes. - - config MBEDTLS_KEY_EXCHANGE_ECDHE_RSA - bool "Enable ECDHE-RSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA - bool "Enable ECDHE-ECDSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA - bool "Enable ECDH-ECDSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDH_RSA - bool "Enable ECDH-RSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - endmenu # TLS key exchange modes - - config MBEDTLS_SSL_RENEGOTIATION - bool "Support TLS renegotiation" - depends on MBEDTLS_TLS_ENABLED - default y - help - The two main uses of renegotiation are (1) refresh keys on long-lived - connections and (2) client authentication after the initial handshake. - If you don't need renegotiation, disabling it will save code size and - reduce the possibility of abuse/vulnerability. - - config MBEDTLS_SSL_PROTO_SSL3 - bool "Legacy SSL 3.0 support" - depends on MBEDTLS_TLS_ENABLED - default n - help - Support the legacy SSL 3.0 protocol. Most servers will speak a newer - TLS protocol these days. - - config MBEDTLS_SSL_PROTO_TLS1 - bool "Support TLS 1.0 protocol" - depends on MBEDTLS_TLS_ENABLED - default y - - config MBEDTLS_SSL_PROTO_TLS1_1 - bool "Support TLS 1.1 protocol" - depends on MBEDTLS_TLS_ENABLED - default y - - config MBEDTLS_SSL_PROTO_TLS1_2 - bool "Support TLS 1.2 protocol" - depends on MBEDTLS_TLS_ENABLED - default y - - config MBEDTLS_SSL_PROTO_DTLS - bool "Support DTLS protocol (all versions)" - default n - depends on MBEDTLS_SSL_PROTO_TLS1_1 || MBEDTLS_SSL_PROTO_TLS1_2 - help - Requires TLS 1.1 to be enabled for DTLS 1.0 - Requires TLS 1.2 to be enabled for DTLS 1.2 - - config MBEDTLS_SSL_ALPN - bool "Support ALPN (Application Layer Protocol Negotiation)" - depends on MBEDTLS_TLS_ENABLED - default y - help - Disabling this option will save some code size if it is not needed. - - config MBEDTLS_CLIENT_SSL_SESSION_TICKETS - bool "TLS: Client Support for RFC 5077 SSL session tickets" - default y - depends on MBEDTLS_TLS_ENABLED - help - Client support for RFC 5077 session tickets. See mbedTLS documentation for more details. - Disabling this option will save some code size. - - config MBEDTLS_SERVER_SSL_SESSION_TICKETS - bool "TLS: Server Support for RFC 5077 SSL session tickets" - default y - depends on MBEDTLS_TLS_ENABLED - depends on MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C - help - Server support for RFC 5077 session tickets. See mbedTLS documentation for more details. - Disabling this option will save some code size. - - menu "Symmetric Ciphers" - - config MBEDTLS_AES_C - bool "AES block cipher" - default y - - config MBEDTLS_CAMELLIA_C - bool "Camellia block cipher" - default n - - config MBEDTLS_DES_C - bool "DES block cipher (legacy, insecure)" - default n - help - Enables the DES block cipher to support 3DES-based TLS ciphersuites. - - 3DES is vulnerable to the Sweet32 attack and should only be enabled - if absolutely necessary. - - choice MBEDTLS_RC4_MODE - prompt "RC4 Stream Cipher (legacy, insecure)" - default MBEDTLS_RC4_DISABLED - help - ARCFOUR (RC4) stream cipher can be disabled entirely, enabled but not - added to default ciphersuites, or enabled completely. - - Please consider the security implications before enabling RC4. - - config MBEDTLS_RC4_DISABLED - bool "Disabled" - config MBEDTLS_RC4_ENABLED_NO_DEFAULT - bool "Enabled, not in default ciphersuites" - config MBEDTLS_RC4_ENABLED - bool "Enabled" - endchoice - - config MBEDTLS_BLOWFISH_C - bool "Blowfish block cipher (read help)" - default n - help - Enables the Blowfish block cipher (not used for TLS sessions.) - - The Blowfish cipher is not used for mbedTLS TLS sessions but can be - used for other purposes. Read up on the limitations of Blowfish (including - Sweet32) before enabling. - - config MBEDTLS_XTEA_C - bool "XTEA block cipher" - default n - help - Enables the XTEA block cipher. - - - config MBEDTLS_CCM_C - bool "CCM (Counter with CBC-MAC) block cipher modes" - default y - depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C - help - Enable Counter with CBC-MAC (CCM) modes for AES and/or Camellia ciphers. - - Disabling this option saves some code size. - - config MBEDTLS_GCM_C - bool "GCM (Galois/Counter) block cipher modes" - default y - depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C - help - Enable Galois/Counter Mode for AES and/or Camellia ciphers. - - This option is generally faster than CCM. - - endmenu # Symmetric Ciphers - - config MBEDTLS_RIPEMD160_C - bool "Enable RIPEMD-160 hash algorithm" - default n - help - Enable the RIPEMD-160 hash algorithm. - - menu "Certificates" - - config MBEDTLS_PEM_PARSE_C - bool "Read & Parse PEM formatted certificates" - default y - help - Enable decoding/parsing of PEM formatted certificates. - - If your certificates are all in the simpler DER format, disabling - this option will save some code size. - - config MBEDTLS_PEM_WRITE_C - bool "Write PEM formatted certificates" - default y - help - Enable writing of PEM formatted certificates. - - If writing certificate data only in DER format, disabling this - option will save some code size. - - config MBEDTLS_X509_CRL_PARSE_C - bool "X.509 CRL parsing" - default y - help - Support for parsing X.509 Certifificate Revocation Lists. - - config MBEDTLS_X509_CSR_PARSE_C - bool "X.509 CSR parsing" - default y - help - Support for parsing X.509 Certifificate Signing Requests - - endmenu # Certificates - - menuconfig MBEDTLS_ECP_C - bool "Elliptic Curve Ciphers" - default y - - config MBEDTLS_ECDH_C - bool "Elliptic Curve Diffie-Hellman (ECDH)" - depends on MBEDTLS_ECP_C - default y - help - Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. - - config MBEDTLS_ECDSA_C - bool "Elliptic Curve DSA" - depends on MBEDTLS_ECDH_C - default y - help - Enable ECDSA. Needed to use ECDSA-xxx TLS ciphersuites. - - config MBEDTLS_ECP_DP_SECP192R1_ENABLED - bool "Enable SECP192R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP192R1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP224R1_ENABLED - bool "Enable SECP224R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP224R1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP256R1_ENABLED - bool "Enable SECP256R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP256R1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP384R1_ENABLED - bool "Enable SECP384R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP384R1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP521R1_ENABLED - bool "Enable SECP521R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP521R1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP192K1_ENABLED - bool "Enable SECP192K1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP192K1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP224K1_ENABLED - bool "Enable SECP224K1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP224K1 Elliptic Curve. - - config MBEDTLS_ECP_DP_SECP256K1_ENABLED - bool "Enable SECP256K1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP256K1 Elliptic Curve. - - config MBEDTLS_ECP_DP_BP256R1_ENABLED - bool "Enable BP256R1 curve" - depends on MBEDTLS_ECP_C - default y - help - support for DP Elliptic Curve. - - config MBEDTLS_ECP_DP_BP384R1_ENABLED - bool "Enable BP384R1 curve" - depends on MBEDTLS_ECP_C - default y - help - support for DP Elliptic Curve. - - config MBEDTLS_ECP_DP_BP512R1_ENABLED - bool "Enable BP512R1 curve" - depends on MBEDTLS_ECP_C - default y - help - support for DP Elliptic Curve. - - config MBEDTLS_ECP_DP_CURVE25519_ENABLED - bool "Enable CURVE25519 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for CURVE25519 Elliptic Curve. - - config MBEDTLS_ECP_NIST_OPTIM - bool "NIST 'modulo p' optimisations" - depends on MBEDTLS_ECP_C - default y - help - NIST 'modulo p' optimisations increase Elliptic Curve operation performance. - - Disabling this option saves some code size. - - # end of Elliptic Curve options - - menu "Util" - - config util_assert - bool "Enable assert for util components" - default n - help - Enable this option, util components will use assert to check if input - parameters are correct. - - Disable this option will speed up the process of some calculation a lot. - - config ESP_SHA - bool "Enable Espressif SHA" - default n - help - Enable Espressif SHA1, SHA256, SHA384 & SHA512 for other components to - save code size for ESP8285(ESP8266 + 1MB flash) users. - - Although this option is disable, bootloader will use it if booloader - is configured to use SHA256 to check hash. - - Disabling the "assert" function at menuconfig can speed up the calculation. - - config ESP_AES - bool "Enable Espressif AES" - default y - help - Enable Espressif AES ECB, CBC, CFB128, CFB8 & CRT for other components to - speed up process speed and save code size. - - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. - - The calculation uses "ibus_data" to speed up load data from instruction bus. - - Disabling the "assert" function at menuconfig can speed up the calculation. - - config ESP_MD5 - bool "Enable Espressif MD5" - default y - help - Enable Espressif MD5 for other components to - speed up process speed and save code size. - - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. - - The calculation uses "ibus_data" to speed up load data from instruction bus. - - Disabling the "assert" function at menuconfig can speed up the calculation. - - config ESP_ARC4 - bool "Enable Espressif ARC4" - default y - help - Enable Espressif ARC4 for other components to - speed up process speed and save code size. - - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. - - The calculation uses "ibus_data" to speed up load data from instruction bus. - - Disabling the "assert" function at menuconfig can speed up the calculation. - - endmenu # Util - - -endmenu # mbedTLS + source "$IDF_PATH/components/mbedtls/mbedtls_v2/Kconfig" +endchoice() diff --git a/components/mbedtls/mbedtls_v2/CMakeLists.txt b/components/mbedtls/mbedtls_v2/CMakeLists.txt new file mode 100644 index 000000000..8f15eae98 --- /dev/null +++ b/components/mbedtls/mbedtls_v2/CMakeLists.txt @@ -0,0 +1,81 @@ +if(MBEDTLS_V3) + return() +endif() + +idf_build_get_property(target IDF_TARGET) + +set(current_dir ${COMPONENT_DIR}/mbedtls_v2) + +idf_component_register(INCLUDE_DIRS "mbedtls_v2/port/include" "mbedtls_v2/port/include/${target}" "mbedtls_v2/mbedtls/include" + REQUIRES lwip) + +# Only build mbedtls libraries +set(ENABLE_TESTING CACHE BOOL OFF) +set(ENABLE_PROGRAMS CACHE BOOL OFF) + +# Needed to for include_next includes to work from within mbedtls +include_directories("${current_dir}/port/include" "${current_dir}/port/include/${target}") + +# Import mbedtls library targets +add_subdirectory(mbedtls_v2/mbedtls) + +# Use port specific implementation of net_socket.c instead of one from mbedtls +get_target_property(src_tls mbedtls SOURCES) +list(REMOVE_ITEM src_tls net_sockets.c) +set_property(TARGET mbedtls PROPERTY SOURCES ${src_tls}) + +set(mbedtls_targets mbedtls mbedcrypto mbedx509) + +# Add port files to mbedtls targets +set(mbedtls_target_sources "${current_dir}/port/mbedtls_debug.c" + "${current_dir}/port/net_sockets.c") + +if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) +set(mbedtls_target_sources ${mbedtls_target_sources} + "${current_dir}/port/dynamic/esp_mbedtls_dynamic_impl.c" + "${current_dir}/port/dynamic/esp_ssl_cli.c" + "${current_dir}/port/dynamic/esp_ssl_srv.c" + "${current_dir}/port/dynamic/esp_ssl_tls.c") +endif() + +target_sources(mbedtls PRIVATE ${mbedtls_target_sources}) + +target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_hardware.c" + "${current_dir}/port/esp_mem.c" + "${current_dir}/port/esp_timing.c" + "${current_dir}/port/esp_aes.c" + "${current_dir}/port/esp_sha1.c" + "${current_dir}/port/esp_sha256.c" + "${current_dir}/port/esp_sha512.c" + "${current_dir}/port/${target}/aes.c" + "${current_dir}/port/${target}/arc4.c" + "${current_dir}/port/${target}/base64.c" + "${current_dir}/port/${target}/md5.c" + "${current_dir}/port/${target}/sha1.c" + "${current_dir}/port/${target}/sha256.c" + "${current_dir}/port/${target}/sha512.c") + + +foreach(target ${mbedtls_targets}) + target_compile_definitions(${target} PUBLIC -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DCONFIG_SSL_USING_MBEDTLS) +endforeach() + +if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) + set(WRAP_FUNCTIONS + mbedtls_ssl_handshake_client_step + mbedtls_ssl_handshake_server_step + mbedtls_ssl_read + mbedtls_ssl_write + mbedtls_ssl_session_reset + mbedtls_ssl_free + mbedtls_ssl_setup + mbedtls_ssl_send_alert_message + mbedtls_ssl_close_notify) + + foreach(wrap ${WRAP_FUNCTIONS}) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") + endforeach() +endif() + +# Link mbedtls libraries to component library +target_link_libraries(${COMPONENT_LIB} INTERFACE ${mbedtls_targets}) diff --git a/components/mbedtls/mbedtls_v2/Kconfig b/components/mbedtls/mbedtls_v2/Kconfig new file mode 100644 index 000000000..2a44d3ded --- /dev/null +++ b/components/mbedtls/mbedtls_v2/Kconfig @@ -0,0 +1,660 @@ +menu "mbedTLS" + depends on MBEDTLS_V2 + choice MBEDTLS_MEM_ALLOC_MODE + prompt "Memory allocation strategy" + default MBEDTLS_INTERNAL_MEM_ALLOC + help + Allocation strategy for mbedTLS, essentially provides ability to + allocate all required dynamic allocations from, + + - Internal DRAM memory only + - External SPIRAM memory only + - Either internal or external memory based on default malloc() + behavior in ESP-IDF + - Custom allocation mode, by overwriting calloc()/free() using + mbedtls_platform_set_calloc_free() function + + Recommended mode here is always internal, since that is most preferred + from security perspective. But if application requirement does not + allow sufficient free internal memory then alternate mode can be + selected. + + config MBEDTLS_INTERNAL_MEM_ALLOC + bool "Internal memory" + + config MBEDTLS_EXTERNAL_MEM_ALLOC + bool "External SPIRAM" + depends on ESP32_SPIRAM_SUPPORT + + config MBEDTLS_DEFAULT_MEM_ALLOC + bool "Default alloc mode" + + config MBEDTLS_CUSTOM_MEM_ALLOC + bool "Custom alloc mode" + + endchoice #MBEDTLS_MEM_ALLOC_MODE + + config MBEDTLS_SSL_MAX_CONTENT_LEN + int "TLS maximum message content length" + default 16384 + range 512 16384 + depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + Maximum TLS message length (in bytes) supported by mbedTLS. + + 16384 is the default and this value is required to comply + fully with TLS standards. + + However you can set a lower value in order to save RAM. This + is safe if the other end of the connection supports Maximum + Fragment Length Negotiation Extension (max_fragment_length, + see RFC6066) or you know for certain that it will never send a + message longer than a certain number of bytes. + + If the value is set too low, symptoms are a failed TLS + handshake or a return value of MBEDTLS_ERR_SSL_INVALID_RECORD + (-0x7200). + + config MBEDTLS_ASYMMETRIC_CONTENT_LEN + bool "Asymmetric in/out fragment length" + default y + help + If enabled, this option allows customizing TLS in/out fragment length + in asymmetric way. Please note that enabling this with default values + saves 12KB of dynamic memory per TLS connection. + + config MBEDTLS_SSL_IN_CONTENT_LEN + int "TLS maximum incoming fragment length" + default 16384 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum incoming fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + + config MBEDTLS_SSL_OUT_CONTENT_LEN + int "TLS maximum outgoing fragment length" + default 4096 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum outgoing fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + + config MBEDTLS_DYNAMIC_BUFFER + bool "Using dynamic TX/RX buffer" + default n + select MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + Using dynamic TX/RX buffer. After enabling this option, mbedTLS will + allocate TX buffer when need to send data and then free it if all data + is sent, allocate RX buffer when need to receive data and then free it + when all data is used or read by upper layer. + + By default, when SSL is initialized, mbedTLS also allocate TX and + RX buffer with the default value of "MBEDTLS_SSL_OUT_CONTENT_LEN" or + "MBEDTLS_SSL_IN_CONTENT_LEN", so to save more heap, users can set + the options to be an appropriate value. + + config MBEDTLS_DYNAMIC_FREE_PEER_CERT + bool "Free SSL peer certificate after its usage" + default n + depends on MBEDTLS_DYNAMIC_BUFFER + help + Free peer certificate after its usage in handshake process. + + config MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + bool "Free certificate, key and DHM data after its usage" + default n + depends on MBEDTLS_DYNAMIC_BUFFER + help + Free certificate, private key and DHM data after its usage in handshake process. + + The option will decrease heap cost when handshake, but also lead to problem: + + Becasue all certificate, private key and DHM data are freed so users should register + certificate and private key to ssl config object again. + + config MBEDTLS_DEBUG + bool "Enable mbedTLS debugging" + default n + help + Enable mbedTLS debugging functions at compile time. + + If this option is enabled, you can include + "mbedtls/esp_debug.h" and call mbedtls_esp_enable_debug_log() + at runtime in order to enable mbedTLS debug output via the ESP + log mechanism. + + choice MBEDTLS_DEBUG_LEVEL + bool "Set mbedTLS debugging level" + depends on MBEDTLS_DEBUG + default MBEDTLS_DEBUG_LEVEL_VERBOSE + help + Set mbedTLS debugging level + + config MBEDTLS_DEBUG_LEVEL_WARN + bool "Warning" + config MBEDTLS_DEBUG_LEVEL_INFO + bool "Info" + config MBEDTLS_DEBUG_LEVEL_DEBUG + bool "Debug" + config MBEDTLS_DEBUG_LEVEL_VERBOSE + bool "Verbose" + endchoice + + config MBEDTLS_DEBUG_LEVEL + int + default 1 if MBEDTLS_DEBUG_LEVEL_WARN + default 2 if MBEDTLS_DEBUG_LEVEL_INFO + default 3 if MBEDTLS_DEBUG_LEVEL_DEBUG + default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE + + config MBEDTLS_HAVE_TIME + bool "Enable mbedtls time" + depends on !ESP32_TIME_SYSCALL_USE_NONE + default y + help + System has time.h and time(). + The time does not need to be correct, only time differences are used. + + config MBEDTLS_HAVE_TIME_DATE + bool "Enable mbedtls certificate expiry check" + depends on MBEDTLS_HAVE_TIME + default n + help + System has time.h and time(), gmtime() and the clock is correct. + The time needs to be correct (not necesarily very accurate, but at least + the date should be correct). This is used to verify the validity period of + X.509 certificates. + + It is suggested that you should get the real time by "SNTP". + + choice MBEDTLS_TLS_MODE + bool "TLS Protocol Role" + default MBEDTLS_TLS_SERVER_AND_CLIENT + help + mbedTLS can be compiled with protocol support for the TLS + server, TLS client, or both server and client. + + Reducing the number of TLS roles supported saves code size. + + config MBEDTLS_TLS_SERVER_AND_CLIENT + bool "Server & Client" + select MBEDTLS_TLS_SERVER + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_SERVER_ONLY + bool "Server" + select MBEDTLS_TLS_SERVER + config MBEDTLS_TLS_CLIENT_ONLY + bool "Client" + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_DISABLED + bool "None" + + endchoice + + config MBEDTLS_TLS_SERVER + bool + select MBEDTLS_TLS_ENABLED + config MBEDTLS_TLS_CLIENT + bool + select MBEDTLS_TLS_ENABLED + config MBEDTLS_TLS_ENABLED + bool + + menu "TLS Key Exchange Methods" + depends on MBEDTLS_TLS_ENABLED + + config MBEDTLS_PSK_MODES + bool "Enable pre-shared-key ciphersuites" + default n + help + Enable to show configuration for different types of pre-shared-key TLS authentatication methods. + + Leaving this options disabled will save code size if they are not used. + + config MBEDTLS_KEY_EXCHANGE_PSK + bool "Enable PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default n + help + Enable to support symmetric key PSK (pre-shared-key) TLS key exchange modes. + + config MBEDTLS_KEY_EXCHANGE_DHE_PSK + bool "Enable DHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default y + help + Enable to support Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_ECDHE_PSK + bool "Enable ECDHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES && MBEDTLS_ECDH_C + default y + help + Enable to support Elliptic-Curve-Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_RSA_PSK + bool "Enable RSA-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default y + help + Enable to support RSA PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_RSA + bool "Enable RSA-only based ciphersuite modes" + default y + help + Enable to support ciphersuites with prefix TLS-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_DHE_RSA + bool "Enable DHE-RSA based ciphersuite modes" + default y + help + Enable to support ciphersuites with prefix TLS-DHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE + bool "Support Elliptic Curve based ciphersuites" + depends on MBEDTLS_ECP_C + default y + help + Enable to show Elliptic Curve based ciphersuite mode options. + + Disabling all Elliptic Curve ciphersuites saves code size and + can give slightly faster TLS handshakes, provided the server supports + RSA-only ciphersuite modes. + + config MBEDTLS_KEY_EXCHANGE_ECDHE_RSA + bool "Enable ECDHE-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA + bool "Enable ECDHE-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA + bool "Enable ECDH-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDH_RSA + bool "Enable ECDH-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + endmenu # TLS key exchange modes + + config MBEDTLS_SSL_RENEGOTIATION + bool "Support TLS renegotiation" + depends on MBEDTLS_TLS_ENABLED + default y + help + The two main uses of renegotiation are (1) refresh keys on long-lived + connections and (2) client authentication after the initial handshake. + If you don't need renegotiation, disabling it will save code size and + reduce the possibility of abuse/vulnerability. + + config MBEDTLS_SSL_PROTO_SSL3 + bool "Legacy SSL 3.0 support" + depends on MBEDTLS_TLS_ENABLED + default n + help + Support the legacy SSL 3.0 protocol. Most servers will speak a newer + TLS protocol these days. + + config MBEDTLS_SSL_PROTO_TLS1 + bool "Support TLS 1.0 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + + config MBEDTLS_SSL_PROTO_TLS1_1 + bool "Support TLS 1.1 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + + config MBEDTLS_SSL_PROTO_TLS1_2 + bool "Support TLS 1.2 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + + config MBEDTLS_SSL_PROTO_DTLS + bool "Support DTLS protocol (all versions)" + default n + depends on MBEDTLS_SSL_PROTO_TLS1_1 || MBEDTLS_SSL_PROTO_TLS1_2 + help + Requires TLS 1.1 to be enabled for DTLS 1.0 + Requires TLS 1.2 to be enabled for DTLS 1.2 + + config MBEDTLS_SSL_ALPN + bool "Support ALPN (Application Layer Protocol Negotiation)" + depends on MBEDTLS_TLS_ENABLED + default y + help + Disabling this option will save some code size if it is not needed. + + config MBEDTLS_CLIENT_SSL_SESSION_TICKETS + bool "TLS: Client Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED + help + Client support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + + config MBEDTLS_SERVER_SSL_SESSION_TICKETS + bool "TLS: Server Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED + depends on MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C + help + Server support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + + menu "Symmetric Ciphers" + + config MBEDTLS_AES_C + bool "AES block cipher" + default y + + config MBEDTLS_CAMELLIA_C + bool "Camellia block cipher" + default n + + config MBEDTLS_DES_C + bool "DES block cipher (legacy, insecure)" + default n + help + Enables the DES block cipher to support 3DES-based TLS ciphersuites. + + 3DES is vulnerable to the Sweet32 attack and should only be enabled + if absolutely necessary. + + choice MBEDTLS_RC4_MODE + prompt "RC4 Stream Cipher (legacy, insecure)" + default MBEDTLS_RC4_DISABLED + help + ARCFOUR (RC4) stream cipher can be disabled entirely, enabled but not + added to default ciphersuites, or enabled completely. + + Please consider the security implications before enabling RC4. + + config MBEDTLS_RC4_DISABLED + bool "Disabled" + config MBEDTLS_RC4_ENABLED_NO_DEFAULT + bool "Enabled, not in default ciphersuites" + config MBEDTLS_RC4_ENABLED + bool "Enabled" + endchoice + + config MBEDTLS_BLOWFISH_C + bool "Blowfish block cipher (read help)" + default n + help + Enables the Blowfish block cipher (not used for TLS sessions.) + + The Blowfish cipher is not used for mbedTLS TLS sessions but can be + used for other purposes. Read up on the limitations of Blowfish (including + Sweet32) before enabling. + + config MBEDTLS_XTEA_C + bool "XTEA block cipher" + default n + help + Enables the XTEA block cipher. + + + config MBEDTLS_CCM_C + bool "CCM (Counter with CBC-MAC) block cipher modes" + default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C + help + Enable Counter with CBC-MAC (CCM) modes for AES and/or Camellia ciphers. + + Disabling this option saves some code size. + + config MBEDTLS_GCM_C + bool "GCM (Galois/Counter) block cipher modes" + default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C + help + Enable Galois/Counter Mode for AES and/or Camellia ciphers. + + This option is generally faster than CCM. + + endmenu # Symmetric Ciphers + + config MBEDTLS_RIPEMD160_C + bool "Enable RIPEMD-160 hash algorithm" + default n + help + Enable the RIPEMD-160 hash algorithm. + + menu "Certificates" + + config MBEDTLS_PEM_PARSE_C + bool "Read & Parse PEM formatted certificates" + default y + help + Enable decoding/parsing of PEM formatted certificates. + + If your certificates are all in the simpler DER format, disabling + this option will save some code size. + + config MBEDTLS_PEM_WRITE_C + bool "Write PEM formatted certificates" + default y + help + Enable writing of PEM formatted certificates. + + If writing certificate data only in DER format, disabling this + option will save some code size. + + config MBEDTLS_X509_CRL_PARSE_C + bool "X.509 CRL parsing" + default y + help + Support for parsing X.509 Certifificate Revocation Lists. + + config MBEDTLS_X509_CSR_PARSE_C + bool "X.509 CSR parsing" + default y + help + Support for parsing X.509 Certifificate Signing Requests + + endmenu # Certificates + + menuconfig MBEDTLS_ECP_C + bool "Elliptic Curve Ciphers" + default y + + config MBEDTLS_ECDH_C + bool "Elliptic Curve Diffie-Hellman (ECDH)" + depends on MBEDTLS_ECP_C + default y + help + Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. + + config MBEDTLS_ECDSA_C + bool "Elliptic Curve DSA" + depends on MBEDTLS_ECDH_C + default y + help + Enable ECDSA. Needed to use ECDSA-xxx TLS ciphersuites. + + config MBEDTLS_ECP_DP_SECP192R1_ENABLED + bool "Enable SECP192R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP192R1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP224R1_ENABLED + bool "Enable SECP224R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP224R1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP256R1_ENABLED + bool "Enable SECP256R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP256R1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP384R1_ENABLED + bool "Enable SECP384R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP384R1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP521R1_ENABLED + bool "Enable SECP521R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP521R1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP192K1_ENABLED + bool "Enable SECP192K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP192K1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP224K1_ENABLED + bool "Enable SECP224K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP224K1 Elliptic Curve. + + config MBEDTLS_ECP_DP_SECP256K1_ENABLED + bool "Enable SECP256K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP256K1 Elliptic Curve. + + config MBEDTLS_ECP_DP_BP256R1_ENABLED + bool "Enable BP256R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + + config MBEDTLS_ECP_DP_BP384R1_ENABLED + bool "Enable BP384R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + + config MBEDTLS_ECP_DP_BP512R1_ENABLED + bool "Enable BP512R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + + config MBEDTLS_ECP_DP_CURVE25519_ENABLED + bool "Enable CURVE25519 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for CURVE25519 Elliptic Curve. + + config MBEDTLS_ECP_NIST_OPTIM + bool "NIST 'modulo p' optimisations" + depends on MBEDTLS_ECP_C + default y + help + NIST 'modulo p' optimisations increase Elliptic Curve operation performance. + + Disabling this option saves some code size. + + # end of Elliptic Curve options + + menu "Util" + + config util_assert + bool "Enable assert for util components" + default n + help + Enable this option, util components will use assert to check if input + parameters are correct. + + Disable this option will speed up the process of some calculation a lot. + + config ESP_SHA + bool "Enable Espressif SHA" + default n + help + Enable Espressif SHA1, SHA256, SHA384 & SHA512 for other components to + save code size for ESP8285(ESP8266 + 1MB flash) users. + + Although this option is disable, bootloader will use it if booloader + is configured to use SHA256 to check hash. + + Disabling the "assert" function at menuconfig can speed up the calculation. + + config ESP_AES + bool "Enable Espressif AES" + default y + help + Enable Espressif AES ECB, CBC, CFB128, CFB8 & CRT for other components to + speed up process speed and save code size. + + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. + + The calculation uses "ibus_data" to speed up load data from instruction bus. + + Disabling the "assert" function at menuconfig can speed up the calculation. + + config ESP_MD5 + bool "Enable Espressif MD5" + default y + help + Enable Espressif MD5 for other components to + speed up process speed and save code size. + + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. + + The calculation uses "ibus_data" to speed up load data from instruction bus. + + Disabling the "assert" function at menuconfig can speed up the calculation. + + config ESP_ARC4 + bool "Enable Espressif ARC4" + default y + help + Enable Espressif ARC4 for other components to + speed up process speed and save code size. + + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. + + The calculation uses "ibus_data" to speed up load data from instruction bus. + + Disabling the "assert" function at menuconfig can speed up the calculation. + + endmenu # Util + + +endmenu # mbedTLS diff --git a/components/mbedtls/Makefile.projbuild b/components/mbedtls/mbedtls_v2/Makefile.projbuild similarity index 100% rename from components/mbedtls/Makefile.projbuild rename to components/mbedtls/mbedtls_v2/Makefile.projbuild diff --git a/components/mbedtls/component.mk b/components/mbedtls/mbedtls_v2/component.mk similarity index 100% rename from components/mbedtls/component.mk rename to components/mbedtls/mbedtls_v2/component.mk diff --git a/components/mbedtls/mbedtls b/components/mbedtls/mbedtls_v2/mbedtls similarity index 100% rename from components/mbedtls/mbedtls rename to components/mbedtls/mbedtls_v2/mbedtls diff --git a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c b/components/mbedtls/mbedtls_v2/port/dynamic/esp_mbedtls_dynamic_impl.c similarity index 100% rename from components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.c rename to components/mbedtls/mbedtls_v2/port/dynamic/esp_mbedtls_dynamic_impl.c diff --git a/components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h b/components/mbedtls/mbedtls_v2/port/dynamic/esp_mbedtls_dynamic_impl.h similarity index 100% rename from components/mbedtls/port/dynamic/esp_mbedtls_dynamic_impl.h rename to components/mbedtls/mbedtls_v2/port/dynamic/esp_mbedtls_dynamic_impl.h diff --git a/components/mbedtls/port/dynamic/esp_ssl_cli.c b/components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_cli.c similarity index 100% rename from components/mbedtls/port/dynamic/esp_ssl_cli.c rename to components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_cli.c diff --git a/components/mbedtls/port/dynamic/esp_ssl_srv.c b/components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_srv.c similarity index 100% rename from components/mbedtls/port/dynamic/esp_ssl_srv.c rename to components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_srv.c diff --git a/components/mbedtls/port/dynamic/esp_ssl_tls.c b/components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_tls.c similarity index 100% rename from components/mbedtls/port/dynamic/esp_ssl_tls.c rename to components/mbedtls/mbedtls_v2/port/dynamic/esp_ssl_tls.c diff --git a/components/mbedtls/port/esp8266/aes.c b/components/mbedtls/mbedtls_v2/port/esp8266/aes.c similarity index 100% rename from components/mbedtls/port/esp8266/aes.c rename to components/mbedtls/mbedtls_v2/port/esp8266/aes.c diff --git a/components/mbedtls/port/esp8266/arc4.c b/components/mbedtls/mbedtls_v2/port/esp8266/arc4.c similarity index 100% rename from components/mbedtls/port/esp8266/arc4.c rename to components/mbedtls/mbedtls_v2/port/esp8266/arc4.c diff --git a/components/mbedtls/port/esp8266/base64.c b/components/mbedtls/mbedtls_v2/port/esp8266/base64.c similarity index 100% rename from components/mbedtls/port/esp8266/base64.c rename to components/mbedtls/mbedtls_v2/port/esp8266/base64.c diff --git a/components/mbedtls/port/esp8266/md5.c b/components/mbedtls/mbedtls_v2/port/esp8266/md5.c similarity index 100% rename from components/mbedtls/port/esp8266/md5.c rename to components/mbedtls/mbedtls_v2/port/esp8266/md5.c diff --git a/components/mbedtls/port/esp8266/sha1.c b/components/mbedtls/mbedtls_v2/port/esp8266/sha1.c similarity index 100% rename from components/mbedtls/port/esp8266/sha1.c rename to components/mbedtls/mbedtls_v2/port/esp8266/sha1.c diff --git a/components/mbedtls/port/esp8266/sha256.c b/components/mbedtls/mbedtls_v2/port/esp8266/sha256.c similarity index 100% rename from components/mbedtls/port/esp8266/sha256.c rename to components/mbedtls/mbedtls_v2/port/esp8266/sha256.c diff --git a/components/mbedtls/port/esp8266/sha512.c b/components/mbedtls/mbedtls_v2/port/esp8266/sha512.c similarity index 100% rename from components/mbedtls/port/esp8266/sha512.c rename to components/mbedtls/mbedtls_v2/port/esp8266/sha512.c diff --git a/components/mbedtls/port/esp_aes.c b/components/mbedtls/mbedtls_v2/port/esp_aes.c similarity index 100% rename from components/mbedtls/port/esp_aes.c rename to components/mbedtls/mbedtls_v2/port/esp_aes.c diff --git a/components/mbedtls/port/esp_hardware.c b/components/mbedtls/mbedtls_v2/port/esp_hardware.c similarity index 100% rename from components/mbedtls/port/esp_hardware.c rename to components/mbedtls/mbedtls_v2/port/esp_hardware.c diff --git a/components/mbedtls/port/esp_mem.c b/components/mbedtls/mbedtls_v2/port/esp_mem.c similarity index 100% rename from components/mbedtls/port/esp_mem.c rename to components/mbedtls/mbedtls_v2/port/esp_mem.c diff --git a/components/mbedtls/port/esp_sha1.c b/components/mbedtls/mbedtls_v2/port/esp_sha1.c similarity index 100% rename from components/mbedtls/port/esp_sha1.c rename to components/mbedtls/mbedtls_v2/port/esp_sha1.c diff --git a/components/mbedtls/port/esp_sha256.c b/components/mbedtls/mbedtls_v2/port/esp_sha256.c similarity index 100% rename from components/mbedtls/port/esp_sha256.c rename to components/mbedtls/mbedtls_v2/port/esp_sha256.c diff --git a/components/mbedtls/port/esp_sha512.c b/components/mbedtls/mbedtls_v2/port/esp_sha512.c similarity index 100% rename from components/mbedtls/port/esp_sha512.c rename to components/mbedtls/mbedtls_v2/port/esp_sha512.c diff --git a/components/mbedtls/port/esp_timing.c b/components/mbedtls/mbedtls_v2/port/esp_timing.c similarity index 100% rename from components/mbedtls/port/esp_timing.c rename to components/mbedtls/mbedtls_v2/port/esp_timing.c diff --git a/components/mbedtls/port/include/aes_alt.h b/components/mbedtls/mbedtls_v2/port/include/aes_alt.h similarity index 100% rename from components/mbedtls/port/include/aes_alt.h rename to components/mbedtls/mbedtls_v2/port/include/aes_alt.h diff --git a/components/mbedtls/port/include/arc4_alt.h b/components/mbedtls/mbedtls_v2/port/include/arc4_alt.h similarity index 100% rename from components/mbedtls/port/include/arc4_alt.h rename to components/mbedtls/mbedtls_v2/port/include/arc4_alt.h diff --git a/components/mbedtls/port/include/esp8266/esp_aes.h b/components/mbedtls/mbedtls_v2/port/include/esp8266/esp_aes.h similarity index 100% rename from components/mbedtls/port/include/esp8266/esp_aes.h rename to components/mbedtls/mbedtls_v2/port/include/esp8266/esp_aes.h diff --git a/components/mbedtls/port/include/esp8266/esp_arc4.h b/components/mbedtls/mbedtls_v2/port/include/esp8266/esp_arc4.h similarity index 100% rename from components/mbedtls/port/include/esp8266/esp_arc4.h rename to components/mbedtls/mbedtls_v2/port/include/esp8266/esp_arc4.h diff --git a/components/mbedtls/port/include/esp8266/esp_base64.h b/components/mbedtls/mbedtls_v2/port/include/esp8266/esp_base64.h similarity index 100% rename from components/mbedtls/port/include/esp8266/esp_base64.h rename to components/mbedtls/mbedtls_v2/port/include/esp8266/esp_base64.h diff --git a/components/mbedtls/port/include/esp8266/esp_md5.h b/components/mbedtls/mbedtls_v2/port/include/esp8266/esp_md5.h similarity index 100% rename from components/mbedtls/port/include/esp8266/esp_md5.h rename to components/mbedtls/mbedtls_v2/port/include/esp8266/esp_md5.h diff --git a/components/mbedtls/port/include/esp_mem.h b/components/mbedtls/mbedtls_v2/port/include/esp_mem.h similarity index 100% rename from components/mbedtls/port/include/esp_mem.h rename to components/mbedtls/mbedtls_v2/port/include/esp_mem.h diff --git a/components/mbedtls/port/include/mbedtls/esp_config.h b/components/mbedtls/mbedtls_v2/port/include/mbedtls/esp_config.h similarity index 99% rename from components/mbedtls/port/include/mbedtls/esp_config.h rename to components/mbedtls/mbedtls_v2/port/include/mbedtls/esp_config.h index 3502aae6b..b36e427f9 100644 --- a/components/mbedtls/port/include/mbedtls/esp_config.h +++ b/components/mbedtls/mbedtls_v2/port/include/mbedtls/esp_config.h @@ -2255,6 +2255,8 @@ /* \} name SECTION: Module configuration options */ +#define MBEDTLS_PRIVATE(member) member + #if defined(TARGET_LIKE_MBED) #include "mbedtls/target_config.h" #endif diff --git a/components/mbedtls/port/include/mbedtls/esp_debug.h b/components/mbedtls/mbedtls_v2/port/include/mbedtls/esp_debug.h similarity index 100% rename from components/mbedtls/port/include/mbedtls/esp_debug.h rename to components/mbedtls/mbedtls_v2/port/include/mbedtls/esp_debug.h diff --git a/components/mbedtls/port/include/md5_alt.h b/components/mbedtls/mbedtls_v2/port/include/md5_alt.h similarity index 100% rename from components/mbedtls/port/include/md5_alt.h rename to components/mbedtls/mbedtls_v2/port/include/md5_alt.h diff --git a/components/mbedtls/port/include/sha1_alt.h b/components/mbedtls/mbedtls_v2/port/include/sha1_alt.h similarity index 100% rename from components/mbedtls/port/include/sha1_alt.h rename to components/mbedtls/mbedtls_v2/port/include/sha1_alt.h diff --git a/components/mbedtls/port/include/sha256_alt.h b/components/mbedtls/mbedtls_v2/port/include/sha256_alt.h similarity index 100% rename from components/mbedtls/port/include/sha256_alt.h rename to components/mbedtls/mbedtls_v2/port/include/sha256_alt.h diff --git a/components/mbedtls/port/include/sha512_alt.h b/components/mbedtls/mbedtls_v2/port/include/sha512_alt.h similarity index 100% rename from components/mbedtls/port/include/sha512_alt.h rename to components/mbedtls/mbedtls_v2/port/include/sha512_alt.h diff --git a/components/mbedtls/port/mbedtls_debug.c b/components/mbedtls/mbedtls_v2/port/mbedtls_debug.c similarity index 100% rename from components/mbedtls/port/mbedtls_debug.c rename to components/mbedtls/mbedtls_v2/port/mbedtls_debug.c diff --git a/components/mbedtls/port/net_sockets.c b/components/mbedtls/mbedtls_v2/port/net_sockets.c similarity index 100% rename from components/mbedtls/port/net_sockets.c rename to components/mbedtls/mbedtls_v2/port/net_sockets.c diff --git a/components/mbedtls/test/CMakeLists.txt b/components/mbedtls/mbedtls_v2/test/CMakeLists.txt similarity index 100% rename from components/mbedtls/test/CMakeLists.txt rename to components/mbedtls/mbedtls_v2/test/CMakeLists.txt diff --git a/components/mbedtls/test/component.mk b/components/mbedtls/mbedtls_v2/test/component.mk similarity index 100% rename from components/mbedtls/test/component.mk rename to components/mbedtls/mbedtls_v2/test/component.mk diff --git a/components/mbedtls/test/test_aes.c b/components/mbedtls/mbedtls_v2/test/test_aes.c similarity index 100% rename from components/mbedtls/test/test_aes.c rename to components/mbedtls/mbedtls_v2/test/test_aes.c diff --git a/components/mbedtls/test/test_aes_perf.c b/components/mbedtls/mbedtls_v2/test/test_aes_perf.c similarity index 100% rename from components/mbedtls/test/test_aes_perf.c rename to components/mbedtls/mbedtls_v2/test/test_aes_perf.c diff --git a/components/mbedtls/test/test_apb_dport_access.c b/components/mbedtls/mbedtls_v2/test/test_apb_dport_access.c similarity index 100% rename from components/mbedtls/test/test_apb_dport_access.c rename to components/mbedtls/mbedtls_v2/test/test_apb_dport_access.c diff --git a/components/mbedtls/test/test_apb_dport_access.h b/components/mbedtls/mbedtls_v2/test/test_apb_dport_access.h similarity index 100% rename from components/mbedtls/test/test_apb_dport_access.h rename to components/mbedtls/mbedtls_v2/test/test_apb_dport_access.h diff --git a/components/mbedtls/test/test_arc4.c b/components/mbedtls/mbedtls_v2/test/test_arc4.c similarity index 100% rename from components/mbedtls/test/test_arc4.c rename to components/mbedtls/mbedtls_v2/test/test_arc4.c diff --git a/components/mbedtls/test/test_base64.c b/components/mbedtls/mbedtls_v2/test/test_base64.c similarity index 100% rename from components/mbedtls/test/test_base64.c rename to components/mbedtls/mbedtls_v2/test/test_base64.c diff --git a/components/mbedtls/test/test_crc.c b/components/mbedtls/mbedtls_v2/test/test_crc.c similarity index 100% rename from components/mbedtls/test/test_crc.c rename to components/mbedtls/mbedtls_v2/test/test_crc.c diff --git a/components/mbedtls/test/test_ecp.c b/components/mbedtls/mbedtls_v2/test/test_ecp.c similarity index 100% rename from components/mbedtls/test/test_ecp.c rename to components/mbedtls/mbedtls_v2/test/test_ecp.c diff --git a/components/mbedtls/test/test_mbedtls.c b/components/mbedtls/mbedtls_v2/test/test_mbedtls.c similarity index 100% rename from components/mbedtls/test/test_mbedtls.c rename to components/mbedtls/mbedtls_v2/test/test_mbedtls.c diff --git a/components/mbedtls/test/test_mbedtls_mpi.c b/components/mbedtls/mbedtls_v2/test/test_mbedtls_mpi.c similarity index 100% rename from components/mbedtls/test/test_mbedtls_mpi.c rename to components/mbedtls/mbedtls_v2/test/test_mbedtls_mpi.c diff --git a/components/mbedtls/test/test_mbedtls_sha.c b/components/mbedtls/mbedtls_v2/test/test_mbedtls_sha.c similarity index 100% rename from components/mbedtls/test/test_mbedtls_sha.c rename to components/mbedtls/mbedtls_v2/test/test_mbedtls_sha.c diff --git a/components/mbedtls/test/test_md5.c b/components/mbedtls/mbedtls_v2/test/test_md5.c similarity index 100% rename from components/mbedtls/test/test_md5.c rename to components/mbedtls/mbedtls_v2/test/test_md5.c diff --git a/components/mbedtls/test/test_rsa.c b/components/mbedtls/mbedtls_v2/test/test_rsa.c similarity index 100% rename from components/mbedtls/test/test_rsa.c rename to components/mbedtls/mbedtls_v2/test/test_rsa.c diff --git a/components/mbedtls/test/test_sha.c b/components/mbedtls/mbedtls_v2/test/test_sha.c similarity index 100% rename from components/mbedtls/test/test_sha.c rename to components/mbedtls/mbedtls_v2/test/test_sha.c From 0f71f1c583a0d3d85ad21e6145230643bd5db359 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Tue, 15 Apr 2025 17:04:53 +0800 Subject: [PATCH 56/59] feat: Update mbedtls to 1d34215a which is behind mbedtls-3.6.3 on branch mbedtls-3.6.3-idf --- .gitmodules | 4 + components/esp-tls/esp_tls_mbedtls.c | 6 + .../crypto_hash_sha256_mbedtls.c | 20 +- .../crypto_hash_sha512_mbedtls.c | 20 +- components/mbedtls/CMakeLists.txt | 8 +- components/mbedtls/Kconfig | 15 +- components/mbedtls/Makefile.projbuild | 8 + components/mbedtls/component.mk | 10 + components/mbedtls/mbedtls_v2/CMakeLists.txt | 2 +- components/mbedtls/mbedtls_v2/Kconfig | 1142 +++--- components/mbedtls/mbedtls_v2/component.mk | 12 +- components/mbedtls/mbedtls_v3/CMakeLists.txt | 328 ++ components/mbedtls/mbedtls_v3/Kconfig | 1150 ++++++ components/mbedtls/mbedtls_v3/component.mk | 38 + .../mbedtls_v3/esp_crt_bundle/cacrt_all.pem | 3372 +++++++++++++++++ .../mbedtls_v3/esp_crt_bundle/cacrt_local.pem | 33 + .../esp_crt_bundle/cmn_crt_authorities.csv | 42 + .../esp_crt_bundle/esp_crt_bundle.c | 244 ++ .../esp_crt_bundle/gen_crt_bundle.py | 216 ++ .../esp_crt_bundle/include/esp_crt_bundle.h | 76 + .../test_gen_crt_bundle/baltimore.der | Bin 0 -> 891 bytes .../test_gen_crt_bundle/baltimore_crt_bundle | Bin 0 -> 392 bytes .../test_gen_crt_bundle/entrust.pem | 25 + .../test_gen_crt_bundle/entrust_crt_bundle | Bin 0 -> 479 bytes .../test_gen_crt_bundle/invalid_crt.pem | 25 + .../test_gen_crt_bundle/non_ascii_crt.pem | 23 + .../test_gen_crt_bundle.py | 84 + components/mbedtls/mbedtls_v3/mbedtls | 1 + .../mbedtls_v3/port/aes/block/esp_aes.c | 580 +++ .../mbedtls/mbedtls_v3/port/aes/dma/esp_aes.c | 1103 ++++++ .../port/aes/dma/esp_aes_crypto_dma_impl.c | 42 + .../port/aes/dma/esp_aes_gdma_impl.c | 27 + .../port/aes/dma/include/esp_aes_dma_priv.h | 45 + .../mbedtls_v3/port/aes/esp_aes_common.c | 79 + .../mbedtls/mbedtls_v3/port/aes/esp_aes_gcm.c | 820 ++++ .../mbedtls/mbedtls_v3/port/aes/esp_aes_xts.c | 284 ++ .../esp_crypto_shared_gdma.c | 148 + .../port/dynamic/esp_mbedtls_dynamic_impl.c | 555 +++ .../port/dynamic/esp_mbedtls_dynamic_impl.h | 101 + .../mbedtls_v3/port/dynamic/esp_ssl_cli.c | 221 ++ .../mbedtls_v3/port/dynamic/esp_ssl_srv.c | 219 ++ .../mbedtls_v3/port/dynamic/esp_ssl_tls.c | 398 ++ .../mbedtls/mbedtls_v3/port/ecc/ecc_alt.c | 114 + .../mbedtls/mbedtls_v3/port/ecc/esp_ecc.c | 77 + .../mbedtls/mbedtls_v3/port/ecdsa/ecdsa_alt.c | 553 +++ .../mbedtls/mbedtls_v3/port/esp_bignum.c | 677 ++++ .../mbedtls_v3/port/esp_ds/esp_rsa_sign_alt.c | 273 ++ .../mbedtls/mbedtls_v3/port/esp_hardware.c | 26 + components/mbedtls/mbedtls_v3/port/esp_mem.c | 24 + .../mbedtls_v3/port/esp_platform_time.c | 25 + .../mbedtls/mbedtls_v3/port/esp_timing.c | 94 + .../mbedtls_v3/port/include/aes/esp_aes.h | 356 ++ .../mbedtls_v3/port/include/aes/esp_aes_gcm.h | 293 ++ .../port/include/aes/esp_aes_internal.h | 54 + .../mbedtls/mbedtls_v3/port/include/aes_alt.h | 69 + .../mbedtls_v3/port/include/bignum_impl.h | 96 + .../mbedtls_v3/port/include/ecc_impl.h | 60 + .../mbedtls_v3/port/include/ecdsa/ecdsa_alt.h | 54 + .../mbedtls_v3/port/include/entropy_poll.h | 28 + .../port/include/esp_crypto_shared_gdma.h | 47 + .../port/include/esp_ds/esp_rsa_sign_alt.h | 78 + .../mbedtls/mbedtls_v3/port/include/esp_mem.h | 12 + .../mbedtls/mbedtls_v3/port/include/gcm_alt.h | 43 + .../mbedtls_v3/port/include/mbedtls/bignum.h | 99 + .../mbedtls_v3/port/include/mbedtls/certs.h | 28 + .../mbedtls_v3/port/include/mbedtls/ecp.h | 37 + .../port/include/mbedtls/esp_config.h | 3030 +++++++++++++++ .../port/include/mbedtls/esp_debug.h | 59 + .../mbedtls_v3/port/include/mbedtls/gcm.h | 81 + .../port/include/mbedtls/ssl_internal.h | 28 + .../mbedtls_v3/port/include/md/esp_md.h | 119 + .../mbedtls/mbedtls_v3/port/include/md5_alt.h | 35 + .../mbedtls_v3/port/include/rsa_sign_alt.h | 28 + .../mbedtls_v3/port/include/sha/sha_block.h | 113 + .../mbedtls_v3/port/include/sha/sha_dma.h | 159 + .../port/include/sha/sha_parallel_engine.h | 205 + .../mbedtls_v3/port/include/sha1_alt.h | 98 + .../mbedtls_v3/port/include/sha256_alt.h | 79 + .../mbedtls_v3/port/include/sha512_alt.h | 97 + .../mbedtls/mbedtls_v3/port/mbedtls_debug.c | 86 + .../port/mbedtls_rom/mbedtls_rom_osi.c | 468 +++ .../port/mbedtls_rom/mbedtls_rom_osi.h | 794 ++++ .../port/mbedtls_rom/threading_alt.h | 22 + .../mbedtls/mbedtls_v3/port/md/esp_md.c | 61 + .../mbedtls/mbedtls_v3/port/net_sockets.c | 458 +++ .../mbedtls_v3/port/sha/block/esp_sha1.c | 220 ++ .../mbedtls_v3/port/sha/block/esp_sha256.c | 239 ++ .../mbedtls_v3/port/sha/block/esp_sha512.c | 290 ++ .../mbedtls/mbedtls_v3/port/sha/block/sha.c | 73 + .../mbedtls_v3/port/sha/dma/esp_sha1.c | 220 ++ .../mbedtls_v3/port/sha/dma/esp_sha256.c | 230 ++ .../mbedtls_v3/port/sha/dma/esp_sha512.c | 293 ++ .../port/sha/dma/esp_sha_crypto_dma_impl.c | 24 + .../port/sha/dma/esp_sha_gdma_impl.c | 13 + .../port/sha/dma/include/esp_sha_dma_priv.h | 38 + .../mbedtls/mbedtls_v3/port/sha/dma/sha.c | 348 ++ .../mbedtls/mbedtls_v3/port/sha/esp_sha.c | 99 + .../port/sha/parallel_engine/esp_sha1.c | 423 +++ .../port/sha/parallel_engine/esp_sha256.c | 390 ++ .../port/sha/parallel_engine/esp_sha512.c | 428 +++ .../mbedtls_v3/port/sha/parallel_engine/sha.c | 222 ++ .../mbedtls_v3/test_apps/CMakeLists.txt | 7 + .../mbedtls/mbedtls_v3/test_apps/README.md | 2 + .../mbedtls_v3/test_apps/ecdsa_key_p192.pem | 5 + .../mbedtls_v3/test_apps/ecdsa_key_p256.pem | 5 + .../mbedtls_v3/test_apps/main/CMakeLists.txt | 18 + .../mbedtls_v3/test_apps/main/app_main.c | 55 + .../test_apps/main/crts/bad_md_crt.pem | 9 + .../main/crts/correct_sig_crt_esp32_com.pem | 6 + .../test_apps/main/crts/prvtkey.pem | 27 + .../test_apps/main/crts/server_cert_bundle | Bin 0 -> 400 bytes .../test_apps/main/crts/server_cert_chain.pem | 20 + .../test_apps/main/crts/server_root.pem | 22 + .../main/crts/wrong_sig_crt_esp32_com.pem | 6 + .../mbedtls_v3/test_apps/main/test_aes.c | 1588 ++++++++ .../mbedtls_v3/test_apps/main/test_aes_gcm.c | 885 +++++ .../mbedtls_v3/test_apps/main/test_aes_perf.c | 75 + .../test_apps/main/test_aes_sha_parallel.c | 133 + .../test_apps/main/test_aes_sha_rsa.c | 295 ++ .../test_apps/main/test_apb_dport_access.c | 61 + .../test_apps/main/test_apb_dport_access.h | 23 + .../mbedtls_v3/test_apps/main/test_ecp.c | 333 ++ .../test_apps/main/test_esp_crt_bundle.c | 487 +++ .../mbedtls_v3/test_apps/main/test_gcm.c | 144 + .../mbedtls_v3/test_apps/main/test_mbedtls.c | 50 + .../test_apps/main/test_mbedtls_ecdsa.c | 237 ++ .../test_apps/main/test_mbedtls_mpi.c | 282 ++ .../test_apps/main/test_mbedtls_sha.c | 612 +++ .../test_apps/main/test_mbedtls_utils.c | 30 + .../test_apps/main/test_mbedtls_utils.h | 9 + .../mbedtls_v3/test_apps/main/test_rsa.c | 603 +++ .../mbedtls_v3/test_apps/main/test_sha.c | 145 + .../mbedtls_v3/test_apps/main/test_sha_perf.c | 62 + .../mbedtls_v3/test_apps/pytest_mbedtls_ut.py | 89 + .../test_apps/sdkconfig.ci.aes_no_hw | 2 + .../mbedtls_v3/test_apps/sdkconfig.ci.default | 0 .../test_apps/sdkconfig.ci.ecdsa_sign | 2 + .../test_apps/sdkconfig.ci.perf_esp32 | 1 + .../mbedtls_v3/test_apps/sdkconfig.ci.psram | 2 + .../test_apps/sdkconfig.ci.psram_all_ext | 4 + .../test_apps/sdkconfig.ci.psram_esp32 | 3 + .../test_apps/sdkconfig.ci.rom_impl | 2 + .../mbedtls_v3/test_apps/sdkconfig.defaults | 10 + .../test_apps/sdkconfig.defaults.esp32 | 4 + .../test_apps/sdkconfig.defaults.esp32c2 | 2 + .../test_apps/sdkconfig.defaults.esp32c3 | 1 + .../test_apps/sdkconfig.defaults.esp32s2 | 2 + .../test_apps/sdkconfig.defaults.esp32s3 | 2 + components/openssl/Kconfig | 4 +- components/openssl/platform/ssl_pm.c | 26 +- .../src/crypto/crypto_mbedtls-ec.c | 122 +- .../src/crypto/crypto_mbedtls-rsa.c | 75 +- .../wpa_supplicant/src/crypto/tls_mbedtls.c | 59 +- components/wpa_supplicant/src/utils/common.h | 3 + 154 files changed, 30296 insertions(+), 668 deletions(-) create mode 100644 components/mbedtls/Makefile.projbuild create mode 100644 components/mbedtls/component.mk create mode 100644 components/mbedtls/mbedtls_v3/CMakeLists.txt create mode 100644 components/mbedtls/mbedtls_v3/Kconfig create mode 100644 components/mbedtls/mbedtls_v3/component.mk create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_all.pem create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_local.pem create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/cmn_crt_authorities.csv create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/esp_crt_bundle.c create mode 100755 components/mbedtls/mbedtls_v3/esp_crt_bundle/gen_crt_bundle.py create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/include/esp_crt_bundle.h create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore.der create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore_crt_bundle create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/entrust.pem create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/entrust_crt_bundle create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/invalid_crt.pem create mode 100644 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/non_ascii_crt.pem create mode 100755 components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/test_gen_crt_bundle.py create mode 160000 components/mbedtls/mbedtls_v3/mbedtls create mode 100644 components/mbedtls/mbedtls_v3/port/aes/block/esp_aes.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_crypto_dma_impl.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_gdma_impl.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/dma/include/esp_aes_dma_priv.h create mode 100644 components/mbedtls/mbedtls_v3/port/aes/esp_aes_common.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/esp_aes_gcm.c create mode 100644 components/mbedtls/mbedtls_v3/port/aes/esp_aes_xts.c create mode 100644 components/mbedtls/mbedtls_v3/port/crypto_shared_gdma/esp_crypto_shared_gdma.c create mode 100644 components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.c create mode 100644 components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.h create mode 100644 components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_cli.c create mode 100644 components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_srv.c create mode 100644 components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_tls.c create mode 100644 components/mbedtls/mbedtls_v3/port/ecc/ecc_alt.c create mode 100644 components/mbedtls/mbedtls_v3/port/ecc/esp_ecc.c create mode 100644 components/mbedtls/mbedtls_v3/port/ecdsa/ecdsa_alt.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_bignum.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_ds/esp_rsa_sign_alt.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_hardware.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_mem.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_platform_time.c create mode 100644 components/mbedtls/mbedtls_v3/port/esp_timing.c create mode 100644 components/mbedtls/mbedtls_v3/port/include/aes/esp_aes.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_gcm.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_internal.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/aes_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/bignum_impl.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/ecc_impl.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/ecdsa/ecdsa_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/entropy_poll.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/esp_crypto_shared_gdma.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/esp_ds/esp_rsa_sign_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/esp_mem.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/gcm_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/bignum.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/certs.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/ecp.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_debug.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/gcm.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/mbedtls/ssl_internal.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/md/esp_md.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/md5_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/rsa_sign_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha/sha_block.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha/sha_dma.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha/sha_parallel_engine.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha1_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha256_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/include/sha512_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/mbedtls_debug.c create mode 100644 components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.c create mode 100644 components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.h create mode 100644 components/mbedtls/mbedtls_v3/port/mbedtls_rom/threading_alt.h create mode 100644 components/mbedtls/mbedtls_v3/port/md/esp_md.c create mode 100644 components/mbedtls/mbedtls_v3/port/net_sockets.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/block/esp_sha1.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/block/esp_sha256.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/block/esp_sha512.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/block/sha.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha1.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha256.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha512.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_crypto_dma_impl.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_gdma_impl.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/include/esp_sha_dma_priv.h create mode 100644 components/mbedtls/mbedtls_v3/port/sha/dma/sha.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/esp_sha.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha1.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha256.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha512.c create mode 100644 components/mbedtls/mbedtls_v3/port/sha/parallel_engine/sha.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/CMakeLists.txt create mode 100644 components/mbedtls/mbedtls_v3/test_apps/README.md create mode 100644 components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p192.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p256.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/CMakeLists.txt create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/app_main.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/bad_md_crt.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/correct_sig_crt_esp32_com.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/prvtkey.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_bundle create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_chain.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/server_root.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/crts/wrong_sig_crt_esp32_com.pem create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_aes.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_aes_gcm.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_aes_perf.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_parallel.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_rsa.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.h create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_ecp.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_esp_crt_bundle.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_gcm.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_ecdsa.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_mpi.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_sha.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.h create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_rsa.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_sha.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/main/test_sha_perf.c create mode 100644 components/mbedtls/mbedtls_v3/test_apps/pytest_mbedtls_ut.py create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.aes_no_hw create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.default create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.ecdsa_sign create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.perf_esp32 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_all_ext create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_esp32 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.rom_impl create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c2 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c3 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s2 create mode 100644 components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s3 diff --git a/.gitmodules b/.gitmodules index e5959514a..a4f43d7d3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,6 +6,10 @@ path = components/mbedtls/mbedtls_v2/mbedtls url = ../../espressif/mbedtls.git +[submodule "components/mbedtls/mbedtls_v3/mbedtls"] + path = components/mbedtls/mbedtls_v3/mbedtls + url = ../../espressif/mbedtls.git + [submodule "components/lwip/lwip"] path = components/lwip/lwip url = ../../espressif/esp-lwip.git diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index 4e40c865e..b8791836a 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -270,8 +270,14 @@ static esp_err_t set_pki_context(esp_tls_t *tls, const esp_tls_pki_t *pki) return ESP_ERR_MBEDTLS_X509_CRT_PARSE_FAILED; } +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_pk_parse_key(pki->pk_key, pki->privkey_pem_buf, pki->privkey_pem_bytes, + pki->privkey_password, pki->privkey_password_len, + mbedtls_ctr_drbg_random, &tls->ctr_drbg); +#else ret = mbedtls_pk_parse_key(pki->pk_key, pki->privkey_pem_buf, pki->privkey_pem_bytes, pki->privkey_password, pki->privkey_password_len); +#endif if (ret < 0) { ESP_LOGE(TAG, "mbedtls_pk_parse_keyfile returned -0x%x", -ret); ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ERR_TYPE_MBEDTLS, -ret); diff --git a/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c b/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c index 960e2bda0..66fecaabc 100644 --- a/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c +++ b/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha256_mbedtls.c @@ -26,9 +26,9 @@ mbedTLS totals. This doesn't matter inside libsodium's documented API, but would matter if any callers try to use the state's bit count. */ -_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->state) == sizeof(((mbedtls_sha256_context *)0)->state), "state mismatch"); -_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->count) == sizeof(((mbedtls_sha256_context *)0)->total), "count mismatch"); -_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->buf) == sizeof(((mbedtls_sha256_context *)0)->buffer), "buf mismatch"); +_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->state) == sizeof(((mbedtls_sha256_context *)0)->MBEDTLS_PRIVATE(state)), "state mismatch"); +_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->count) == sizeof(((mbedtls_sha256_context *)0)->MBEDTLS_PRIVATE(total)), "count mismatch"); +_Static_assert(sizeof(((crypto_hash_sha256_state *)0)->buf) == sizeof(((mbedtls_sha256_context *)0)->MBEDTLS_PRIVATE(buffer)), "buf mismatch"); /* Inline functions to convert between mbedTLS & libsodium context structures @@ -36,17 +36,17 @@ _Static_assert(sizeof(((crypto_hash_sha256_state *)0)->buf) == sizeof(((mbedtls_ static void sha256_mbedtls_to_libsodium(crypto_hash_sha256_state *ls_state, const mbedtls_sha256_context *mb_ctx) { - memcpy(&ls_state->count, mb_ctx->total, sizeof(ls_state->count)); - memcpy(ls_state->state, mb_ctx->state, sizeof(ls_state->state)); - memcpy(ls_state->buf, mb_ctx->buffer, sizeof(ls_state->buf)); + memcpy(&ls_state->count, mb_ctx->MBEDTLS_PRIVATE(total), sizeof(ls_state->count)); + memcpy(ls_state->state, mb_ctx->MBEDTLS_PRIVATE(state), sizeof(ls_state->state)); + memcpy(ls_state->buf, mb_ctx->MBEDTLS_PRIVATE(buffer), sizeof(ls_state->buf)); } static void sha256_libsodium_to_mbedtls(mbedtls_sha256_context *mb_ctx, crypto_hash_sha256_state *ls_state) { - memcpy(mb_ctx->total, &ls_state->count, sizeof(mb_ctx->total)); - memcpy(mb_ctx->state, ls_state->state, sizeof(mb_ctx->state)); - memcpy(mb_ctx->buffer, ls_state->buf, sizeof(mb_ctx->buffer)); - mb_ctx->is224 = 0; + memcpy(mb_ctx->MBEDTLS_PRIVATE(total), &ls_state->count, sizeof(mb_ctx->MBEDTLS_PRIVATE(total))); + memcpy(mb_ctx->MBEDTLS_PRIVATE(state), ls_state->state, sizeof(mb_ctx->MBEDTLS_PRIVATE(state))); + memcpy(mb_ctx->MBEDTLS_PRIVATE(buffer), ls_state->buf, sizeof(mb_ctx->MBEDTLS_PRIVATE(buffer))); + mb_ctx->MBEDTLS_PRIVATE(is224) = 0; } int diff --git a/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c b/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c index b005f0ce0..83a4cd708 100644 --- a/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c +++ b/components/libsodium/port/crypto_hash_mbedtls/crypto_hash_sha512_mbedtls.c @@ -27,9 +27,9 @@ mbedTLS totals. This doesn't matter inside libsodium's documented API, but would matter if any callers try to use the state's bit count. */ -_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->state) == sizeof(((mbedtls_sha512_context *)0)->state), "state mismatch"); -_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->count) == sizeof(((mbedtls_sha512_context *)0)->total), "count mismatch"); -_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->buf) == sizeof(((mbedtls_sha512_context *)0)->buffer), "buf mismatch"); +_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->state) == sizeof(((mbedtls_sha512_context *)0)->MBEDTLS_PRIVATE(state)), "state mismatch"); +_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->count) == sizeof(((mbedtls_sha512_context *)0)->MBEDTLS_PRIVATE(total)), "count mismatch"); +_Static_assert(sizeof(((crypto_hash_sha512_state *)0)->buf) == sizeof(((mbedtls_sha512_context *)0)->MBEDTLS_PRIVATE(buffer)), "buf mismatch"); /* Inline functions to convert between mbedTLS & libsodium context structures @@ -37,17 +37,17 @@ _Static_assert(sizeof(((crypto_hash_sha512_state *)0)->buf) == sizeof(((mbedtls_ static void sha512_mbedtls_to_libsodium(crypto_hash_sha512_state *ls_state, const mbedtls_sha512_context *mb_ctx) { - memcpy(ls_state->count, mb_ctx->total, sizeof(ls_state->count)); - memcpy(ls_state->state, mb_ctx->state, sizeof(ls_state->state)); - memcpy(ls_state->buf, mb_ctx->buffer, sizeof(ls_state->buf)); + memcpy(ls_state->count, mb_ctx->MBEDTLS_PRIVATE(total), sizeof(ls_state->count)); + memcpy(ls_state->state, mb_ctx->MBEDTLS_PRIVATE(state), sizeof(ls_state->state)); + memcpy(ls_state->buf, mb_ctx->MBEDTLS_PRIVATE(buffer), sizeof(ls_state->buf)); } static void sha512_libsodium_to_mbedtls(mbedtls_sha512_context *mb_ctx, crypto_hash_sha512_state *ls_state) { - memcpy(mb_ctx->total, ls_state->count, sizeof(mb_ctx->total)); - memcpy(mb_ctx->state, ls_state->state, sizeof(mb_ctx->state)); - memcpy(mb_ctx->buffer, ls_state->buf, sizeof(mb_ctx->buffer)); - mb_ctx->is384 = 0; + memcpy(mb_ctx->MBEDTLS_PRIVATE(total), ls_state->count, sizeof(mb_ctx->MBEDTLS_PRIVATE(total))); + memcpy(mb_ctx->MBEDTLS_PRIVATE(state), ls_state->state, sizeof(mb_ctx->MBEDTLS_PRIVATE(state))); + memcpy(mb_ctx->MBEDTLS_PRIVATE(buffer), ls_state->buf, sizeof(mb_ctx->MBEDTLS_PRIVATE(buffer))); + mb_ctx->MBEDTLS_PRIVATE(is384) = 0; } int diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index fb421f383..ab8e1fd88 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -1,6 +1,2 @@ -if (CONFIG_MBEDTLS_V2) -set(MBEDTLS_V2 1) -message("Use MBEDTLS_V2") -endif() - -include("${IDF_PATH}/components/mbedtls/mbedtls_v2/CMakeLists.txt") \ No newline at end of file +include("${IDF_PATH}/components/mbedtls/mbedtls_v2/CMakeLists.txt") +include("${IDF_PATH}/components/mbedtls/mbedtls_v3/CMakeLists.txt") \ No newline at end of file diff --git a/components/mbedtls/Kconfig b/components/mbedtls/Kconfig index 243182542..e0ff70505 100644 --- a/components/mbedtls/Kconfig +++ b/components/mbedtls/Kconfig @@ -1,3 +1,5 @@ +menu "mbedTLS" + choice MBEDTLS_VERSION prompt "Mbedtls Version" default MBEDTLS_V2 @@ -5,6 +7,15 @@ choice MBEDTLS_VERSION config MBEDTLS_V2 bool "MbedTls V2.x" + config MBEDTLS_V3 + bool "MbedTls V3.x (Not Support openssl)" +endchoice + +if MBEDTLS_V2 +source "$IDF_PATH/components/mbedtls/mbedtls_v2/Kconfig" +endif - source "$IDF_PATH/components/mbedtls/mbedtls_v2/Kconfig" -endchoice() +if MBEDTLS_V3 +source "$IDF_PATH/components/mbedtls/mbedtls_v3/Kconfig" +endif +endmenu diff --git a/components/mbedtls/Makefile.projbuild b/components/mbedtls/Makefile.projbuild new file mode 100644 index 000000000..9992735ed --- /dev/null +++ b/components/mbedtls/Makefile.projbuild @@ -0,0 +1,8 @@ +# Anyone compiling mbedTLS code needs the name of the +# alternative config file +CPPFLAGS += -DMBEDTLS_CONFIG_FILE='"mbedtls/esp_config.h"' -DCONFIG_SSL_USING_MBEDTLS + +# Catch usage of deprecated mbedTLS functions when building tests +ifneq ("$(filter mbedtls,$(TEST_COMPONENTS_LIST))","") +CPPFLAGS += -DMBEDTLS_DEPRECATED_WARNING +endif diff --git a/components/mbedtls/component.mk b/components/mbedtls/component.mk new file mode 100644 index 000000000..1970e3b32 --- /dev/null +++ b/components/mbedtls/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +ifdef CONFIG_MBEDTLS_V2 +include $(IDF_PATH)/components/mbedtls/mbedtls_v2/component.mk +endif + +ifdef CONFIG_MBEDTLS_V3 +include $(IDF_PATH)/components/mbedtls/mbedtls_v3/component.mk +endif \ No newline at end of file diff --git a/components/mbedtls/mbedtls_v2/CMakeLists.txt b/components/mbedtls/mbedtls_v2/CMakeLists.txt index 8f15eae98..3ece781de 100644 --- a/components/mbedtls/mbedtls_v2/CMakeLists.txt +++ b/components/mbedtls/mbedtls_v2/CMakeLists.txt @@ -1,4 +1,4 @@ -if(MBEDTLS_V3) +if(CONFIG_MBEDTLS_V3) return() endif() diff --git a/components/mbedtls/mbedtls_v2/Kconfig b/components/mbedtls/mbedtls_v2/Kconfig index 2a44d3ded..f60d42ff6 100644 --- a/components/mbedtls/mbedtls_v2/Kconfig +++ b/components/mbedtls/mbedtls_v2/Kconfig @@ -1,660 +1,656 @@ -menu "mbedTLS" - depends on MBEDTLS_V2 - choice MBEDTLS_MEM_ALLOC_MODE - prompt "Memory allocation strategy" - default MBEDTLS_INTERNAL_MEM_ALLOC - help - Allocation strategy for mbedTLS, essentially provides ability to - allocate all required dynamic allocations from, - - - Internal DRAM memory only - - External SPIRAM memory only - - Either internal or external memory based on default malloc() - behavior in ESP-IDF - - Custom allocation mode, by overwriting calloc()/free() using - mbedtls_platform_set_calloc_free() function - - Recommended mode here is always internal, since that is most preferred - from security perspective. But if application requirement does not - allow sufficient free internal memory then alternate mode can be - selected. - - config MBEDTLS_INTERNAL_MEM_ALLOC - bool "Internal memory" - - config MBEDTLS_EXTERNAL_MEM_ALLOC - bool "External SPIRAM" - depends on ESP32_SPIRAM_SUPPORT - - config MBEDTLS_DEFAULT_MEM_ALLOC - bool "Default alloc mode" - - config MBEDTLS_CUSTOM_MEM_ALLOC - bool "Custom alloc mode" - - endchoice #MBEDTLS_MEM_ALLOC_MODE - - config MBEDTLS_SSL_MAX_CONTENT_LEN - int "TLS maximum message content length" - default 16384 - range 512 16384 - depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - Maximum TLS message length (in bytes) supported by mbedTLS. - - 16384 is the default and this value is required to comply - fully with TLS standards. - - However you can set a lower value in order to save RAM. This - is safe if the other end of the connection supports Maximum - Fragment Length Negotiation Extension (max_fragment_length, - see RFC6066) or you know for certain that it will never send a - message longer than a certain number of bytes. - - If the value is set too low, symptoms are a failed TLS - handshake or a return value of MBEDTLS_ERR_SSL_INVALID_RECORD - (-0x7200). - - config MBEDTLS_ASYMMETRIC_CONTENT_LEN - bool "Asymmetric in/out fragment length" - default y - help - If enabled, this option allows customizing TLS in/out fragment length - in asymmetric way. Please note that enabling this with default values - saves 12KB of dynamic memory per TLS connection. - - config MBEDTLS_SSL_IN_CONTENT_LEN - int "TLS maximum incoming fragment length" - default 16384 - range 512 16384 - depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - This defines maximum incoming fragment length, overriding default - maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). - - config MBEDTLS_SSL_OUT_CONTENT_LEN - int "TLS maximum outgoing fragment length" - default 4096 - range 512 16384 - depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN - help - This defines maximum outgoing fragment length, overriding default - maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). - - config MBEDTLS_DYNAMIC_BUFFER - bool "Using dynamic TX/RX buffer" +choice MBEDTLS_MEM_ALLOC_MODE + prompt "Memory allocation strategy" + default MBEDTLS_INTERNAL_MEM_ALLOC + help + Allocation strategy for mbedTLS, essentially provides ability to + allocate all required dynamic allocations from, + + - Internal DRAM memory only + - External SPIRAM memory only + - Either internal or external memory based on default malloc() + behavior in ESP-IDF + - Custom allocation mode, by overwriting calloc()/free() using + mbedtls_platform_set_calloc_free() function + + Recommended mode here is always internal, since that is most preferred + from security perspective. But if application requirement does not + allow sufficient free internal memory then alternate mode can be + selected. + + config MBEDTLS_INTERNAL_MEM_ALLOC + bool "Internal memory" + + config MBEDTLS_EXTERNAL_MEM_ALLOC + bool "External SPIRAM" + depends on ESP32_SPIRAM_SUPPORT + + config MBEDTLS_DEFAULT_MEM_ALLOC + bool "Default alloc mode" + + config MBEDTLS_CUSTOM_MEM_ALLOC + bool "Custom alloc mode" + +endchoice #MBEDTLS_MEM_ALLOC_MODE + +config MBEDTLS_SSL_MAX_CONTENT_LEN + int "TLS maximum message content length" + default 16384 + range 512 16384 + depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + Maximum TLS message length (in bytes) supported by mbedTLS. + + 16384 is the default and this value is required to comply + fully with TLS standards. + + However you can set a lower value in order to save RAM. This + is safe if the other end of the connection supports Maximum + Fragment Length Negotiation Extension (max_fragment_length, + see RFC6066) or you know for certain that it will never send a + message longer than a certain number of bytes. + + If the value is set too low, symptoms are a failed TLS + handshake or a return value of MBEDTLS_ERR_SSL_INVALID_RECORD + (-0x7200). + +config MBEDTLS_ASYMMETRIC_CONTENT_LEN + bool "Asymmetric in/out fragment length" + default y + help + If enabled, this option allows customizing TLS in/out fragment length + in asymmetric way. Please note that enabling this with default values + saves 12KB of dynamic memory per TLS connection. + +config MBEDTLS_SSL_IN_CONTENT_LEN + int "TLS maximum incoming fragment length" + default 16384 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum incoming fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + +config MBEDTLS_SSL_OUT_CONTENT_LEN + int "TLS maximum outgoing fragment length" + default 4096 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum outgoing fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + +config MBEDTLS_DYNAMIC_BUFFER + bool "Using dynamic TX/RX buffer" + default n + select MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + Using dynamic TX/RX buffer. After enabling this option, mbedTLS will + allocate TX buffer when need to send data and then free it if all data + is sent, allocate RX buffer when need to receive data and then free it + when all data is used or read by upper layer. + + By default, when SSL is initialized, mbedTLS also allocate TX and + RX buffer with the default value of "MBEDTLS_SSL_OUT_CONTENT_LEN" or + "MBEDTLS_SSL_IN_CONTENT_LEN", so to save more heap, users can set + the options to be an appropriate value. + +config MBEDTLS_DYNAMIC_FREE_PEER_CERT + bool "Free SSL peer certificate after its usage" + default n + depends on MBEDTLS_DYNAMIC_BUFFER + help + Free peer certificate after its usage in handshake process. + +config MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + bool "Free certificate, key and DHM data after its usage" + default n + depends on MBEDTLS_DYNAMIC_BUFFER + help + Free certificate, private key and DHM data after its usage in handshake process. + + The option will decrease heap cost when handshake, but also lead to problem: + + Becasue all certificate, private key and DHM data are freed so users should register + certificate and private key to ssl config object again. + +config MBEDTLS_DEBUG + bool "Enable mbedTLS debugging" + default n + help + Enable mbedTLS debugging functions at compile time. + + If this option is enabled, you can include + "mbedtls/esp_debug.h" and call mbedtls_esp_enable_debug_log() + at runtime in order to enable mbedTLS debug output via the ESP + log mechanism. + +choice MBEDTLS_DEBUG_LEVEL + bool "Set mbedTLS debugging level" + depends on MBEDTLS_DEBUG + default MBEDTLS_DEBUG_LEVEL_VERBOSE + help + Set mbedTLS debugging level + + config MBEDTLS_DEBUG_LEVEL_WARN + bool "Warning" + config MBEDTLS_DEBUG_LEVEL_INFO + bool "Info" + config MBEDTLS_DEBUG_LEVEL_DEBUG + bool "Debug" + config MBEDTLS_DEBUG_LEVEL_VERBOSE + bool "Verbose" +endchoice + +config MBEDTLS_DEBUG_LEVEL + int + default 1 if MBEDTLS_DEBUG_LEVEL_WARN + default 2 if MBEDTLS_DEBUG_LEVEL_INFO + default 3 if MBEDTLS_DEBUG_LEVEL_DEBUG + default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE + +config MBEDTLS_HAVE_TIME + bool "Enable mbedtls time" + depends on !ESP32_TIME_SYSCALL_USE_NONE + default y + help + System has time.h and time(). + The time does not need to be correct, only time differences are used. + +config MBEDTLS_HAVE_TIME_DATE + bool "Enable mbedtls certificate expiry check" + depends on MBEDTLS_HAVE_TIME + default n + help + System has time.h and time(), gmtime() and the clock is correct. + The time needs to be correct (not necesarily very accurate, but at least + the date should be correct). This is used to verify the validity period of + X.509 certificates. + + It is suggested that you should get the real time by "SNTP". + +choice MBEDTLS_TLS_MODE + bool "TLS Protocol Role" + default MBEDTLS_TLS_SERVER_AND_CLIENT + help + mbedTLS can be compiled with protocol support for the TLS + server, TLS client, or both server and client. + + Reducing the number of TLS roles supported saves code size. + + config MBEDTLS_TLS_SERVER_AND_CLIENT + bool "Server & Client" + select MBEDTLS_TLS_SERVER + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_SERVER_ONLY + bool "Server" + select MBEDTLS_TLS_SERVER + config MBEDTLS_TLS_CLIENT_ONLY + bool "Client" + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_DISABLED + bool "None" + +endchoice + +config MBEDTLS_TLS_SERVER + bool + select MBEDTLS_TLS_ENABLED +config MBEDTLS_TLS_CLIENT + bool + select MBEDTLS_TLS_ENABLED +config MBEDTLS_TLS_ENABLED + bool + +menu "TLS Key Exchange Methods" + depends on MBEDTLS_TLS_ENABLED + + config MBEDTLS_PSK_MODES + bool "Enable pre-shared-key ciphersuites" default n - select MBEDTLS_ASYMMETRIC_CONTENT_LEN help - Using dynamic TX/RX buffer. After enabling this option, mbedTLS will - allocate TX buffer when need to send data and then free it if all data - is sent, allocate RX buffer when need to receive data and then free it - when all data is used or read by upper layer. - - By default, when SSL is initialized, mbedTLS also allocate TX and - RX buffer with the default value of "MBEDTLS_SSL_OUT_CONTENT_LEN" or - "MBEDTLS_SSL_IN_CONTENT_LEN", so to save more heap, users can set - the options to be an appropriate value. - - config MBEDTLS_DYNAMIC_FREE_PEER_CERT - bool "Free SSL peer certificate after its usage" - default n - depends on MBEDTLS_DYNAMIC_BUFFER - help - Free peer certificate after its usage in handshake process. - - config MBEDTLS_DYNAMIC_FREE_CONFIG_DATA - bool "Free certificate, key and DHM data after its usage" - default n - depends on MBEDTLS_DYNAMIC_BUFFER - help - Free certificate, private key and DHM data after its usage in handshake process. - - The option will decrease heap cost when handshake, but also lead to problem: + Enable to show configuration for different types of pre-shared-key TLS authentatication methods. - Becasue all certificate, private key and DHM data are freed so users should register - certificate and private key to ssl config object again. + Leaving this options disabled will save code size if they are not used. - config MBEDTLS_DEBUG - bool "Enable mbedTLS debugging" + config MBEDTLS_KEY_EXCHANGE_PSK + bool "Enable PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES default n help - Enable mbedTLS debugging functions at compile time. - - If this option is enabled, you can include - "mbedtls/esp_debug.h" and call mbedtls_esp_enable_debug_log() - at runtime in order to enable mbedTLS debug output via the ESP - log mechanism. + Enable to support symmetric key PSK (pre-shared-key) TLS key exchange modes. - choice MBEDTLS_DEBUG_LEVEL - bool "Set mbedTLS debugging level" - depends on MBEDTLS_DEBUG - default MBEDTLS_DEBUG_LEVEL_VERBOSE + config MBEDTLS_KEY_EXCHANGE_DHE_PSK + bool "Enable DHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default y help - Set mbedTLS debugging level - - config MBEDTLS_DEBUG_LEVEL_WARN - bool "Warning" - config MBEDTLS_DEBUG_LEVEL_INFO - bool "Info" - config MBEDTLS_DEBUG_LEVEL_DEBUG - bool "Debug" - config MBEDTLS_DEBUG_LEVEL_VERBOSE - bool "Verbose" - endchoice + Enable to support Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - config MBEDTLS_DEBUG_LEVEL - int - default 1 if MBEDTLS_DEBUG_LEVEL_WARN - default 2 if MBEDTLS_DEBUG_LEVEL_INFO - default 3 if MBEDTLS_DEBUG_LEVEL_DEBUG - default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE - - config MBEDTLS_HAVE_TIME - bool "Enable mbedtls time" - depends on !ESP32_TIME_SYSCALL_USE_NONE + config MBEDTLS_KEY_EXCHANGE_ECDHE_PSK + bool "Enable ECDHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES && MBEDTLS_ECDH_C default y help - System has time.h and time(). - The time does not need to be correct, only time differences are used. + Enable to support Elliptic-Curve-Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - config MBEDTLS_HAVE_TIME_DATE - bool "Enable mbedtls certificate expiry check" - depends on MBEDTLS_HAVE_TIME - default n + config MBEDTLS_KEY_EXCHANGE_RSA_PSK + bool "Enable RSA-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default y help - System has time.h and time(), gmtime() and the clock is correct. - The time needs to be correct (not necesarily very accurate, but at least - the date should be correct). This is used to verify the validity period of - X.509 certificates. - - It is suggested that you should get the real time by "SNTP". + Enable to support RSA PSK (pre-shared-key) TLS authentication modes. - choice MBEDTLS_TLS_MODE - bool "TLS Protocol Role" - default MBEDTLS_TLS_SERVER_AND_CLIENT + config MBEDTLS_KEY_EXCHANGE_RSA + bool "Enable RSA-only based ciphersuite modes" + default y help - mbedTLS can be compiled with protocol support for the TLS - server, TLS client, or both server and client. - - Reducing the number of TLS roles supported saves code size. - - config MBEDTLS_TLS_SERVER_AND_CLIENT - bool "Server & Client" - select MBEDTLS_TLS_SERVER - select MBEDTLS_TLS_CLIENT - config MBEDTLS_TLS_SERVER_ONLY - bool "Server" - select MBEDTLS_TLS_SERVER - config MBEDTLS_TLS_CLIENT_ONLY - bool "Client" - select MBEDTLS_TLS_CLIENT - config MBEDTLS_TLS_DISABLED - bool "None" + Enable to support ciphersuites with prefix TLS-RSA-WITH- - endchoice - - config MBEDTLS_TLS_SERVER - bool - select MBEDTLS_TLS_ENABLED - config MBEDTLS_TLS_CLIENT - bool - select MBEDTLS_TLS_ENABLED - config MBEDTLS_TLS_ENABLED - bool - - menu "TLS Key Exchange Methods" - depends on MBEDTLS_TLS_ENABLED - - config MBEDTLS_PSK_MODES - bool "Enable pre-shared-key ciphersuites" - default n - help - Enable to show configuration for different types of pre-shared-key TLS authentatication methods. - - Leaving this options disabled will save code size if they are not used. - - config MBEDTLS_KEY_EXCHANGE_PSK - bool "Enable PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default n - help - Enable to support symmetric key PSK (pre-shared-key) TLS key exchange modes. - - config MBEDTLS_KEY_EXCHANGE_DHE_PSK - bool "Enable DHE-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default y - help - Enable to support Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_ECDHE_PSK - bool "Enable ECDHE-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES && MBEDTLS_ECDH_C - default y - help - Enable to support Elliptic-Curve-Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_RSA_PSK - bool "Enable RSA-PSK based ciphersuite modes" - depends on MBEDTLS_PSK_MODES - default y - help - Enable to support RSA PSK (pre-shared-key) TLS authentication modes. - - config MBEDTLS_KEY_EXCHANGE_RSA - bool "Enable RSA-only based ciphersuite modes" - default y - help - Enable to support ciphersuites with prefix TLS-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_DHE_RSA - bool "Enable DHE-RSA based ciphersuite modes" - default y - help - Enable to support ciphersuites with prefix TLS-DHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE - bool "Support Elliptic Curve based ciphersuites" - depends on MBEDTLS_ECP_C - default y - help - Enable to show Elliptic Curve based ciphersuite mode options. - - Disabling all Elliptic Curve ciphersuites saves code size and - can give slightly faster TLS handshakes, provided the server supports - RSA-only ciphersuite modes. - - config MBEDTLS_KEY_EXCHANGE_ECDHE_RSA - bool "Enable ECDHE-RSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA - bool "Enable ECDHE-ECDSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA - bool "Enable ECDH-ECDSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - config MBEDTLS_KEY_EXCHANGE_ECDH_RSA - bool "Enable ECDH-RSA based ciphersuite modes" - depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C - default y - help - Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - - endmenu # TLS key exchange modes - - config MBEDTLS_SSL_RENEGOTIATION - bool "Support TLS renegotiation" - depends on MBEDTLS_TLS_ENABLED + config MBEDTLS_KEY_EXCHANGE_DHE_RSA + bool "Enable DHE-RSA based ciphersuite modes" default y help - The two main uses of renegotiation are (1) refresh keys on long-lived - connections and (2) client authentication after the initial handshake. - If you don't need renegotiation, disabling it will save code size and - reduce the possibility of abuse/vulnerability. - - config MBEDTLS_SSL_PROTO_SSL3 - bool "Legacy SSL 3.0 support" - depends on MBEDTLS_TLS_ENABLED - default n - help - Support the legacy SSL 3.0 protocol. Most servers will speak a newer - TLS protocol these days. + Enable to support ciphersuites with prefix TLS-DHE-RSA-WITH- - config MBEDTLS_SSL_PROTO_TLS1 - bool "Support TLS 1.0 protocol" - depends on MBEDTLS_TLS_ENABLED + config MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE + bool "Support Elliptic Curve based ciphersuites" + depends on MBEDTLS_ECP_C default y + help + Enable to show Elliptic Curve based ciphersuite mode options. - config MBEDTLS_SSL_PROTO_TLS1_1 - bool "Support TLS 1.1 protocol" - depends on MBEDTLS_TLS_ENABLED - default y + Disabling all Elliptic Curve ciphersuites saves code size and + can give slightly faster TLS handshakes, provided the server supports + RSA-only ciphersuite modes. - config MBEDTLS_SSL_PROTO_TLS1_2 - bool "Support TLS 1.2 protocol" - depends on MBEDTLS_TLS_ENABLED + config MBEDTLS_KEY_EXCHANGE_ECDHE_RSA + bool "Enable ECDHE-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C default y - - config MBEDTLS_SSL_PROTO_DTLS - bool "Support DTLS protocol (all versions)" - default n - depends on MBEDTLS_SSL_PROTO_TLS1_1 || MBEDTLS_SSL_PROTO_TLS1_2 help - Requires TLS 1.1 to be enabled for DTLS 1.0 - Requires TLS 1.2 to be enabled for DTLS 1.2 + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - config MBEDTLS_SSL_ALPN - bool "Support ALPN (Application Layer Protocol Negotiation)" - depends on MBEDTLS_TLS_ENABLED + config MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA + bool "Enable ECDHE-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C default y help - Disabling this option will save some code size if it is not needed. + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - config MBEDTLS_CLIENT_SSL_SESSION_TICKETS - bool "TLS: Client Support for RFC 5077 SSL session tickets" + config MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA + bool "Enable ECDH-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C default y - depends on MBEDTLS_TLS_ENABLED help - Client support for RFC 5077 session tickets. See mbedTLS documentation for more details. - Disabling this option will save some code size. + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- - config MBEDTLS_SERVER_SSL_SESSION_TICKETS - bool "TLS: Server Support for RFC 5077 SSL session tickets" + config MBEDTLS_KEY_EXCHANGE_ECDH_RSA + bool "Enable ECDH-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C default y - depends on MBEDTLS_TLS_ENABLED - depends on MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C help - Server support for RFC 5077 session tickets. See mbedTLS documentation for more details. - Disabling this option will save some code size. - - menu "Symmetric Ciphers" - - config MBEDTLS_AES_C - bool "AES block cipher" - default y - - config MBEDTLS_CAMELLIA_C - bool "Camellia block cipher" - default n - - config MBEDTLS_DES_C - bool "DES block cipher (legacy, insecure)" - default n - help - Enables the DES block cipher to support 3DES-based TLS ciphersuites. - - 3DES is vulnerable to the Sweet32 attack and should only be enabled - if absolutely necessary. - - choice MBEDTLS_RC4_MODE - prompt "RC4 Stream Cipher (legacy, insecure)" - default MBEDTLS_RC4_DISABLED - help - ARCFOUR (RC4) stream cipher can be disabled entirely, enabled but not - added to default ciphersuites, or enabled completely. - - Please consider the security implications before enabling RC4. - - config MBEDTLS_RC4_DISABLED - bool "Disabled" - config MBEDTLS_RC4_ENABLED_NO_DEFAULT - bool "Enabled, not in default ciphersuites" - config MBEDTLS_RC4_ENABLED - bool "Enabled" - endchoice - - config MBEDTLS_BLOWFISH_C - bool "Blowfish block cipher (read help)" - default n - help - Enables the Blowfish block cipher (not used for TLS sessions.) - - The Blowfish cipher is not used for mbedTLS TLS sessions but can be - used for other purposes. Read up on the limitations of Blowfish (including - Sweet32) before enabling. - - config MBEDTLS_XTEA_C - bool "XTEA block cipher" - default n - help - Enables the XTEA block cipher. - - - config MBEDTLS_CCM_C - bool "CCM (Counter with CBC-MAC) block cipher modes" - default y - depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C - help - Enable Counter with CBC-MAC (CCM) modes for AES and/or Camellia ciphers. - - Disabling this option saves some code size. - - config MBEDTLS_GCM_C - bool "GCM (Galois/Counter) block cipher modes" - default y - depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C - help - Enable Galois/Counter Mode for AES and/or Camellia ciphers. - - This option is generally faster than CCM. - - endmenu # Symmetric Ciphers - - config MBEDTLS_RIPEMD160_C - bool "Enable RIPEMD-160 hash algorithm" - default n - help - Enable the RIPEMD-160 hash algorithm. - - menu "Certificates" + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + +endmenu # TLS key exchange modes + +config MBEDTLS_SSL_RENEGOTIATION + bool "Support TLS renegotiation" + depends on MBEDTLS_TLS_ENABLED + default y + help + The two main uses of renegotiation are (1) refresh keys on long-lived + connections and (2) client authentication after the initial handshake. + If you don't need renegotiation, disabling it will save code size and + reduce the possibility of abuse/vulnerability. + +config MBEDTLS_SSL_PROTO_SSL3 + bool "Legacy SSL 3.0 support" + depends on MBEDTLS_TLS_ENABLED + default n + help + Support the legacy SSL 3.0 protocol. Most servers will speak a newer + TLS protocol these days. + +config MBEDTLS_SSL_PROTO_TLS1 + bool "Support TLS 1.0 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + +config MBEDTLS_SSL_PROTO_TLS1_1 + bool "Support TLS 1.1 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + +config MBEDTLS_SSL_PROTO_TLS1_2 + bool "Support TLS 1.2 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + +config MBEDTLS_SSL_PROTO_DTLS + bool "Support DTLS protocol (all versions)" + default n + depends on MBEDTLS_SSL_PROTO_TLS1_1 || MBEDTLS_SSL_PROTO_TLS1_2 + help + Requires TLS 1.1 to be enabled for DTLS 1.0 + Requires TLS 1.2 to be enabled for DTLS 1.2 + +config MBEDTLS_SSL_ALPN + bool "Support ALPN (Application Layer Protocol Negotiation)" + depends on MBEDTLS_TLS_ENABLED + default y + help + Disabling this option will save some code size if it is not needed. + +config MBEDTLS_CLIENT_SSL_SESSION_TICKETS + bool "TLS: Client Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED + help + Client support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + +config MBEDTLS_SERVER_SSL_SESSION_TICKETS + bool "TLS: Server Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED + depends on MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C + help + Server support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + +menu "Symmetric Ciphers" + + config MBEDTLS_AES_C + bool "AES block cipher" + default y - config MBEDTLS_PEM_PARSE_C - bool "Read & Parse PEM formatted certificates" - default y - help - Enable decoding/parsing of PEM formatted certificates. + config MBEDTLS_CAMELLIA_C + bool "Camellia block cipher" + default n - If your certificates are all in the simpler DER format, disabling - this option will save some code size. + config MBEDTLS_DES_C + bool "DES block cipher (legacy, insecure)" + default n + help + Enables the DES block cipher to support 3DES-based TLS ciphersuites. - config MBEDTLS_PEM_WRITE_C - bool "Write PEM formatted certificates" - default y - help - Enable writing of PEM formatted certificates. + 3DES is vulnerable to the Sweet32 attack and should only be enabled + if absolutely necessary. - If writing certificate data only in DER format, disabling this - option will save some code size. + choice MBEDTLS_RC4_MODE + prompt "RC4 Stream Cipher (legacy, insecure)" + default MBEDTLS_RC4_DISABLED + help + ARCFOUR (RC4) stream cipher can be disabled entirely, enabled but not + added to default ciphersuites, or enabled completely. - config MBEDTLS_X509_CRL_PARSE_C - bool "X.509 CRL parsing" - default y - help - Support for parsing X.509 Certifificate Revocation Lists. + Please consider the security implications before enabling RC4. - config MBEDTLS_X509_CSR_PARSE_C - bool "X.509 CSR parsing" - default y - help - Support for parsing X.509 Certifificate Signing Requests + config MBEDTLS_RC4_DISABLED + bool "Disabled" + config MBEDTLS_RC4_ENABLED_NO_DEFAULT + bool "Enabled, not in default ciphersuites" + config MBEDTLS_RC4_ENABLED + bool "Enabled" + endchoice - endmenu # Certificates + config MBEDTLS_BLOWFISH_C + bool "Blowfish block cipher (read help)" + default n + help + Enables the Blowfish block cipher (not used for TLS sessions.) - menuconfig MBEDTLS_ECP_C - bool "Elliptic Curve Ciphers" - default y + The Blowfish cipher is not used for mbedTLS TLS sessions but can be + used for other purposes. Read up on the limitations of Blowfish (including + Sweet32) before enabling. - config MBEDTLS_ECDH_C - bool "Elliptic Curve Diffie-Hellman (ECDH)" - depends on MBEDTLS_ECP_C - default y + config MBEDTLS_XTEA_C + bool "XTEA block cipher" + default n help - Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. + Enables the XTEA block cipher. - config MBEDTLS_ECDSA_C - bool "Elliptic Curve DSA" - depends on MBEDTLS_ECDH_C - default y - help - Enable ECDSA. Needed to use ECDSA-xxx TLS ciphersuites. - config MBEDTLS_ECP_DP_SECP192R1_ENABLED - bool "Enable SECP192R1 curve" - depends on MBEDTLS_ECP_C + config MBEDTLS_CCM_C + bool "CCM (Counter with CBC-MAC) block cipher modes" default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C help - Enable support for SECP192R1 Elliptic Curve. + Enable Counter with CBC-MAC (CCM) modes for AES and/or Camellia ciphers. - config MBEDTLS_ECP_DP_SECP224R1_ENABLED - bool "Enable SECP224R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP224R1 Elliptic Curve. + Disabling this option saves some code size. - config MBEDTLS_ECP_DP_SECP256R1_ENABLED - bool "Enable SECP256R1 curve" - depends on MBEDTLS_ECP_C + config MBEDTLS_GCM_C + bool "GCM (Galois/Counter) block cipher modes" default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C help - Enable support for SECP256R1 Elliptic Curve. + Enable Galois/Counter Mode for AES and/or Camellia ciphers. - config MBEDTLS_ECP_DP_SECP384R1_ENABLED - bool "Enable SECP384R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP384R1 Elliptic Curve. + This option is generally faster than CCM. - config MBEDTLS_ECP_DP_SECP521R1_ENABLED - bool "Enable SECP521R1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP521R1 Elliptic Curve. +endmenu # Symmetric Ciphers - config MBEDTLS_ECP_DP_SECP192K1_ENABLED - bool "Enable SECP192K1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP192K1 Elliptic Curve. +config MBEDTLS_RIPEMD160_C + bool "Enable RIPEMD-160 hash algorithm" + default n + help + Enable the RIPEMD-160 hash algorithm. - config MBEDTLS_ECP_DP_SECP224K1_ENABLED - bool "Enable SECP224K1 curve" - depends on MBEDTLS_ECP_C - default y - help - Enable support for SECP224K1 Elliptic Curve. +menu "Certificates" - config MBEDTLS_ECP_DP_SECP256K1_ENABLED - bool "Enable SECP256K1 curve" - depends on MBEDTLS_ECP_C + config MBEDTLS_PEM_PARSE_C + bool "Read & Parse PEM formatted certificates" default y help - Enable support for SECP256K1 Elliptic Curve. + Enable decoding/parsing of PEM formatted certificates. - config MBEDTLS_ECP_DP_BP256R1_ENABLED - bool "Enable BP256R1 curve" - depends on MBEDTLS_ECP_C - default y - help - support for DP Elliptic Curve. + If your certificates are all in the simpler DER format, disabling + this option will save some code size. - config MBEDTLS_ECP_DP_BP384R1_ENABLED - bool "Enable BP384R1 curve" - depends on MBEDTLS_ECP_C + config MBEDTLS_PEM_WRITE_C + bool "Write PEM formatted certificates" default y help - support for DP Elliptic Curve. + Enable writing of PEM formatted certificates. - config MBEDTLS_ECP_DP_BP512R1_ENABLED - bool "Enable BP512R1 curve" - depends on MBEDTLS_ECP_C - default y - help - support for DP Elliptic Curve. + If writing certificate data only in DER format, disabling this + option will save some code size. - config MBEDTLS_ECP_DP_CURVE25519_ENABLED - bool "Enable CURVE25519 curve" - depends on MBEDTLS_ECP_C + config MBEDTLS_X509_CRL_PARSE_C + bool "X.509 CRL parsing" default y help - Enable support for CURVE25519 Elliptic Curve. + Support for parsing X.509 Certifificate Revocation Lists. - config MBEDTLS_ECP_NIST_OPTIM - bool "NIST 'modulo p' optimisations" - depends on MBEDTLS_ECP_C + config MBEDTLS_X509_CSR_PARSE_C + bool "X.509 CSR parsing" default y help - NIST 'modulo p' optimisations increase Elliptic Curve operation performance. - - Disabling this option saves some code size. - - # end of Elliptic Curve options - - menu "Util" - - config util_assert - bool "Enable assert for util components" - default n - help - Enable this option, util components will use assert to check if input - parameters are correct. - - Disable this option will speed up the process of some calculation a lot. + Support for parsing X.509 Certifificate Signing Requests + +endmenu # Certificates + +menuconfig MBEDTLS_ECP_C + bool "Elliptic Curve Ciphers" + default y + +config MBEDTLS_ECDH_C + bool "Elliptic Curve Diffie-Hellman (ECDH)" + depends on MBEDTLS_ECP_C + default y + help + Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. + +config MBEDTLS_ECDSA_C + bool "Elliptic Curve DSA" + depends on MBEDTLS_ECDH_C + default y + help + Enable ECDSA. Needed to use ECDSA-xxx TLS ciphersuites. + +config MBEDTLS_ECP_DP_SECP192R1_ENABLED + bool "Enable SECP192R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP192R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP224R1_ENABLED + bool "Enable SECP224R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP224R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP256R1_ENABLED + bool "Enable SECP256R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP256R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP384R1_ENABLED + bool "Enable SECP384R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP384R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP521R1_ENABLED + bool "Enable SECP521R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP521R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP192K1_ENABLED + bool "Enable SECP192K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP192K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP224K1_ENABLED + bool "Enable SECP224K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP224K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP256K1_ENABLED + bool "Enable SECP256K1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP256K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_BP256R1_ENABLED + bool "Enable BP256R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_BP384R1_ENABLED + bool "Enable BP384R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_BP512R1_ENABLED + bool "Enable BP512R1 curve" + depends on MBEDTLS_ECP_C + default y + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_CURVE25519_ENABLED + bool "Enable CURVE25519 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for CURVE25519 Elliptic Curve. + +config MBEDTLS_ECP_NIST_OPTIM + bool "NIST 'modulo p' optimisations" + depends on MBEDTLS_ECP_C + default y + help + NIST 'modulo p' optimisations increase Elliptic Curve operation performance. + + Disabling this option saves some code size. + + # end of Elliptic Curve options + +menu "Util" + + config util_assert + bool "Enable assert for util components" + default n + help + Enable this option, util components will use assert to check if input + parameters are correct. - config ESP_SHA - bool "Enable Espressif SHA" - default n - help - Enable Espressif SHA1, SHA256, SHA384 & SHA512 for other components to - save code size for ESP8285(ESP8266 + 1MB flash) users. + Disable this option will speed up the process of some calculation a lot. - Although this option is disable, bootloader will use it if booloader - is configured to use SHA256 to check hash. + config ESP_SHA + bool "Enable Espressif SHA" + default n + help + Enable Espressif SHA1, SHA256, SHA384 & SHA512 for other components to + save code size for ESP8285(ESP8266 + 1MB flash) users. - Disabling the "assert" function at menuconfig can speed up the calculation. + Although this option is disable, bootloader will use it if booloader + is configured to use SHA256 to check hash. - config ESP_AES - bool "Enable Espressif AES" - default y - help - Enable Espressif AES ECB, CBC, CFB128, CFB8 & CRT for other components to - speed up process speed and save code size. + Disabling the "assert" function at menuconfig can speed up the calculation. - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. + config ESP_AES + bool "Enable Espressif AES" + default y + help + Enable Espressif AES ECB, CBC, CFB128, CFB8 & CRT for other components to + speed up process speed and save code size. - The calculation uses "ibus_data" to speed up load data from instruction bus. + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. - Disabling the "assert" function at menuconfig can speed up the calculation. + The calculation uses "ibus_data" to speed up load data from instruction bus. - config ESP_MD5 - bool "Enable Espressif MD5" - default y - help - Enable Espressif MD5 for other components to - speed up process speed and save code size. + Disabling the "assert" function at menuconfig can speed up the calculation. - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. + config ESP_MD5 + bool "Enable Espressif MD5" + default y + help + Enable Espressif MD5 for other components to + speed up process speed and save code size. - The calculation uses "ibus_data" to speed up load data from instruction bus. + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. - Disabling the "assert" function at menuconfig can speed up the calculation. + The calculation uses "ibus_data" to speed up load data from instruction bus. - config ESP_ARC4 - bool "Enable Espressif ARC4" - default y - help - Enable Espressif ARC4 for other components to - speed up process speed and save code size. + Disabling the "assert" function at menuconfig can speed up the calculation. - ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU - core to flash is DIO not QIO, which makes it read flash data slower. - So the function will speed up ESP8285 obviously. + config ESP_ARC4 + bool "Enable Espressif ARC4" + default y + help + Enable Espressif ARC4 for other components to + speed up process speed and save code size. - The calculation uses "ibus_data" to speed up load data from instruction bus. + ESP8285 is like ESP8266 + 1MB flash, but its internal I/O connection from CPU + core to flash is DIO not QIO, which makes it read flash data slower. + So the function will speed up ESP8285 obviously. - Disabling the "assert" function at menuconfig can speed up the calculation. + The calculation uses "ibus_data" to speed up load data from instruction bus. - endmenu # Util + Disabling the "assert" function at menuconfig can speed up the calculation. +endmenu # Util -endmenu # mbedTLS diff --git a/components/mbedtls/mbedtls_v2/component.mk b/components/mbedtls/mbedtls_v2/component.mk index 541009de6..5bf42f7bf 100644 --- a/components/mbedtls/mbedtls_v2/component.mk +++ b/components/mbedtls/mbedtls_v2/component.mk @@ -2,13 +2,15 @@ # Component Makefile # -COMPONENT_ADD_INCLUDEDIRS := port/include mbedtls/include port/include/$(IDF_TARGET) +CURRENT_DIR := mbedtls_v2 -COMPONENT_SRCDIRS := mbedtls/library port port/$(IDF_TARGET) +COMPONENT_ADD_INCLUDEDIRS := $(CURRENT_DIR)/port/include $(CURRENT_DIR)/mbedtls/include $(CURRENT_DIR)/port/include/$(IDF_TARGET) -COMPONENT_OBJEXCLUDE := mbedtls/library/net_sockets.o +COMPONENT_SRCDIRS := $(CURRENT_DIR)/mbedtls/library $(CURRENT_DIR)/port $(CURRENT_DIR)/port/$(IDF_TARGET) -COMPONENT_SUBMODULES += mbedtls +COMPONENT_OBJEXCLUDE := $(CURRENT_DIR)/mbedtls/library/net_sockets.o + +COMPONENT_SUBMODULES += $(CURRENT_DIR)/mbedtls ifdef CONFIG_MBEDTLS_DYNAMIC_BUFFER @@ -26,6 +28,6 @@ WRAP_ARGUMENT := -Wl,--wrap= COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS)) -COMPONENT_SRCDIRS += port/dynamic +COMPONENT_SRCDIRS += $(CURRENT_DIR)/port/dynamic endif diff --git a/components/mbedtls/mbedtls_v3/CMakeLists.txt b/components/mbedtls/mbedtls_v3/CMakeLists.txt new file mode 100644 index 000000000..dbfc6dc24 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/CMakeLists.txt @@ -0,0 +1,328 @@ +if(CONFIG_MBEDTLS_V2) + return() +endif() + +idf_build_get_property(idf_target IDF_TARGET) +idf_build_get_property(python PYTHON) + +set(current_dir ${COMPONENT_DIR}/mbedtls_v3) + +set(mbedtls_srcs "") +set(mbedtls_include_dirs "mbedtls_v3/port/include" "mbedtls_v3/mbedtls/include" "mbedtls_v3/mbedtls/library") + +if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) + list(APPEND mbedtls_include_dirs "port/mbedtls_rom") +endif() + +if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) + list(APPEND mbedtls_srcs "mbedtls_v3/esp_crt_bundle/esp_crt_bundle.c") + list(APPEND mbedtls_include_dirs "mbedtls_v3/esp_crt_bundle/include") +endif() + +idf_component_register(SRCS "${mbedtls_srcs}" + INCLUDE_DIRS "${mbedtls_include_dirs}" + PRIV_REQUIRES "${priv_requires}" + ) + +# Determine the type of mbedtls component library +if(mbedtls_srcs STREQUAL "") + # For no sources in component library we must use "INTERFACE" + set(linkage_type INTERFACE) +else() + set(linkage_type PUBLIC) +endif() + + +if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) + set(bundle_name "x509_crt_bundle") + set(DEFAULT_CRT_DIR ${current_dir}/esp_crt_bundle) + + # Generate custom certificate bundle using the generate_cert_bundle utility + set(GENERATE_CERT_BUNDLEPY ${python} ${current_dir}/esp_crt_bundle/gen_crt_bundle.py) + + if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL) + list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem) + elseif(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN) + list(APPEND crt_paths ${DEFAULT_CRT_DIR}/cacrt_all.pem ${DEFAULT_CRT_DIR}/cacrt_local.pem) + list(APPEND args --filter ${DEFAULT_CRT_DIR}/cmn_crt_authorities.csv) + endif() + + if(CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE) + get_filename_component(custom_bundle_path + ${CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH} ABSOLUTE BASE_DIR "${project_dir}") + list(APPEND crt_paths ${custom_bundle_path}) + + endif() + list(APPEND args --input ${crt_paths} -q) + + get_filename_component(crt_bundle + ${bundle_name} + ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + + # Generate bundle according to config + add_custom_command(OUTPUT ${crt_bundle} + COMMAND ${GENERATE_CERT_BUNDLEPY} ${args} + DEPENDS ${custom_bundle_path} + VERBATIM) + + add_custom_target(custom_bundle DEPENDS ${cert_bundle}) + add_dependencies(${COMPONENT_LIB} custom_bundle) + + + target_add_binary_data(${COMPONENT_LIB} ${crt_bundle} BINARY) + set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + APPEND PROPERTY ADDITIONAL_CLEAN_FILES + "${crt_bundle}") +endif() + + +# Only build mbedtls libraries +set(ENABLE_TESTING CACHE BOOL OFF) +set(ENABLE_PROGRAMS CACHE BOOL OFF) + +# Use pre-generated source files in mbedtls repository +set(GEN_FILES CACHE BOOL OFF) + +# Make sure mbedtls finds the same Python interpreter as IDF uses +idf_build_get_property(python PYTHON) +set(Python3_EXECUTABLE ${python}) + +# Needed to for include_next includes to work from within mbedtls +set(include_dirs "${current_dir}/port/include") + +if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) + list(APPEND include_dirs "${current_dir}/esp_crt_bundle/include") +endif() + +include_directories(${include_dirs}) + +# Needed to for mbedtls_rom includes to work from within mbedtls +if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) + include_directories("${current_dir}/port/mbedtls_rom") +endif() + +# Import mbedtls library targets +add_subdirectory(mbedtls_v3/mbedtls) + +# Use port specific implementation of net_socket.c instead of one from mbedtls +get_target_property(src_tls mbedtls SOURCES) +list(REMOVE_ITEM src_tls net_sockets.c) +set_property(TARGET mbedtls PROPERTY SOURCES ${src_tls}) + +# Core libraries from the mbedTLS project +set(mbedtls_targets mbedtls mbedcrypto mbedx509) +# 3rd party libraries from the mbedTLS project +list(APPEND mbedtls_targets everest p256m) + +set(mbedtls_target_sources "${current_dir}/port/mbedtls_debug.c" + "${current_dir}/port/esp_platform_time.c" + "${current_dir}/port/net_sockets.c") + +if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) +set(mbedtls_target_sources ${mbedtls_target_sources} + "${current_dir}/port/dynamic/esp_mbedtls_dynamic_impl.c" + "${current_dir}/port/dynamic/esp_ssl_cli.c" + "${current_dir}/port/dynamic/esp_ssl_srv.c" + "${current_dir}/port/dynamic/esp_ssl_tls.c") +endif() + +if(${IDF_TARGET} STREQUAL "linux") +set(mbedtls_target_sources ${mbedtls_target_sources} "${current_dir}/port/net_sockets.c") +endif() + +# While updating to MbedTLS release/v3.4.0, building mbedtls/library/psa_crypto.c +# clang produces an unreachable-code warning. +if(CMAKE_C_COMPILER_ID MATCHES "Clang") + target_compile_options(mbedcrypto PRIVATE "-Wno-unreachable-code") +endif() + +# net_sockets.c should only be compiled if BSD socket functions are available. +# Do this by checking if lwip component is included into the build. +idf_build_get_property(build_components BUILD_COMPONENTS) +if(lwip IN_LIST build_components) + list(APPEND mbedtls_target_sources "${current_dir}/port/net_sockets.c") + idf_component_get_property(lwip_lib lwip COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} ${linkage_type} ${lwip_lib}) +endif() + +# Add port files to mbedtls targets +target_sources(mbedtls PRIVATE ${mbedtls_target_sources}) + +# Choose perihperal type + +if(CONFIG_SOC_SHA_SUPPORTED) + if(CONFIG_SOC_SHA_SUPPORT_DMA) + set(SHA_PERIPHERAL_TYPE "dma") + elseif(CONFIG_SOC_SHA_SUPPORT_PARALLEL_ENG) + set(SHA_PERIPHERAL_TYPE "parallel_engine") + else() + set(SHA_PERIPHERAL_TYPE "block") + endif() +endif() + +if(CONFIG_SOC_AES_SUPPORTED) + if(CONFIG_SOC_AES_SUPPORT_DMA) + set(AES_PERIPHERAL_TYPE "dma") + else() + set(AES_PERIPHERAL_TYPE "block") + endif() +endif() + +if(SHA_PERIPHERAL_TYPE STREQUAL "dma") + target_include_directories(mbedcrypto PRIVATE "${current_dir}/port/sha/dma/include") + + if(NOT CONFIG_SOC_SHA_GDMA) + set(SHA_DMA_SRCS "${current_dir}/port/sha/dma/esp_sha_crypto_dma_impl.c") + else() + set(SHA_DMA_SRCS "${current_dir}/port/sha/dma/esp_sha_gdma_impl.c") + + endif() + target_sources(mbedcrypto PRIVATE "${SHA_DMA_SRCS}") +endif() + +if(AES_PERIPHERAL_TYPE STREQUAL "dma") + if(NOT CONFIG_SOC_AES_GDMA) + set(AES_DMA_SRCS "${current_dir}/port/aes/dma/esp_aes_crypto_dma_impl.c") + else() + set(AES_DMA_SRCS "${current_dir}/port/aes/dma/esp_aes_gdma_impl.c" + "${current_dir}/port/crypto_shared_gdma/esp_crypto_shared_gdma.c") + endif() + + target_include_directories(mbedcrypto PRIVATE "${current_dir}/port/aes/dma/include") + target_sources(mbedcrypto PRIVATE "${AES_DMA_SRCS}") +endif() + +target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_mem.c" + "${current_dir}/port/esp_timing.c" + "${current_dir}/port/esp_hardware.c") + +if(CONFIG_SOC_AES_SUPPORTED) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/aes/esp_aes_xts.c" + "${current_dir}/port/aes/esp_aes_common.c" + "${current_dir}/port/aes/${AES_PERIPHERAL_TYPE}/esp_aes.c" + ) +endif() + +if(CONFIG_SOC_SHA_SUPPORTED) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/sha/esp_sha.c" + "${current_dir}/port/sha/${SHA_PERIPHERAL_TYPE}/sha.c" + ) +endif() + +# CONFIG_ESP_TLS_USE_DS_PERIPHERAL can be enabled only for the supported targets. +if(CONFIG_ESP_TLS_USE_DS_PERIPHERAL) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_ds/esp_rsa_sign_alt.c") +endif() + +# Note: some mbedTLS hardware acceleration can be enabled/disabled by config. +# +# We don't need to filter aes.c as this uses a different prefix (esp_aes_x) and the +# config option only changes the prefixes in the header so mbedtls_aes_x compiles to esp_aes_x +# +# The other port-specific files don't override internal mbedTLS functions, they just add new functions. + +if(CONFIG_MBEDTLS_HARDWARE_MPI) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_bignum.c" + "${current_dir}/port/${idf_target}/bignum.c" + ) +endif() + +if(CONFIG_MBEDTLS_HARDWARE_SHA) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/sha/${SHA_PERIPHERAL_TYPE}/esp_sha1.c" + "${current_dir}/port/sha/${SHA_PERIPHERAL_TYPE}/esp_sha256.c" + "${current_dir}/port/sha/${SHA_PERIPHERAL_TYPE}/esp_sha512.c" + ) +endif() + +if(CONFIG_MBEDTLS_HARDWARE_GCM OR CONFIG_MBEDTLS_HARDWARE_AES) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/aes/esp_aes_gcm.c") +endif() + +if(CONFIG_MBEDTLS_HARDWARE_ECC) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/ecc/esp_ecc.c" + "${current_dir}/port/ecc/ecc_alt.c") +endif() + +if(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN OR CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/ecdsa/ecdsa_alt.c") + + if(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_sign") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_sign_restartable") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_write_signature") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_write_signature_restartable") + endif() + + if(CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_verify") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_verify_restartable") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_read_signature") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mbedtls_ecdsa_read_signature_restartable") + endif() +endif() + +if(CONFIG_MBEDTLS_ROM_MD5) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/md/esp_md.c") +endif() + +if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/mbedtls_rom/mbedtls_rom_osi.c") + target_link_libraries(${COMPONENT_LIB} PRIVATE "-u mbedtls_rom_osi_functions_init") +endif() + +foreach(target ${mbedtls_targets}) + target_compile_definitions(${target} PUBLIC -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h") +endforeach() + +if(CONFIG_MBEDTLS_DYNAMIC_BUFFER) + set(WRAP_FUNCTIONS + mbedtls_ssl_write_client_hello + mbedtls_ssl_handshake_client_step + mbedtls_ssl_handshake_server_step + mbedtls_ssl_read + mbedtls_ssl_write + mbedtls_ssl_session_reset + mbedtls_ssl_free + mbedtls_ssl_setup + mbedtls_ssl_send_alert_message + mbedtls_ssl_close_notify) + + foreach(wrap ${WRAP_FUNCTIONS}) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") + endforeach() +endif() + +set_property(TARGET mbedcrypto APPEND PROPERTY LINK_INTERFACE_LIBRARIES mbedtls) + +# if(CONFIG_PM_ENABLE) +# target_link_libraries(mbedcrypto PRIVATE idf::esp_pm) +# endif() + +if(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN OR CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY) + target_link_libraries(mbedcrypto PRIVATE idf::efuse) +endif() + +target_link_libraries(${COMPONENT_LIB} ${linkage_type} ${mbedtls_targets}) + +if(CONFIG_ESP_TLS_USE_DS_PERIPHERAL) + # The linker seems to be unable to resolve all the dependencies without increasing this + set_property(TARGET mbedcrypto APPEND PROPERTY LINK_INTERFACE_MULTIPLICITY 6) +endif() + +# Additional optional dependencies for the mbedcrypto library +function(mbedcrypto_optional_deps component_name) + idf_build_get_property(components BUILD_COMPONENTS) + if(${component_name} IN_LIST components) + idf_component_get_property(lib_name ${component_name} COMPONENT_LIB) + target_link_libraries(mbedcrypto PRIVATE ${lib_name}) + endif() +endfunction() + +if(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM) + mbedcrypto_optional_deps(esp_timer idf::esp_timer) +endif() + +# Link esp-cryptoauthlib to mbedtls +if(CONFIG_ATCA_MBEDTLS_ECDSA) + idf_component_optional_requires(PRIVATE espressif__esp-cryptoauthlib esp-cryptoauthlib) +endif() diff --git a/components/mbedtls/mbedtls_v3/Kconfig b/components/mbedtls/mbedtls_v3/Kconfig new file mode 100644 index 000000000..961cc4707 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/Kconfig @@ -0,0 +1,1150 @@ +choice MBEDTLS_MEM_ALLOC_MODE + prompt "Memory allocation strategy" + default MBEDTLS_INTERNAL_MEM_ALLOC + help + Allocation strategy for mbedTLS, essentially provides ability to + allocate all required dynamic allocations from, + + - Internal DRAM memory only + - External SPIRAM memory only + - Either internal or external memory based on default malloc() + behavior in ESP-IDF + - Custom allocation mode, by overwriting calloc()/free() using + mbedtls_platform_set_calloc_free() function + - Internal IRAM memory wherever applicable else internal DRAM + + Recommended mode here is always internal (*), since that is most preferred + from security perspective. But if application requirement does not + allow sufficient free internal memory then alternate mode can be + selected. + + (*) In case of ESP32-S2/ESP32-S3, hardware allows encryption of external + SPIRAM contents provided hardware flash encryption feature is enabled. + In that case, using external SPIRAM allocation strategy is also safe choice + from security perspective. + + config MBEDTLS_INTERNAL_MEM_ALLOC + bool "Internal memory" + + config MBEDTLS_EXTERNAL_MEM_ALLOC + bool "External SPIRAM" + depends on SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC + + config MBEDTLS_DEFAULT_MEM_ALLOC + bool "Default alloc mode" + + config MBEDTLS_CUSTOM_MEM_ALLOC + bool "Custom alloc mode" + + config MBEDTLS_IRAM_8BIT_MEM_ALLOC + bool "Internal IRAM" + depends on ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY + help + Allows to use IRAM memory region as 8bit accessible region. + + TLS input and output buffers will be allocated in IRAM section which is 32bit aligned + memory. Every unaligned (8bit or 16bit) access will result in an exception + and incur penalty of certain clock cycles per unaligned read/write. + +endchoice #MBEDTLS_MEM_ALLOC_MODE + +config MBEDTLS_SSL_MAX_CONTENT_LEN + int "TLS maximum message content length" + default 16384 + range 512 16384 + depends on !MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + Maximum TLS message length (in bytes) supported by mbedTLS. + + 16384 is the default and this value is required to comply + fully with TLS standards. + + However you can set a lower value in order to save RAM. This + is safe if the other end of the connection supports Maximum + Fragment Length Negotiation Extension (max_fragment_length, + see RFC6066) or you know for certain that it will never send a + message longer than a certain number of bytes. + + If the value is set too low, symptoms are a failed TLS + handshake or a return value of MBEDTLS_ERR_SSL_INVALID_RECORD + (-0x7200). + +config MBEDTLS_ASYMMETRIC_CONTENT_LEN + bool "Asymmetric in/out fragment length" + default y + help + If enabled, this option allows customizing TLS in/out fragment length + in asymmetric way. Please note that enabling this with default values + saves 12KB of dynamic memory per TLS connection. + +config MBEDTLS_SSL_IN_CONTENT_LEN + int "TLS maximum incoming fragment length" + default 16384 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum incoming fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + +config MBEDTLS_SSL_OUT_CONTENT_LEN + int "TLS maximum outgoing fragment length" + default 4096 + range 512 16384 + depends on MBEDTLS_ASYMMETRIC_CONTENT_LEN + help + This defines maximum outgoing fragment length, overriding default + maximum content length (MBEDTLS_SSL_MAX_CONTENT_LEN). + +config MBEDTLS_DYNAMIC_BUFFER + bool "Using dynamic TX/RX buffer" + default n + select MBEDTLS_ASYMMETRIC_CONTENT_LEN + # Dynamic buffer feature is not supported with DTLS + depends on !IDF_TARGET_LINUX && !MBEDTLS_SSL_PROTO_DTLS && !MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH + help + Using dynamic TX/RX buffer. After enabling this option, mbedTLS will + allocate TX buffer when need to send data and then free it if all data + is sent, allocate RX buffer when need to receive data and then free it + when all data is used or read by upper layer. + + By default, when SSL is initialized, mbedTLS also allocate TX and + RX buffer with the default value of "MBEDTLS_SSL_OUT_CONTENT_LEN" or + "MBEDTLS_SSL_IN_CONTENT_LEN", so to save more heap, users can set + the options to be an appropriate value. + +config MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + bool "Free private key and DHM data after its usage" + default n + depends on MBEDTLS_DYNAMIC_BUFFER + help + Free private key and DHM data after its usage in handshake process. + + The option will decrease heap cost when handshake, but also lead to problem: + + Because all certificate, private key and DHM data are freed so users should register + certificate and private key to ssl config object again. + +config MBEDTLS_DYNAMIC_FREE_CA_CERT + bool "Free SSL CA certificate after its usage" + default y + depends on MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + help + Free CA certificate after its usage in the handshake process. + This option will decrease the heap footprint for the TLS handshake, but may lead to a problem: + If the respective ssl object needs to perform the TLS handshake again, + the CA certificate should once again be registered to the ssl object. + +config MBEDTLS_DEBUG + bool "Enable mbedTLS debugging" + default n + help + Enable mbedTLS debugging functions at compile time. + + If this option is enabled, you can include + "mbedtls/esp_debug.h" and call mbedtls_esp_enable_debug_log() + at runtime in order to enable mbedTLS debug output via the ESP + log mechanism. + +choice MBEDTLS_DEBUG_LEVEL + bool "Set mbedTLS debugging level" + depends on MBEDTLS_DEBUG + default MBEDTLS_DEBUG_LEVEL_VERBOSE + help + Set mbedTLS debugging level + + config MBEDTLS_DEBUG_LEVEL_WARN + bool "Warning" + config MBEDTLS_DEBUG_LEVEL_INFO + bool "Info" + config MBEDTLS_DEBUG_LEVEL_DEBUG + bool "Debug" + config MBEDTLS_DEBUG_LEVEL_VERBOSE + bool "Verbose" +endchoice + +config MBEDTLS_DEBUG_LEVEL + int + default 1 if MBEDTLS_DEBUG_LEVEL_WARN + default 2 if MBEDTLS_DEBUG_LEVEL_INFO + default 3 if MBEDTLS_DEBUG_LEVEL_DEBUG + default 4 if MBEDTLS_DEBUG_LEVEL_VERBOSE + +menu "mbedTLS v3.x related" + # NOTE: MBEDTLS_DYNAMIC_BUFFER feature is not supported with TLS 1.3 yet. Ref: IDF-4762 + config MBEDTLS_SSL_PROTO_TLS1_3 + bool "Support TLS 1.3 protocol" + depends on MBEDTLS_TLS_ENABLED && MBEDTLS_SSL_KEEP_PEER_CERTIFICATE && !MBEDTLS_DYNAMIC_BUFFER + select MBEDTLS_HKDF_C + default n + + menu "TLS 1.3 related configurations" + depends on MBEDTLS_SSL_PROTO_TLS1_3 + + config MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE + bool "TLS 1.3 middlebox compatibility mode" + default y + + config MBEDTLS_SSL_TLS1_3_KEXM_PSK + bool "TLS 1.3 PSK key exchange mode" + default y + + config MBEDTLS_SSL_TLS1_3_KEXM_EPHEMERAL + bool "TLS 1.3 ephemeral key exchange mode" + default y + + config MBEDTLS_SSL_TLS1_3_KEXM_PSK_EPHEMERAL + bool "TLS 1.3 PSK ephemeral key exchange mode" + default y + + endmenu + + config MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH + bool "Variable SSL buffer length" + default n + help + This enables the SSL buffer to be resized automatically + based on the negotiated maximum fragment length in each direction. + + config MBEDTLS_ECDH_LEGACY_CONTEXT + bool "Use a backward compatible ECDH context (Experimental)" + default n + depends on MBEDTLS_ECDH_C && MBEDTLS_ECP_RESTARTABLE + help + Use the legacy ECDH context format. + Define this option only if you enable MBEDTLS_ECP_RESTARTABLE or if you + want to access ECDH context fields directly. + + config MBEDTLS_X509_TRUSTED_CERT_CALLBACK + bool "Enable trusted certificate callbacks" + default n + help + Enables users to configure the set of trusted certificates + through a callback instead of a linked list. + + See mbedTLS documentation for required API and more details. + + config MBEDTLS_SSL_CONTEXT_SERIALIZATION + bool "Enable serialization of the TLS context structures" + default n + depends on MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C + help + Enable serialization of the TLS context structures + This is a local optimization in handling a single, potentially long-lived connection. + + See mbedTLS documentation for required API and more details. + Disabling this option will save some code size. + + config MBEDTLS_SSL_KEEP_PEER_CERTIFICATE + bool "Keep peer certificate after handshake completion" + default y + help + Keep the peer's certificate after completion of the handshake. + Disabling this option will save about 4kB of heap and some code size. + + See mbedTLS documentation for required API and more details. + + config MBEDTLS_PKCS7_C + bool "Enable PKCS #7" + default y + depends on MBEDTLS_X509_CRL_PARSE_C + help + Enable PKCS #7 core for using PKCS #7-formatted signatures. + + config MBEDTLS_SSL_CID_PADDING_GRANULARITY + int "Record plaintext padding" + default 16 + range 0 32 + depends on MBEDTLS_SSL_PROTO_TLS1_3 || MBEDTLS_SSL_DTLS_CONNECTION_ID + help + Controls the use of record plaintext padding in TLS 1.3 and + when using the Connection ID extension in DTLS 1.2. + + The padding will always be chosen so that the length of the + padded plaintext is a multiple of the value of this option. + + Notes: + A value of 1 means that no padding will be used for outgoing records. + On systems lacking division instructions, a power of two should be preferred. + + menu "DTLS-based configurations" + depends on MBEDTLS_SSL_PROTO_DTLS + + config MBEDTLS_SSL_DTLS_CONNECTION_ID + bool "Support for the DTLS Connection ID extension" + default n + help + Enable support for the DTLS Connection ID extension which allows to + identify DTLS connections across changes in the underlying transport. + + config MBEDTLS_SSL_CID_IN_LEN_MAX + int "Maximum length of CIDs used for incoming DTLS messages" + default 32 + range 0 32 + depends on MBEDTLS_SSL_DTLS_CONNECTION_ID + help + Maximum length of CIDs used for incoming DTLS messages + + config MBEDTLS_SSL_CID_OUT_LEN_MAX + int "Maximum length of CIDs used for outgoing DTLS messages" + default 32 + range 0 32 + depends on MBEDTLS_SSL_DTLS_CONNECTION_ID + help + Maximum length of CIDs used for outgoing DTLS messages + + config MBEDTLS_SSL_DTLS_SRTP + bool "Enable support for negotiation of DTLS-SRTP (RFC 5764)" + default n + help + Enable support for negotiation of DTLS-SRTP (RFC 5764) through the use_srtp extension. + + See mbedTLS documentation for required API and more details. + Disabling this option will save some code size. + + endmenu + +endmenu + +menu "Certificate Bundle" + depends on !IDF_TARGET_ESP8266 + config MBEDTLS_CERTIFICATE_BUNDLE + bool "Enable trusted root certificate bundle" + default y + help + Enable support for large number of default root certificates + + When enabled this option allows user to store default as well + as customer specific root certificates in compressed format rather + than storing full certificate. For the root certificates the public key and the subject name + will be stored. + + choice MBEDTLS_DEFAULT_CERTIFICATE_BUNDLE + bool "Default certificate bundle options" + depends on MBEDTLS_CERTIFICATE_BUNDLE + default MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL + + config MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL + bool "Use the full default certificate bundle" + config MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN + bool "Use only the most common certificates from the default bundles" + help + Use only the most common certificates from the default bundles, reducing the size with 50%, + while still having around 99% coverage. + config MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE + bool "Do not use the default certificate bundle" + endchoice + + config MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE + depends on MBEDTLS_CERTIFICATE_BUNDLE + default n + bool "Add custom certificates to the default bundle" + config MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH + depends on MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE + string "Custom certificate bundle path" + help + Name of the custom certificate directory or file. This path is evaluated + relative to the project root directory. + + config MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS + int "Maximum no of certificates allowed in certificate bundle" + default 200 + depends on MBEDTLS_CERTIFICATE_BUNDLE + +endmenu + +config MBEDTLS_ECP_RESTARTABLE + bool "Enable mbedTLS ecp restartable" + select MBEDTLS_ECDH_LEGACY_CONTEXT + depends on MBEDTLS_ECP_C + default n + help + Enable "non-blocking" ECC operations that can return early and be resumed. + +config MBEDTLS_CMAC_C + bool "Enable CMAC mode for block ciphers" + default n + depends on MBEDTLS_AES_C || MBEDTLS_DES_C + help + Enable the CMAC (Cipher-based Message Authentication Code) mode for + block ciphers. + +config MBEDTLS_HARDWARE_AES + bool "Enable hardware AES acceleration" + default y + depends on !SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST && SOC_AES_SUPPORTED + help + Enable hardware accelerated AES encryption & decryption. + + Note that if the ESP32 CPU is running at 240MHz, hardware AES does not + offer any speed boost over software AES. + +config MBEDTLS_AES_USE_INTERRUPT + bool "Use interrupt for long AES operations" + depends on !IDF_TARGET_ESP32 && MBEDTLS_HARDWARE_AES + default y + help + Use an interrupt to coordinate long AES operations. + + This allows other code to run on the CPU while an AES operation is pending. + Otherwise the CPU busy-waits. + +config MBEDTLS_HARDWARE_GCM + bool "Enable partially hardware accelerated GCM" + depends on SOC_AES_SUPPORT_GCM && MBEDTLS_HARDWARE_AES + default y + help + Enable partially hardware accelerated GCM. GHASH calculation is still done + in software. + + If MBEDTLS_HARDWARE_GCM is disabled and MBEDTLS_HARDWARE_AES is enabled then + mbedTLS will still use the hardware accelerated AES block operation, but + on a single block at a time. + +config MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER + bool "Enable support for non-AES ciphers in GCM operation" + depends on MBEDTLS_HARDWARE_AES + default n + help + Enable this config to support fallback to software definitions for a non-AES + cipher GCM operation as we support hardware acceleration only for AES cipher. + Some of the non-AES ciphers used in a GCM operation are DES, ARIA, CAMELLIA, + CHACHA20, BLOWFISH. + + If this config is disabled, performing a non-AES cipher GCM operation with + the config MBEDTLS_HARDWARE_AES enabled will result in calculation of an + AES-GCM operation instead for the given input values and thus could lead + to failure in certificate validation which would ultimately lead to a SSL + handshake failure. + + This config being by-default enabled leads to an increase in binary size + footprint of ~2.5KB. + In case you are sure that your use case (for example, client and server + configurations in case of a TLS handshake) would not involve any GCM + operations using a non-AES cipher, you can safely disable this config, + leading to reduction in binary size footprint. + +config MBEDTLS_HARDWARE_MPI + bool "Enable hardware MPI (bignum) acceleration" + default y + depends on !SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST && SOC_MPI_SUPPORTED + help + Enable hardware accelerated multiple precision integer operations. + + Hardware accelerated multiplication, modulo multiplication, + and modular exponentiation for up to SOC_RSA_MAX_BIT_LEN bit results. + + These operations are used by RSA. + +config MBEDTLS_LARGE_KEY_SOFTWARE_MPI + bool "Fallback to software implementation for larger MPI values" + depends on MBEDTLS_HARDWARE_MPI + default y if SOC_RSA_MAX_BIT_LEN <= 3072 # HW max 3072 bits + default n + help + Fallback to software implementation for RSA key lengths + larger than SOC_RSA_MAX_BIT_LEN. If this is not active + then the ESP will be unable to process keys greater + than SOC_RSA_MAX_BIT_LEN. + +config MBEDTLS_MPI_USE_INTERRUPT + bool "Use interrupt for MPI exp-mod operations" + depends on !IDF_TARGET_ESP32 && MBEDTLS_HARDWARE_MPI + default y + help + Use an interrupt to coordinate long MPI operations. + + This allows other code to run on the CPU while an MPI operation is pending. + Otherwise the CPU busy-waits. + +config MBEDTLS_HARDWARE_SHA + bool "Enable hardware SHA acceleration" + default y + depends on !SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST && SOC_SHA_SUPPORTED + help + Enable hardware accelerated SHA1, SHA256, SHA384 & SHA512 in mbedTLS. + + Due to a hardware limitation, on the ESP32 hardware acceleration is only + guaranteed if SHA digests are calculated one at a time. If more + than one SHA digest is calculated at the same time, one will + be calculated fully in hardware and the rest will be calculated + (at least partially calculated) in software. This happens automatically. + + SHA hardware acceleration is faster than software in some situations but + slower in others. You should benchmark to find the best setting for you. + +config MBEDTLS_HARDWARE_ECC + bool "Enable hardware ECC acceleration" + default y + depends on SOC_ECC_SUPPORTED + help + Enable hardware accelerated ECC point multiplication and point verification for points + on curve SECP192R1 and SECP256R1 in mbedTLS + +config MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK + bool "Fallback to software implementation for curves not supported in hardware" + depends on MBEDTLS_HARDWARE_ECC + default y + help + Fallback to software implementation of ECC point multiplication and point verification + for curves not supported in hardware. + +config MBEDTLS_ROM_MD5 + depends on !IDF_TARGET_ESP8266 + bool "Use MD5 implementation in ROM" + default y + help + Use ROM MD5 in mbedTLS. + +config MBEDTLS_HARDWARE_ECDSA_SIGN + bool "Enable ECDSA signing using on-chip ECDSA peripheral" + default n + depends on SOC_ECDSA_SUPPORTED + help + Enable hardware accelerated ECDSA peripheral to sign data + on curve SECP192R1 and SECP256R1 in mbedTLS. + + Note that for signing, the private key has to be burnt in an efuse key block + with key purpose set to ECDSA_KEY. + If no key is burnt, it will report an error + + The key should be burnt in little endian format. espefuse.py utility handles it internally + but care needs to be taken while burning using esp_efuse APIs + +menu "Enable Software Countermeasure for ECDSA signing using on-chip ECDSA peripheral" + depends on MBEDTLS_HARDWARE_ECDSA_SIGN + depends on IDF_TARGET_ESP32H2 + config MBEDTLS_HARDWARE_ECDSA_SIGN_MASKING_CM + bool "Mask original ECDSA sign operation under dummy sign operations" + select HAL_ECDSA_GEN_SIG_CM + # ToDo: IDF-11051 + default y + help + The ECDSA peripheral before ECO5 does not offer constant time ECDSA sign operation. + This time can be observed through power profiling of the device, + making the ECDSA private key vulnerable to side-channel timing attacks. + This countermeasure masks the real ECDSA sign operation + under dummy sign operations to add randomness in the generated power signature. + It is highly recommended to also enable Secure Boot for the device in addition to this countermeasure + so that only trusted software can execute on the device. + + config MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM + bool "Make ECDSA signature operation pseudo constant time for software" + default y + help + This option adds a delay after the actual ECDSA signature operation + so that the entire operation appears to be constant time for the software. + This fix helps in protecting the device only in case of remote timing attack on the ECDSA private key. + For e.g., When an interface is exposed by the device to perform ECDSA signature + of an arbitrary message. + The signature time would appear to be constant to the external entity after enabling + this option. + +endmenu + +config MBEDTLS_HARDWARE_ECDSA_VERIFY + bool "Enable ECDSA signature verification using on-chip ECDSA peripheral" + default y + depends on SOC_ECDSA_SUPPORTED + help + Enable hardware accelerated ECDSA peripheral to verify signature + on curve SECP192R1 and SECP256R1 in mbedTLS. + +config MBEDTLS_ATCA_HW_ECDSA_SIGN + depends on !IDF_TARGET_ESP8266 + bool "Enable hardware ECDSA sign acceleration when using ATECC608A" + default n + help + This option enables hardware acceleration for ECDSA sign function, only + when using ATECC608A cryptoauth chip (integrated with ESP32-WROOM-32SE) + +config MBEDTLS_ATCA_HW_ECDSA_VERIFY + depends on !IDF_TARGET_ESP8266 + bool "Enable hardware ECDSA verify acceleration when using ATECC608A" + default n + help + This option enables hardware acceleration for ECDSA sign function, only + when using ATECC608A cryptoauth chip (integrated with ESP32-WROOM-32SE) + +config MBEDTLS_HAVE_TIME + bool "Enable mbedtls time support" + depends on !ESP_TIME_FUNCS_USE_NONE + default y + help + Enable use of time.h functions (time() and gmtime()) by mbedTLS. + + This option doesn't require the system time to be correct, but enables + functionality that requires relative timekeeping - for example periodic + expiry of TLS session tickets or session cache entries. + + Disabling this option will save some firmware size, particularly if + the rest of the firmware doesn't call any standard timekeeeping + functions. + +config MBEDTLS_PLATFORM_TIME_ALT + bool "Enable mbedtls time support: platform-specific" + depends on MBEDTLS_HAVE_TIME + default n + help + Enabling this config will provide users with a function + "mbedtls_platform_set_time()" that allows to set an alternative + time function pointer. + +config MBEDTLS_HAVE_TIME_DATE + bool "Enable mbedtls certificate expiry check" + depends on MBEDTLS_HAVE_TIME + default n + help + Enables X.509 certificate expiry checks in mbedTLS. + + If this option is disabled (default) then X.509 certificate + "valid from" and "valid to" timestamp fields are ignored. + + If this option is enabled, these fields are compared with the + current system date and time. The time is retrieved using the + standard time() and gmtime() functions. If the certificate is not + valid for the current system time then verification will fail with + code MBEDTLS_X509_BADCERT_FUTURE or MBEDTLS_X509_BADCERT_EXPIRED. + + Enabling this option requires adding functionality in the firmware + to set the system clock to a valid timestamp before using TLS. The + recommended way to do this is via ESP-IDF's SNTP functionality, but + any method can be used. + + In the case where only a small number of certificates are trusted by + the device, please carefully consider the tradeoffs of enabling this + option. There may be undesired consequences, for example if all + trusted certificates expire while the device is offline and a TLS + connection is required to update. Or if an issue with the SNTP + server means that the system time is invalid for an extended period + after a reset. + +config MBEDTLS_ECDSA_DETERMINISTIC + bool "Enable deterministic ECDSA" + default y + help + Standard ECDSA is "fragile" in the sense that lack of entropy when signing + may result in a compromise of the long-term signing key. + +config MBEDTLS_SHA512_C + bool "Enable the SHA-384 and SHA-512 cryptographic hash algorithms" + default y + help + Enable MBEDTLS_SHA512_C adds support for SHA-384 and SHA-512. + +config MBEDTLS_SHA3_C + bool "Enable the SHA3 cryptographic hash algorithm" + default n + help + Enabling MBEDTLS_SHA3_C adds support for SHA3. + Enabling this configuration option increases the flash footprint + by almost 4KB. + +choice MBEDTLS_TLS_MODE + bool "TLS Protocol Role" + default MBEDTLS_TLS_SERVER_AND_CLIENT + help + mbedTLS can be compiled with protocol support for the TLS + server, TLS client, or both server and client. + + Reducing the number of TLS roles supported saves code size. + + config MBEDTLS_TLS_SERVER_AND_CLIENT + bool "Server & Client" + select MBEDTLS_TLS_SERVER + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_SERVER_ONLY + bool "Server" + select MBEDTLS_TLS_SERVER + config MBEDTLS_TLS_CLIENT_ONLY + bool "Client" + select MBEDTLS_TLS_CLIENT + config MBEDTLS_TLS_DISABLED + bool "None" + +endchoice + +config MBEDTLS_TLS_SERVER + bool + select MBEDTLS_TLS_ENABLED +config MBEDTLS_TLS_CLIENT + bool + select MBEDTLS_TLS_ENABLED +config MBEDTLS_TLS_ENABLED + bool + +menu "TLS Key Exchange Methods" + depends on MBEDTLS_TLS_ENABLED + + config MBEDTLS_PSK_MODES + bool "Enable pre-shared-key ciphersuites" + default n + help + Enable to show configuration for different types of pre-shared-key TLS authentatication methods. + + Leaving this options disabled will save code size if they are not used. + + config MBEDTLS_KEY_EXCHANGE_PSK + bool "Enable PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default n + help + Enable to support symmetric key PSK (pre-shared-key) TLS key exchange modes. + + config MBEDTLS_KEY_EXCHANGE_DHE_PSK + bool "Enable DHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES && MBEDTLS_DHM_C + default y + help + Enable to support Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_ECDHE_PSK + bool "Enable ECDHE-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES && MBEDTLS_ECDH_C + default y + help + Enable to support Elliptic-Curve-Diffie-Hellman PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_RSA_PSK + bool "Enable RSA-PSK based ciphersuite modes" + depends on MBEDTLS_PSK_MODES + default y + help + Enable to support RSA PSK (pre-shared-key) TLS authentication modes. + + config MBEDTLS_KEY_EXCHANGE_RSA + bool "Enable RSA-only based ciphersuite modes" + default y + help + Enable to support ciphersuites with prefix TLS-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_DHE_RSA + bool "Enable DHE-RSA based ciphersuite modes" + default y + depends on MBEDTLS_DHM_C + help + Enable to support ciphersuites with prefix TLS-DHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE + bool "Support Elliptic Curve based ciphersuites" + depends on MBEDTLS_ECP_C + default y + help + Enable to show Elliptic Curve based ciphersuite mode options. + + Disabling all Elliptic Curve ciphersuites saves code size and + can give slightly faster TLS handshakes, provided the server supports + RSA-only ciphersuite modes. + + config MBEDTLS_KEY_EXCHANGE_ECDHE_RSA + bool "Enable ECDHE-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA + bool "Enable ECDHE-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA + bool "Enable ECDH-ECDSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C && MBEDTLS_ECDSA_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECDH_RSA + bool "Enable ECDH-RSA based ciphersuite modes" + depends on MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE && MBEDTLS_ECDH_C + default y + help + Enable to support ciphersuites with prefix TLS-ECDHE-RSA-WITH- + + config MBEDTLS_KEY_EXCHANGE_ECJPAKE + bool "Enable ECJPAKE based ciphersuite modes" + depends on MBEDTLS_ECJPAKE_C && MBEDTLS_ECP_DP_SECP256R1_ENABLED + default n + help + Enable to support ciphersuites with prefix TLS-ECJPAKE-WITH- + +endmenu # TLS key exchange modes + +config MBEDTLS_SSL_RENEGOTIATION + bool "Support TLS renegotiation" + depends on MBEDTLS_TLS_ENABLED + default y + help + The two main uses of renegotiation are (1) refresh keys on long-lived + connections and (2) client authentication after the initial handshake. + If you don't need renegotiation, disabling it will save code size and + reduce the possibility of abuse/vulnerability. + +config MBEDTLS_SSL_PROTO_TLS1_2 + bool "Support TLS 1.2 protocol" + depends on MBEDTLS_TLS_ENABLED + default y + +config MBEDTLS_SSL_PROTO_GMTSSL1_1 + bool "Support GM/T SSL 1.1 protocol" + depends on MBEDTLS_TLS_ENABLED + default n + help + Provisions for GM/T SSL 1.1 support + +config MBEDTLS_SSL_PROTO_DTLS + bool "Support DTLS protocol (all versions)" + default n + depends on MBEDTLS_SSL_PROTO_TLS1_2 + help + Requires TLS 1.2 to be enabled for DTLS 1.2 + +config MBEDTLS_SSL_ALPN + bool "Support ALPN (Application Layer Protocol Negotiation)" + depends on MBEDTLS_TLS_ENABLED + default y + help + Disabling this option will save some code size if it is not needed. + +config MBEDTLS_CLIENT_SSL_SESSION_TICKETS + bool "TLS: Client Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED + help + Client support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + +config MBEDTLS_SERVER_SSL_SESSION_TICKETS + bool "TLS: Server Support for RFC 5077 SSL session tickets" + default y + depends on MBEDTLS_TLS_ENABLED && (MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C) + help + Server support for RFC 5077 session tickets. See mbedTLS documentation for more details. + Disabling this option will save some code size. + +menu "Symmetric Ciphers" + + config MBEDTLS_AES_C + bool "AES block cipher" + default y + + config MBEDTLS_CAMELLIA_C + bool "Camellia block cipher" + default n + + config MBEDTLS_DES_C + bool "DES block cipher (legacy, insecure)" + default n + help + Enables the DES block cipher to support 3DES-based TLS ciphersuites. + + 3DES is vulnerable to the Sweet32 attack and should only be enabled + if absolutely necessary. + + config MBEDTLS_BLOWFISH_C + bool "Blowfish block cipher (read help)" + default n + help + Enables the Blowfish block cipher (not used for TLS sessions.) + + The Blowfish cipher is not used for mbedTLS TLS sessions but can be + used for other purposes. Read up on the limitations of Blowfish (including + Sweet32) before enabling. + + config MBEDTLS_XTEA_C + bool "XTEA block cipher" + default n + help + Enables the XTEA block cipher. + + + config MBEDTLS_CCM_C + bool "CCM (Counter with CBC-MAC) block cipher modes" + default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C + help + Enable Counter with CBC-MAC (CCM) modes for AES and/or Camellia ciphers. + + Disabling this option saves some code size. + + config MBEDTLS_GCM_C + bool "GCM (Galois/Counter) block cipher modes" + default y + depends on MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C + help + Enable Galois/Counter Mode for AES and/or Camellia ciphers. + + This option is generally faster than CCM. + + config MBEDTLS_NIST_KW_C + bool "NIST key wrapping (KW) and KW padding (KWP)" + default n + depends on MBEDTLS_AES_C + help + Enable NIST key wrapping and key wrapping padding. + +endmenu # Symmetric Ciphers + +config MBEDTLS_RIPEMD160_C + bool "Enable RIPEMD-160 hash algorithm" + default n + help + Enable the RIPEMD-160 hash algorithm. + +menu "Certificates" + + config MBEDTLS_PEM_PARSE_C + bool "Read & Parse PEM formatted certificates" + default y + help + Enable decoding/parsing of PEM formatted certificates. + + If your certificates are all in the simpler DER format, disabling + this option will save some code size. + + config MBEDTLS_PEM_WRITE_C + bool "Write PEM formatted certificates" + default y + help + Enable writing of PEM formatted certificates. + + If writing certificate data only in DER format, disabling this + option will save some code size. + + config MBEDTLS_X509_CRL_PARSE_C + bool "X.509 CRL parsing" + default y + help + Support for parsing X.509 Certificate Revocation Lists. + + config MBEDTLS_X509_CSR_PARSE_C + bool "X.509 CSR parsing" + default y + help + Support for parsing X.509 Certificate Signing Requests + +endmenu # Certificates + +menuconfig MBEDTLS_ECP_C + bool "Elliptic Curve Ciphers" + default y + +config MBEDTLS_DHM_C + bool "Diffie-Hellman-Merkle key exchange (DHM)" + default n + help + Enable DHM. Needed to use DHE-xxx TLS ciphersuites. + + Note that the security of Diffie-Hellman key exchanges depends on + a suitable prime being used for the exchange. Please see detailed + warning text about this in file `mbedtls/dhm.h` file. + +config MBEDTLS_ECDH_C + bool "Elliptic Curve Diffie-Hellman (ECDH)" + depends on MBEDTLS_ECP_C + default y + help + Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. + +config MBEDTLS_ECDSA_C + bool "Elliptic Curve DSA" + depends on MBEDTLS_ECDH_C + default y + help + Enable ECDSA. Needed to use ECDSA-xxx TLS ciphersuites. + +config MBEDTLS_ECJPAKE_C + bool "Elliptic curve J-PAKE" + depends on MBEDTLS_ECP_C + default n + help + Enable ECJPAKE. Needed to use ECJPAKE-xxx TLS ciphersuites. + +config MBEDTLS_ECP_DP_SECP192R1_ENABLED + bool "Enable SECP192R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP192R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP224R1_ENABLED + bool "Enable SECP224R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP224R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP256R1_ENABLED + bool "Enable SECP256R1 curve" + depends on MBEDTLS_ECP_C + default y + help + Enable support for SECP256R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP384R1_ENABLED + bool "Enable SECP384R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP384R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP521R1_ENABLED + bool "Enable SECP521R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP521R1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP192K1_ENABLED + bool "Enable SECP192K1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP192K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP224K1_ENABLED + bool "Enable SECP224K1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP224K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_SECP256K1_ENABLED + bool "Enable SECP256K1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for SECP256K1 Elliptic Curve. + +config MBEDTLS_ECP_DP_BP256R1_ENABLED + bool "Enable BP256R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_BP384R1_ENABLED + bool "Enable BP384R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_BP512R1_ENABLED + bool "Enable BP512R1 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + support for DP Elliptic Curve. + +config MBEDTLS_ECP_DP_CURVE25519_ENABLED + bool "Enable CURVE25519 curve" + depends on MBEDTLS_ECP_C + default y if !(MBEDTLS_ATCA_HW_ECDSA_SIGN || MBEDTLS_ATCA_HW_ECDSA_VERIFY) + help + Enable support for CURVE25519 Elliptic Curve. + +config MBEDTLS_ECP_NIST_OPTIM + bool "NIST 'modulo p' optimisations" + depends on MBEDTLS_ECP_C + default y + help + NIST 'modulo p' optimisations increase Elliptic Curve operation performance. + + Disabling this option saves some code size. + +config MBEDTLS_ECP_FIXED_POINT_OPTIM + bool "Enable fixed-point multiplication optimisations" + depends on MBEDTLS_ECP_C + default n + help + This configuration option enables optimizations to speedup (about 3 ~ 4 times) the ECP + fixed point multiplication using pre-computed tables in the flash memory. + Enabling this configuration option increases the flash footprint + (about 29KB if all Elliptic Curve selected) in the application binary. + + # end of Elliptic Curve options + +config MBEDTLS_POLY1305_C + bool "Poly1305 MAC algorithm" + default n + help + Enable support for Poly1305 MAC algorithm. + +config MBEDTLS_CHACHA20_C + bool "Chacha20 stream cipher" + default n + help + Enable support for Chacha20 stream cipher. + +config MBEDTLS_CHACHAPOLY_C + bool "ChaCha20-Poly1305 AEAD algorithm" + default n + depends on MBEDTLS_CHACHA20_C && MBEDTLS_POLY1305_C + help + Enable support for ChaCha20-Poly1305 AEAD algorithm. + +config MBEDTLS_HKDF_C + bool "HKDF algorithm (RFC 5869)" + default n + help + Enable support for the Hashed Message Authentication Code + (HMAC)-based key derivation function (HKDF). + +config MBEDTLS_THREADING_C + depends on !IDF_TARGET_ESP8266 + bool "Enable the threading abstraction layer" + default n + help + If you do intend to use contexts between threads, you will need to enable + this layer to prevent race conditions. + +config MBEDTLS_THREADING_ALT + bool "Enable threading alternate implementation" + depends on MBEDTLS_THREADING_C + default y + help + Enable threading alt to allow your own alternate threading implementation. + +config MBEDTLS_THREADING_PTHREAD + bool "Enable threading pthread implementation" + depends on MBEDTLS_THREADING_C + default n + help + Enable the pthread wrapper layer for the threading layer. + +config MBEDTLS_ERROR_STRINGS + bool "Enable error code to error string conversion" + default y + help + Enables mbedtls_strerror() for converting error codes to error strings. + Disabling this config can save some code/rodata size as the error + string conversion implementation is replaced with an empty stub. + +config MBEDTLS_USE_CRYPTO_ROM_IMPL + bool "Use ROM implementation of the crypto algorithm" + depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB + default "n" + select MBEDTLS_SHA512_C + select MBEDTLS_AES_C + select MBEDTLS_CCM_C + select MBEDTLS_CMAC_C + select MBEDTLS_ROM_MD5 + select MBEDTLS_HARDWARE_SHA + select MBEDTLS_ECP_RESTARTABLE + select MBEDTLS_THREADING_C + help + Enable this flag to use mbedtls crypto algorithm from ROM instead of ESP-IDF. + + This configuration option saves flash footprint in the application binary. + Note that the version of mbedtls crypto algorithm library in ROM(ECO1~ECO3) is v2.16.12, + and the version of mbedtls crypto algorithm library in ROM(ECO4) is v3.6.0. + We have done the security analysis of the mbedtls revision in ROM (ECO1~ECO4) + and ensured that affected symbols have been patched (removed). If in the future + mbedtls revisions there are security issues that also affects the version in + ROM (ECO1~ECO4) then we shall patch the relevant symbols. This would increase + the flash footprint and hence care must be taken to keep some reserved space + for the application binary in flash layout. + diff --git a/components/mbedtls/mbedtls_v3/component.mk b/components/mbedtls/mbedtls_v3/component.mk new file mode 100644 index 000000000..66da38fb2 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/component.mk @@ -0,0 +1,38 @@ +# +# Component Makefile +# + +CURRENT_DIR := mbedtls_v3 + +COMPONENT_ADD_INCLUDEDIRS := $(CURRENT_DIR)/port/include $(CURRENT_DIR)/mbedtls/include $(CURRENT_DIR)/mbedtls/library + +COMPONENT_SRCDIRS := $(CURRENT_DIR)/mbedtls/library $(CURRENT_DIR)/port + +COMPONENT_OBJEXCLUDE := $(CURRENT_DIR)/mbedtls/library/net_sockets.o + +ifndef CONFIG_MBEDTLS_HARDWARE_MPI +COMPONENT_OBJEXCLUDE += $(CURRENT_DIR)/port/esp_bignum.o +endif + +COMPONENT_SUBMODULES += $(CURRENT_DIR)/mbedtls + +ifdef CONFIG_MBEDTLS_DYNAMIC_BUFFER + +WRAP_FUNCTIONS = mbedtls_ssl_write_client_hello \ + mbedtls_ssl_handshake_client_step \ + mbedtls_ssl_handshake_server_step \ + mbedtls_ssl_read \ + mbedtls_ssl_write \ + mbedtls_ssl_session_reset \ + mbedtls_ssl_free \ + mbedtls_ssl_setup \ + mbedtls_ssl_send_alert_message \ + mbedtls_ssl_close_notify + +WRAP_ARGUMENT := -Wl,--wrap= + +COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS)) + +COMPONENT_SRCDIRS += $(CURRENT_DIR)/port/dynamic + +endif diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_all.pem b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_all.pem new file mode 100644 index 000000000..2ae7b6cb2 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_all.pem @@ -0,0 +1,3372 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Jan 10 04:12:06 2023 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.29. +## SHA256: 90c470e705b4b5f36f09684dc50e2b79c8b86989a848b62cd1a7bd6460ee65f6 +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) FÅ‘tanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +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 +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +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 +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +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 +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +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 +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl +e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb +a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS ++LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M +kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG +r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q +S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV +J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL +dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD +ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh +swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel +/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn +jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5 +9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M +7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8 +0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR +WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW +HYbL +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +E-Tugra Global Root CA RSA v3 +============================= +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +E-Tugra Global Root CA ECC v3 +============================= +-----BEGIN CERTIFICATE----- +MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV +BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB +IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP +MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 +Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 +w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 +Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ +zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W +Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 +-----END CERTIFICATE----- + +Security Communication RootCA3 +============================== +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNVBAYTAkpQMSUw +IwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TZWN1cml0eSBD +b21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQsw +CQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UE +AxMeU2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4rCmDvu20r +hvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzAlrenfna84xtSGc4RHwsE +NPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MGTfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2 +/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF79+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGm +npjKIG58u4iFW/vAEGK78vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtY +XLVqAvO4g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3weGVPK +p7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst+3A7caoreyYn8xrC +3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M0V9hvqG8OmpI6iZVIhZdXw3/JzOf +GAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQT9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0Vcw +CBEF/VfR2ccCAwEAAaNCMEAwHQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PAFNr0Y/Dq9HHu +Tofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd9XbXv8S2gVj/yP9kaWJ5rW4O +H3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQIUYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASx +YfQAW0q3nHE3GYV5v4GwxxMOdnE+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZ +XSEIx2C/pHF7uNkegr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml ++LLfiAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUVnuiZIesn +KwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD2NCcnWXL0CsnMQMeNuE9 +dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm +6Vwdp6POXiUyK+OVrCoHzrQoeIY8LaadTdJ0MN1kURXbg4NR16/9M51NZg== +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_local.pem b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_local.pem new file mode 100644 index 000000000..3633ed161 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cacrt_local.pem @@ -0,0 +1,33 @@ +## +## Local CA Root Certificates +## +## Local CA Root Certificates that gets appended to "cacrt_all.pem" + + +## letsencrypt has generated a cross signed certificate with DST ROOT CA X3 +## for compatibility after the expiry of the certificate. +## The new certificate has the ISSUER name as DST Root CA X3. +## Thus, the handshake fails if esp_crt_bundle does not find the +## respective name in the crt_bundle. +## Keeping this certificate for compatibility reasons. +## This will be removed once the cross-signed certificate expires in Sep 2024. + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/cmn_crt_authorities.csv b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cmn_crt_authorities.csv new file mode 100644 index 000000000..0e10c7013 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/cmn_crt_authorities.csv @@ -0,0 +1,42 @@ +Owner,Common Name or Certificate Name +Amazon Trust Services,Amazon Root CA 1 +Amazon Trust Services,Amazon Root CA 2 +Amazon Trust Services,Amazon Root CA 3 +Amazon Trust Services,Amazon Root CA 4 +Amazon Trust Services,Starfield Services Root Certificate Authority - G2 +DigiCert,Baltimore CyberTrust Root +DigiCert,DigiCert Assured ID Root CA +DigiCert,DigiCert Assured ID Root G2 +DigiCert,DigiCert Assured ID Root G3 +DigiCert,DigiCert Global Root CA +DigiCert,DigiCert Global Root G2 +DigiCert,DigiCert Global Root G3 +DigiCert,DigiCert High Assurance EV Root CA +DigiCert,DigiCert TLS ECC P384 Root G5 +DigiCert,DigiCert TLS RSA4096 Root G5 +DigiCert,DigiCert Trusted Root G4 +GlobalSign nv-sa,GlobalSign Root CA - R3 +GlobalSign nv-sa,GlobalSign ECC Root CA - R5 +GlobalSign nv-sa,GlobalSign Root CA - R6 +GlobalSign nv-sa,GlobalSign Root CA +GlobalSign nv-sa,GlobalSign Root E46 +GlobalSign nv-sa,GlobalSign Root R46 +GoDaddy,Go Daddy Class 2 CA +GoDaddy,Go Daddy Root Certificate Authority - G2 +GoDaddy,Starfield Class 2 CA +GoDaddy,Starfield Root Certificate Authority - G2 +Google Trust Services LLC,GlobalSign ECC Root CA - R4 +Google Trust Services LLC,GTS Root R1 +Google Trust Services LLC,GTS Root R2 +Google Trust Services LLC,GTS Root R3 +Google Trust Services LLC,GTS Root R4 +"IdenTrust Services, LLC",DST Root CA X3 +"IdenTrust Services, LLC",IdenTrust Commercial Root CA 1 +"IdenTrust Services, LLC",IdenTrust Public Sector Root CA 1 +Internet Security Research Group,ISRG Root X1 +Internet Security Research Group,ISRG Root X2 +Sectigo,COMODO Certification Authority +Sectigo,COMODO ECC Certification Authority +Sectigo,COMODO RSA Certification Authority +Sectigo,USERTrust ECC Certification Authority +Sectigo,USERTrust RSA Certification Authority diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/mbedtls_v3/esp_crt_bundle/esp_crt_bundle.c new file mode 100644 index 000000000..e1b042b8c --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/esp_crt_bundle.c @@ -0,0 +1,244 @@ +/* + * SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_crt_bundle.h" +#include "esp_log.h" + +#define BUNDLE_HEADER_OFFSET 2 +#define CRT_HEADER_OFFSET 4 + +static const char *TAG = "esp-x509-crt-bundle"; + +/* a dummy certificate so that + * cacert_ptr passes non-NULL check during handshake */ +static const mbedtls_x509_crt s_dummy_crt; + +extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start"); +extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end"); + + +typedef struct crt_bundle_t { + const uint8_t **crts; + uint16_t num_certs; + size_t x509_crt_bundle_len; +} crt_bundle_t; + +static crt_bundle_t s_crt_bundle; + +static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len); + + +static int esp_crt_check_signature(mbedtls_x509_crt *child, const uint8_t *pub_key_buf, size_t pub_key_len) +{ + int ret = 0; + mbedtls_x509_crt parent; + const mbedtls_md_info_t *md_info; + unsigned char hash[MBEDTLS_MD_MAX_SIZE]; + + mbedtls_x509_crt_init(&parent); + + if ( (ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, pub_key_len) ) != 0) { + ESP_LOGE(TAG, "PK parse failed with error %X", ret); + goto cleanup; + } + + + // Fast check to avoid expensive computations when not necessary + if (!mbedtls_pk_can_do(&parent.pk, child->MBEDTLS_PRIVATE(sig_pk))) { + ESP_LOGE(TAG, "Simple compare failed"); + ret = -1; + goto cleanup; + } + + md_info = mbedtls_md_info_from_type(child->MBEDTLS_PRIVATE(sig_md)); + if ( (ret = mbedtls_md( md_info, child->tbs.p, child->tbs.len, hash )) != 0 ) { + ESP_LOGE(TAG, "Internal mbedTLS error %X", ret); + goto cleanup; + } + + if ( (ret = mbedtls_pk_verify_ext( child->MBEDTLS_PRIVATE(sig_pk), child->MBEDTLS_PRIVATE(sig_opts), &parent.pk, + child->MBEDTLS_PRIVATE(sig_md), hash, mbedtls_md_get_size( md_info ), + child->MBEDTLS_PRIVATE(sig).p, child->MBEDTLS_PRIVATE(sig).len )) != 0 ) { + + ESP_LOGE(TAG, "PK verify failed with error %X", ret); + goto cleanup; + } +cleanup: + mbedtls_x509_crt_free(&parent); + + return ret; +} + + +/* This callback is called for every certificate in the chain. If the chain + * is proper each intermediate certificate is validated through its parent + * in the x509_crt_verify_chain() function. So this callback should + * only verify the first untrusted link in the chain is signed by the + * root certificate in the trusted bundle +*/ +int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int depth, uint32_t *flags) +{ + mbedtls_x509_crt *child = crt; + + /* It's OK for a trusted cert to have a weak signature hash alg. + as we already trust this certificate */ + uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD); + + if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) { + return 0; + } + + + if (s_crt_bundle.crts == NULL) { + ESP_LOGE(TAG, "No certificates in bundle"); + return MBEDTLS_ERR_X509_FATAL_ERROR; + } + + ESP_LOGD(TAG, "%d certificates in bundle", s_crt_bundle.num_certs); + + size_t name_len = 0; + const uint8_t *crt_name; + + bool crt_found = false; + int start = 0; + int end = s_crt_bundle.num_certs - 1; + int middle = (end - start) / 2; + + /* Look for the certificate using binary search on subject name */ + while (start <= end) { + name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1]; + crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET; + + int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len ); + if (cmp_res == 0) { + crt_found = true; + break; + } else if (cmp_res < 0) { + end = middle - 1; + } else { + start = middle + 1; + } + middle = (start + end) / 2; + } + + int ret = MBEDTLS_ERR_X509_FATAL_ERROR; + if (crt_found) { + size_t key_len = s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3]; + ret = esp_crt_check_signature(child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len); + } + + if (ret == 0) { + ESP_LOGI(TAG, "Certificate validated"); + *flags = 0; + return 0; + } + + ESP_LOGE(TAG, "Failed to verify certificate"); + return MBEDTLS_ERR_X509_FATAL_ERROR; +} + + +/* Initialize the bundle into an array so we can do binary search for certs, + the bundle generated by the python utility is already presorted by subject name + */ +static esp_err_t esp_crt_bundle_init(const uint8_t *x509_bundle, size_t bundle_size) +{ + if (bundle_size < BUNDLE_HEADER_OFFSET + CRT_HEADER_OFFSET) { + ESP_LOGE(TAG, "Invalid certificate bundle"); + return ESP_ERR_INVALID_ARG; + } + + uint16_t num_certs = (x509_bundle[0] << 8) | x509_bundle[1]; + if (num_certs > CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS) { + ESP_LOGE(TAG, "No. of certs in the certificate bundle = %d exceeds\n" + "Max allowed certificates in the certificate bundle = %d\n" + "Please update the menuconfig option with appropriate value", num_certs, CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS); + return ESP_ERR_INVALID_ARG; + } + + const uint8_t **crts = calloc(num_certs, sizeof(x509_bundle)); + if (crts == NULL) { + ESP_LOGE(TAG, "Unable to allocate memory for bundle"); + return ESP_ERR_NO_MEM; + } + + const uint8_t *cur_crt; + /* This is the maximum region that is allowed to access */ + const uint8_t *bundle_end = x509_bundle + bundle_size; + cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; + + for (int i = 0; i < num_certs; i++) { + crts[i] = cur_crt; + if (cur_crt + CRT_HEADER_OFFSET > bundle_end) { + ESP_LOGE(TAG, "Invalid certificate bundle"); + free(crts); + return ESP_ERR_INVALID_ARG; + } + size_t name_len = cur_crt[0] << 8 | cur_crt[1]; + size_t key_len = cur_crt[2] << 8 | cur_crt[3]; + cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len; + } + + if (cur_crt > bundle_end) { + ESP_LOGE(TAG, "Invalid certificate bundle"); + free(crts); + return ESP_ERR_INVALID_ARG; + } + + /* The previous crt bundle is only updated when initialization of the + * current crt_bundle is successful */ + /* Free previous crt_bundle */ + free(s_crt_bundle.crts); + s_crt_bundle.num_certs = num_certs; + s_crt_bundle.crts = crts; + return ESP_OK; +} + +esp_err_t esp_crt_bundle_attach(void *conf) +{ + esp_err_t ret = ESP_OK; + // If no bundle has been set by the user then use the bundle embedded in the binary + if (s_crt_bundle.crts == NULL) { + ret = esp_crt_bundle_init(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); + } + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to attach bundle"); + return ret; + } + + if (conf) { + /* point to a dummy certificate + * This is only required so that the + * cacert_ptr passes non-NULL check during handshake + */ + mbedtls_ssl_config *ssl_conf = (mbedtls_ssl_config *)conf; + mbedtls_ssl_conf_ca_chain(ssl_conf, (mbedtls_x509_crt*)&s_dummy_crt, NULL); + mbedtls_ssl_conf_verify(ssl_conf, esp_crt_verify_callback, NULL); + } + + return ret; +} + +void esp_crt_bundle_detach(mbedtls_ssl_config *conf) +{ + free(s_crt_bundle.crts); + s_crt_bundle.crts = NULL; + if (conf) { + mbedtls_ssl_conf_verify(conf, NULL, NULL); + } +} + +esp_err_t esp_crt_bundle_set(const uint8_t *x509_bundle, size_t bundle_size) +{ + return esp_crt_bundle_init(x509_bundle, bundle_size); +} + +bool esp_crt_bundle_in_use(const mbedtls_x509_crt* ca_chain) +{ + return ((ca_chain == &s_dummy_crt) ? true : false); +} diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/gen_crt_bundle.py b/components/mbedtls/mbedtls_v3/esp_crt_bundle/gen_crt_bundle.py new file mode 100755 index 000000000..0211514a4 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/gen_crt_bundle.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# +# ESP32 x509 certificate bundle generation utility +# +# Converts PEM and DER certificates to a custom bundle format which stores just the +# subject name and public key to reduce space +# +# The bundle will have the format: number of certificates; crt 1 subject name length; crt 1 public key length; +# crt 1 subject name; crt 1 public key; crt 2... +# +# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import with_statement + +import argparse +import csv +import os +import re +import struct +import sys +from io import open + +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization +except ImportError: + print('The cryptography package is not installed.' + 'Please refer to the Get Started section of the ESP-IDF Programming Guide for ' + 'setting up the required packages.') + raise + +ca_bundle_bin_file = 'x509_crt_bundle' + +quiet = False + + +def status(msg): + """ Print status message to stderr """ + if not quiet: + critical(msg) + + +def critical(msg): + """ Print critical message to stderr """ + sys.stderr.write('gen_crt_bundle.py: ') + sys.stderr.write(msg) + sys.stderr.write('\n') + + +class CertificateBundle: + def __init__(self): + self.certificates = [] + self.compressed_crts = [] + + if os.path.isfile(ca_bundle_bin_file): + os.remove(ca_bundle_bin_file) + + def add_from_path(self, crts_path): + + found = False + for file_path in os.listdir(crts_path): + found |= self.add_from_file(os.path.join(crts_path, file_path)) + + if found is False: + raise InputError('No valid x509 certificates found in %s' % crts_path) + + def add_from_file(self, file_path): + try: + if file_path.endswith('.pem'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'r', encoding='utf-8') as f: + crt_str = f.read() + self.add_from_pem(crt_str) + return True + + elif file_path.endswith('.der'): + status('Parsing certificates from %s' % file_path) + with open(file_path, 'rb') as f: + crt_str = f.read() + self.add_from_der(crt_str) + return True + + except ValueError: + critical('Invalid certificate in %s' % file_path) + raise InputError('Invalid certificate') + + return False + + def add_from_pem(self, crt_str): + """ A single PEM file may have multiple certificates """ + + crt = '' + count = 0 + start = False + + for strg in crt_str.splitlines(True): + if strg == '-----BEGIN CERTIFICATE-----\n' and start is False: + crt = '' + start = True + elif strg == '-----END CERTIFICATE-----\n' and start is True: + crt += strg + '\n' + start = False + self.certificates.append(x509.load_pem_x509_certificate(crt.encode(), default_backend())) + count += 1 + if start is True: + crt += strg + + if count == 0: + raise InputError('No certificate found') + + status('Successfully added %d certificates' % count) + + def add_from_der(self, crt_str): + self.certificates.append(x509.load_der_x509_certificate(crt_str, default_backend())) + status('Successfully added 1 certificate') + + def create_bundle(self): + # Sort certificates in order to do binary search when looking up certificates + self.certificates = sorted(self.certificates, key=lambda cert: cert.subject.public_bytes(default_backend())) + + bundle = struct.pack('>H', len(self.certificates)) + + for crt in self.certificates: + """ Read the public key as DER format """ + pub_key = crt.public_key() + pub_key_der = pub_key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo) + + """ Read the subject name as DER format """ + sub_name_der = crt.subject.public_bytes(default_backend()) + + name_len = len(sub_name_der) + key_len = len(pub_key_der) + len_data = struct.pack('>HH', name_len, key_len) + + bundle += len_data + bundle += sub_name_der + bundle += pub_key_der + + return bundle + + def add_with_filter(self, crts_path, filter_path): + + filter_set = set() + with open(filter_path, 'r', encoding='utf-8') as f: + csv_reader = csv.reader(f, delimiter=',') + + # Skip header + next(csv_reader) + for row in csv_reader: + filter_set.add(row[1]) + + status('Parsing certificates from %s' % crts_path) + crt_str = [] + with open(crts_path, 'r', encoding='utf-8') as f: + crt_str = f.read() + + # Split all certs into a list of (name, certificate string) tuples + pem_crts = re.findall(r'(^.+?)\n(=+\n[\s\S]+?END CERTIFICATE-----\n)', crt_str, re.MULTILINE) + + filtered_crts = '' + for name, crt in pem_crts: + if name in filter_set: + filtered_crts += crt + + self.add_from_pem(filtered_crts) + + +class InputError(RuntimeError): + def __init__(self, e): + super(InputError, self).__init__(e) + + +def main(): + global quiet + + parser = argparse.ArgumentParser(description='ESP-IDF x509 certificate bundle utility') + + parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true') + parser.add_argument('--input', '-i', nargs='+', required=True, + help='Paths to the custom certificate folders or files to parse, parses all .pem or .der files') + parser.add_argument('--filter', '-f', help='Path to CSV-file where the second columns contains the name of the certificates \ + that should be included from cacrt_all.pem') + + args = parser.parse_args() + + quiet = args.quiet + + bundle = CertificateBundle() + + for path in args.input: + if os.path.isfile(path): + if os.path.basename(path) == 'cacrt_all.pem' and args.filter: + bundle.add_with_filter(path, args.filter) + else: + bundle.add_from_file(path) + elif os.path.isdir(path): + bundle.add_from_path(path) + else: + raise InputError('Invalid --input=%s, is neither file nor folder' % args.input) + + status('Successfully added %d certificates in total' % len(bundle.certificates)) + + crt_bundle = bundle.create_bundle() + + with open(ca_bundle_bin_file, 'wb') as f: + f.write(crt_bundle) + + +if __name__ == '__main__': + try: + main() + except InputError as e: + print(e) + sys.exit(2) diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/include/esp_crt_bundle.h b/components/mbedtls/mbedtls_v3/esp_crt_bundle/include/esp_crt_bundle.h new file mode 100644 index 000000000..61c09f0bd --- /dev/null +++ b/components/mbedtls/mbedtls_v3/esp_crt_bundle/include/esp_crt_bundle.h @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef _ESP_CRT_BUNDLE_H_ +#define _ESP_CRT_BUNDLE_H_ + +#include "esp_err.h" +#include "mbedtls/ssl.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Attach and enable use of a bundle for certificate verification + * + * Attach and enable use of a bundle for certificate verification through a verification callback. + * If no specific bundle has been set through esp_crt_bundle_set() it will default to the + * bundle defined in menuconfig and embedded in the binary. + * + * @param[in] conf The config struct for the SSL connection. + * + * @return + * - ESP_OK if adding certificates was successful. + * - Other if an error occurred or an action must be taken by the calling process. + */ +esp_err_t esp_crt_bundle_attach(void *conf); + + +/** + * @brief Disable and dealloc the certification bundle + * + * Removes the certificate verification callback and deallocates used resources + * + * @param[in] conf The config struct for the SSL connection. + */ +void esp_crt_bundle_detach(mbedtls_ssl_config *conf); + + +/** + * @brief Set the default certificate bundle used for verification + * + * Overrides the default certificate bundle only in case of successful initialization. In most use cases the bundle should be + * set through menuconfig. The bundle needs to be sorted by subject name since binary search is + * used to find certificates. + * + * @param[in] x509_bundle A pointer to the certificate bundle. + * + * @param[in] bundle_size Size of the certificate bundle in bytes. + * + * @return + * - ESP_OK if adding certificates was successful. + * - Other if an error occurred or an action must be taken by the calling process. + */ +esp_err_t esp_crt_bundle_set(const uint8_t *x509_bundle, size_t bundle_size); + +/** + * @brief Check if the given CA certificate chain is the default "dummy" + * certificate chain attached by the esp_crt_bundle + * + * @param ca_chain A pointer to the CA chain. + * @return true if the ca_chain is the dummy CA chain attached by esp_crt_bundle + * @return false otherwise + */ +bool esp_crt_bundle_in_use(const mbedtls_x509_crt* ca_chain); + +#ifdef __cplusplus +} +#endif + +#endif //_ESP_CRT_BUNDLE_H_ diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore.der b/components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore.der new file mode 100644 index 0000000000000000000000000000000000000000..da96dbb2c93d063581e5a96ebb4e258c37923087 GIT binary patch literal 891 zcmXqLVlFpmVv1kD%*4pV#KOeDu+xB-jZ>@5qwPB{BO@y-gF%!bw*e;`b0`a&Fq5aN zp^$+9h{Gk!>6DmLl9`)dlxiq!AP5rU7UpuUOiC>ZDJm^4F;p^800}Y+OTrZ@Afy$7 z^7Bg!!|F<^IChw;MQFb8lbttNLc@n^wolb5B&#(PO+Vy_3-8?#l#`0j4ZA-VJ!F6KYLwB<*Txh2chm4;Ok`xj_^5t{jZv1`*~o4BuY=WRVPuSW00l6ig{BHp=w z3v#oilJ}-Oliz;s!>9RryQQ~(3g{@Fbm-AzvEErmOso4O?!FWdd{<|>dct};gDF)P zjBS^v@_+r)H!tbDyNeEE>~7huMwi?#S?FE(*LcS$OK$5so2!4GH_A#LR&jcjY`GEu Dq-#{f literal 0 HcmV?d00001 diff --git a/components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore_crt_bundle b/components/mbedtls/mbedtls_v3/esp_crt_bundle/test_gen_crt_bundle/baltimore_crt_bundle new file mode 100644 index 0000000000000000000000000000000000000000..07068589404e88f154a326317ba373f5eb8ce82a GIT binary patch literal 392 zcmZQzWQbu@Gl(+eHsEAq4rO5zX7Y436fzJ1akzvzof30OGIR5bQVoR-1VLil!d%Xk zNvTC4MWw|hhDrtsAVFqfNw`7g#IN_O4meWLBo{u;;n@{b!J{{vqkB1qC1a4+x!NJ3Z^+%~Qrz<=1|^+4(WU z#mPhX(t$%u*2N|K+qR7L&z8IFkxW-2C1xalU7Gx00#9x1Cbp{2t*>5RVO|&4Vi*5k z^lj#zpO$7trRU3od_SL@ptIBV=}F~adwG=^iZSdnHTNE~dhJHlqC~0BLgD7X7iE@I z8Z)dg%%orz6-de>G{e@ZItpmcJaW}jtwXO8+IEr-`?I_ZKu!@_jXg5 z#_}Hzb~+TAA3g2PH(e{^fYQ_?(cA7fzh2P(-{gIAt=&DJ+Y=fB>^Zwy7~U9g*-D)K z{Z}h;mhx0_9oKpCdCrx&JTWu=c`QGw>>d;MH}ARJmTwuZ7u%Yn7j^Y+e5~2hXnsUoBZf| z!;{qCCgyya4F$gIE!y +#include "mbedtls/aes.h" +#include "mbedtls/platform_util.h" +#include "esp_log.h" +#include "aes/esp_aes.h" +#include "soc/hwcrypto_periph.h" +#include +#include "hal/aes_hal.h" +#include "aes/esp_aes_internal.h" + +#include + +#include +#include "esp_private/periph_ctrl.h" + + +static const char *TAG = "esp-aes"; +/* AES uses a spinlock mux not a lock as the underlying block operation + only takes 208 cycles (to write key & compute block), +600 cycles + for DPORT protection but +3400 cycles again if you use a full sized lock. + + For CBC, CFB, etc. this may mean that interrupts are disabled for a longer + period of time for bigger lengths. However at the moment this has to happen + anyway due to DPORT protection... +*/ +static portMUX_TYPE aes_spinlock = portMUX_INITIALIZER_UNLOCKED; + + +void esp_aes_acquire_hardware( void ) +{ + portENTER_CRITICAL(&aes_spinlock); + + /* Enable AES hardware */ + periph_module_enable(PERIPH_AES_MODULE); +} + +void esp_aes_release_hardware( void ) +{ + /* Disable AES hardware */ + periph_module_disable(PERIPH_AES_MODULE); + + portEXIT_CRITICAL(&aes_spinlock); +} + + + +/* Run a single 16 byte block of AES, using the hardware engine. + * + * Call only while holding esp_aes_acquire_hardware(). + * + * The function esp_aes_block zeroises the output buffer in the case of following conditions: + * 1. If key is not written in the hardware + * 2. If the fault injection check failed + */ +static int esp_aes_block(esp_aes_context *ctx, const void *input, void *output) +{ + uint32_t i0, i1, i2, i3; + const uint32_t *input_words = (uint32_t *)input; + uint32_t *output_words = (uint32_t *)output; + + /* If no key is written to hardware yet, either the user hasn't called + mbedtls_aes_setkey_enc/mbedtls_aes_setkey_dec - meaning we also don't + know which mode to use - or a fault skipped the + key write to hardware. Treat this as a fatal error and zero the output block. + */ + if (ctx->key_in_hardware != ctx->key_bytes) { + mbedtls_platform_zeroize(output, 16); + return MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH; + } + i0 = input_words[0]; + i1 = input_words[1]; + i2 = input_words[2]; + i3 = input_words[3]; + + aes_hal_transform_block(input, output); + + /* Physical security check: Verify the AES accelerator actually ran, and wasn't + skipped due to external fault injection while starting the peripheral. + + Note that i0,i1,i2,i3 are copied from input buffer in case input==output. + + Bypassing this check requires at least one additional fault. + */ + if (i0 == output_words[0] && i1 == output_words[1] && i2 == output_words[2] && i3 == output_words[3]) { + // calling zeroing functions to narrow the + // window for a double-fault of the abort step, here + memset(output, 0, 16); + mbedtls_platform_zeroize(output, 16); + abort(); + } + + return 0; +} + +static int esp_aes_validate_input(esp_aes_context *ctx, const unsigned char *input, + const unsigned char *output ) +{ + if (!ctx) { + ESP_LOGD(TAG, "No AES context supplied"); + return -1; + } + if (!input) { + ESP_LOGD(TAG, "No input supplied"); + return -1; + } + if (!output) { + ESP_LOGD(TAG, "No output supplied"); + return -1; + } + + return 0; +} + + +void esp_aes_encrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + esp_internal_aes_encrypt(ctx, input, output); +} + +/* + * AES-ECB block encryption + */ +int esp_internal_aes_encrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + r = esp_aes_block(ctx, input, output); + esp_aes_release_hardware(); + return r; +} + +void esp_aes_decrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + esp_internal_aes_decrypt(ctx, input, output); +} + +/* + * AES-ECB block decryption + */ + +int esp_internal_aes_decrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_DECRYPT); + r = esp_aes_block(ctx, input, output); + esp_aes_release_hardware(); + return r; +} + +/* + * AES-ECB block encryption/decryption + */ +int esp_aes_crypt_ecb(esp_aes_context *ctx, + int mode, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + r = esp_aes_block(ctx, input, output); + esp_aes_release_hardware(); + return r; +} + + +/* + * AES-CBC buffer encryption/decryption + */ +int esp_aes_crypt_cbc(esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int ret = -1; + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGD(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + uint32_t *output_words = (uint32_t *)output; + const uint32_t *input_words = (const uint32_t *)input; + uint32_t *iv_words = (uint32_t *)iv; + unsigned char temp[16]; + + if ( length % 16 ) { + return ( ERR_ESP_AES_INVALID_INPUT_LENGTH ); + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + + + if ( mode == ESP_AES_DECRYPT ) { + while ( length > 0 ) { + memcpy(temp, input_words, 16); + ret = esp_aes_block(ctx, input_words, output_words); + if (ret != 0) { + goto cleanup; + } + + output_words[0] = output_words[0] ^ iv_words[0]; + output_words[1] = output_words[1] ^ iv_words[1]; + output_words[2] = output_words[2] ^ iv_words[2]; + output_words[3] = output_words[3] ^ iv_words[3]; + + memcpy( iv_words, temp, 16 ); + + input_words += 4; + output_words += 4; + length -= 16; + } + } else { // ESP_AES_ENCRYPT + while ( length > 0 ) { + + output_words[0] = input_words[0] ^ iv_words[0]; + output_words[1] = input_words[1] ^ iv_words[1]; + output_words[2] = input_words[2] ^ iv_words[2]; + output_words[3] = input_words[3] ^ iv_words[3]; + + ret = esp_aes_block(ctx, output_words, output_words); + if (ret != 0) { + goto cleanup; + } + + memcpy( iv_words, output_words, 16 ); + + input_words += 4; + output_words += 4; + length -= 16; + } + } + ret = 0; + +cleanup: + esp_aes_release_hardware(); + return ret; +} + +/* + * AES-CFB128 buffer encryption/decryption + */ +int esp_aes_crypt_cfb128(esp_aes_context *ctx, + int mode, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int ret = -1; + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv_off) { + ESP_LOGE(TAG, "No IV offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + int c; + size_t n = *iv_off; + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + + if ( mode == ESP_AES_DECRYPT ) { + while ( length-- ) { + if ( n == 0 ) { + ret = esp_aes_block(ctx, iv, iv); + if (ret != 0) { + goto cleanup; + } + } + + c = *input++; + *output++ = (unsigned char)( c ^ iv[n] ); + iv[n] = (unsigned char) c; + + n = ( n + 1 ) & 0x0F; + } + } else { + while ( length-- ) { + if ( n == 0 ) { + ret = esp_aes_block(ctx, iv, iv); + if (ret != 0) { + goto cleanup; + } + } + + iv[n] = *output++ = (unsigned char)( iv[n] ^ *input++ ); + + n = ( n + 1 ) & 0x0F; + } + } + + *iv_off = n; + ret = 0; + +cleanup: + esp_aes_release_hardware(); + return ret; +} + +/* + * AES-CFB8 buffer encryption/decryption + */ +int esp_aes_crypt_cfb8(esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int ret = -1; + unsigned char c; + unsigned char ov[17]; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + + + while ( length-- ) { + memcpy( ov, iv, 16 ); + ret = esp_aes_block(ctx, iv, iv); + if (ret != 0) { + goto cleanup; + } + + if ( mode == ESP_AES_DECRYPT ) { + ov[16] = *input; + } + + c = *output++ = (unsigned char)( iv[0] ^ *input++ ); + + if ( mode == ESP_AES_ENCRYPT ) { + ov[16] = c; + } + + memcpy( iv, ov + 1, 16 ); + } + ret = 0; + +cleanup: + esp_aes_release_hardware(); + return ret; +} + +/* + * AES-CTR buffer encryption/decryption + */ +int esp_aes_crypt_ctr(esp_aes_context *ctx, + size_t length, + size_t *nc_off, + unsigned char nonce_counter[16], + unsigned char stream_block[16], + const unsigned char *input, + unsigned char *output ) +{ + int c, i, ret = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!stream_block) { + ESP_LOGE(TAG, "No stream supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!nonce_counter) { + ESP_LOGE(TAG, "No nonce supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!nc_off) { + ESP_LOGE(TAG, "No nonce offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + size_t n = *nc_off; + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + + + while ( length-- ) { + if ( n == 0 ) { + ret = esp_aes_block(ctx, nonce_counter, stream_block); + if (ret != 0) { + goto cleanup; + } + + for ( i = 16; i > 0; i-- ) { + if ( ++nonce_counter[i - 1] != 0 ) { + break; + } + } + } + c = *input++; + *output++ = (unsigned char)( c ^ stream_block[n] ); + + n = ( n + 1 ) & 0x0F; + } + + *nc_off = n; + ret = 0; + +cleanup: + esp_aes_release_hardware(); + return ret; +} + +/* + * AES-OFB (Output Feedback Mode) buffer encryption/decryption + */ +int esp_aes_crypt_ofb(esp_aes_context *ctx, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int ret = -1; + size_t n; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv_off) { + ESP_LOGE(TAG, "No IV offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + n = *iv_off; + + if (n > 15) { + return (MBEDTLS_ERR_AES_BAD_INPUT_DATA); + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + + + while (length--) { + if ( n == 0 ) { + ret = esp_aes_block(ctx, iv, iv); + if (ret != 0) { + goto cleanup; + } + } + *output++ = *input++ ^ iv[n]; + + n = ( n + 1 ) & 0x0F; + } + + *iv_off = n; + ret = 0; + +cleanup: + esp_aes_release_hardware(); + + return ( ret ); +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes.c b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes.c new file mode 100644 index 000000000..1dd940186 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes.c @@ -0,0 +1,1103 @@ +/** + * \brief AES block cipher, ESP DMA hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016-2020, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* + * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. + * + * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + */ + +#include +#include "mbedtls/aes.h" +#include "esp_intr_alloc.h" +#include "esp_private/periph_ctrl.h" +#include "esp_log.h" +#include "esp_attr.h" +#include "soc/lldesc.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "sys/param.h" +#if CONFIG_PM_ENABLE +#include "esp_pm.h" +#endif +#include "esp_crypto_lock.h" +#include "hal/aes_hal.h" +#include "aes/esp_aes_internal.h" +#include "esp_aes_dma_priv.h" + +#if CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/cache.h" +#endif + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#if SOC_AES_GDMA +#define AES_LOCK() esp_crypto_sha_aes_lock_acquire() +#define AES_RELEASE() esp_crypto_sha_aes_lock_release() +#elif SOC_AES_CRYPTO_DMA +#define AES_LOCK() esp_crypto_dma_lock_acquire() +#define AES_RELEASE() esp_crypto_dma_lock_release() +#endif + +/* Max size of each chunk to process when output buffer is in unaligned external ram + must be a multiple of block size +*/ +#define AES_MAX_CHUNK_WRITE_SIZE 1600 + +/* Input over this length will yield and wait for interrupt instead of + busy-waiting, 30000 bytes is approx 0.5 ms */ +#define AES_DMA_INTR_TRIG_LEN 2000 + +/* With buffers in PSRAM (worst condition) we still achieve a speed of 4 MB/s + thus a 2 second timeout value should be suffient for even very large buffers. + */ +#define AES_WAIT_INTR_TIMEOUT_MS 2000 + +#if defined(CONFIG_MBEDTLS_AES_USE_INTERRUPT) +static SemaphoreHandle_t op_complete_sem; +#if defined(CONFIG_PM_ENABLE) +static esp_pm_lock_handle_t s_pm_cpu_lock; +static esp_pm_lock_handle_t s_pm_sleep_lock; +#endif +#endif + +#if SOC_PSRAM_DMA_CAPABLE + +#if (CONFIG_ESP32S2_DATA_CACHE_LINE_16B || CONFIG_ESP32S3_DATA_CACHE_LINE_16B) +#define DCACHE_LINE_SIZE 16 +#elif (CONFIG_ESP32S2_DATA_CACHE_LINE_32B || CONFIG_ESP32S3_DATA_CACHE_LINE_32B) +#define DCACHE_LINE_SIZE 32 +#elif CONFIG_ESP32S3_DATA_CACHE_LINE_64B +#define DCACHE_LINE_SIZE 64 +#endif //(CONFIG_ESP32S2_DATA_CACHE_LINE_16B || CONFIG_ESP32S3_DATA_CACHE_LINE_16B) + +#endif //SOC_PSRAM_DMA_CAPABLE + +static const char *TAG = "esp-aes"; +static bool s_check_dma_capable(const void *p); + +/* These are static due to: + * * Must be in DMA capable memory, so stack is not a safe place to put them + * * To avoid having to malloc/free them for every DMA operation + */ +static DRAM_ATTR lldesc_t s_stream_in_desc; +static DRAM_ATTR lldesc_t s_stream_out_desc; +static DRAM_ATTR uint8_t s_stream_in[AES_BLOCK_BYTES]; +static DRAM_ATTR uint8_t s_stream_out[AES_BLOCK_BYTES]; + +static inline void esp_aes_wait_dma_done(lldesc_t *output) +{ + /* Wait for DMA write operation to complete */ + while (1) { + if ( esp_aes_dma_done(output) ) { + break; + } + } +} + +/* Append a descriptor to the chain, set head if chain empty */ +static inline void lldesc_append(lldesc_t **head, lldesc_t *item) +{ + lldesc_t *it; + if (*head == NULL) { + *head = item; + return; + } + + it = *head; + + while (it->empty != 0) { + it = (lldesc_t *)it->empty; + } + it->eof = 0; + it->empty = (uint32_t)item; +} + +void esp_aes_acquire_hardware( void ) +{ + /* Released by esp_aes_release_hardware()*/ + AES_LOCK(); + + /* Enable AES and DMA hardware */ +#if SOC_AES_CRYPTO_DMA + periph_module_enable(PERIPH_AES_DMA_MODULE); +#elif SOC_AES_GDMA + periph_module_enable(PERIPH_AES_MODULE); +#endif +} + +/* Function to disable AES and Crypto DMA clocks and release locks */ +void esp_aes_release_hardware( void ) +{ + /* Disable AES and DMA hardware */ +#if SOC_AES_CRYPTO_DMA + periph_module_disable(PERIPH_AES_DMA_MODULE); +#elif SOC_AES_GDMA + periph_module_disable(PERIPH_AES_MODULE); +#endif + + AES_RELEASE(); +} + + +#if defined (CONFIG_MBEDTLS_AES_USE_INTERRUPT) +static IRAM_ATTR void esp_aes_complete_isr(void *arg) +{ + BaseType_t higher_woken; + aes_hal_interrupt_clear(); + xSemaphoreGiveFromISR(op_complete_sem, &higher_woken); + if (higher_woken) { + portYIELD_FROM_ISR(); + } +} + +void esp_aes_intr_alloc(void) +{ + if (op_complete_sem == NULL) { + + esp_err_t ret = esp_intr_alloc(ETS_AES_INTR_SOURCE, 0, esp_aes_complete_isr, NULL, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate AES interrupt %d", ret); + // This should be treated as fatal error as this API would mostly + // be invoked within mbedTLS interface. There is no way for the system + // to proceed if the AES interrupt allocation fails here. + abort(); + } + + static StaticSemaphore_t op_sem_buf; + op_complete_sem = xSemaphoreCreateBinaryStatic(&op_sem_buf); + // Static semaphore creation is unlikley to fail but still basic sanity + assert(op_complete_sem != NULL); + } +} + +static esp_err_t esp_aes_isr_initialise( void ) +{ + aes_hal_interrupt_clear(); + aes_hal_interrupt_enable(true); + + /* AES is clocked proportionally to CPU clock, take power management lock */ +#ifdef CONFIG_PM_ENABLE + if (s_pm_cpu_lock == NULL) { + if (esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "aes_sleep", &s_pm_sleep_lock) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create PM sleep lock"); + return ESP_FAIL; + } + if (esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "aes_cpu", &s_pm_cpu_lock) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create PM CPU lock"); + return ESP_FAIL; + } + } + esp_pm_lock_acquire(s_pm_cpu_lock); + esp_pm_lock_acquire(s_pm_sleep_lock); +#endif + + return ESP_OK; +} +#endif // CONFIG_MBEDTLS_AES_USE_INTERRUPT + +/* Wait for AES hardware block operation to complete */ +static int esp_aes_dma_wait_complete(bool use_intr, lldesc_t *output_desc) +{ +#if defined (CONFIG_MBEDTLS_AES_USE_INTERRUPT) + if (use_intr) { + if (!xSemaphoreTake(op_complete_sem, AES_WAIT_INTR_TIMEOUT_MS / portTICK_PERIOD_MS)) { + /* indicates a fundamental problem with driver */ + ESP_LOGE(TAG, "Timed out waiting for completion of AES Interrupt"); + return -1; + } +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_cpu_lock); + esp_pm_lock_release(s_pm_sleep_lock); +#endif // CONFIG_PM_ENABLE + } +#endif + /* Checking this if interrupt is used also, to avoid + issues with AES fault injection + */ + aes_hal_wait_done(); + + esp_aes_wait_dma_done(output_desc); + return 0; +} + + +static int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, uint8_t *stream_out); + + +/* Output buffers in external ram needs to be 16-byte aligned and DMA cant access input in the iCache mem range, + reallocate them into internal memory and encrypt in chunks to avoid + having to malloc too big of a buffer + + The function esp_aes_process_dma_ext_ram zeroises the output buffer in the case of memory allocation failure. +*/ + +static int esp_aes_process_dma_ext_ram(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, uint8_t *stream_out, bool realloc_input, bool realloc_output) +{ + size_t chunk_len; + int ret = 0; + int offset = 0; + unsigned char *input_buf = NULL; + unsigned char *output_buf = NULL; + const unsigned char *dma_input; + chunk_len = MIN(AES_MAX_CHUNK_WRITE_SIZE, len); + + if (realloc_input) { + input_buf = heap_caps_malloc(chunk_len, MALLOC_CAP_DMA); + + if (input_buf == NULL) { + mbedtls_platform_zeroize(output, len); + ESP_LOGE(TAG, "Failed to allocate memory"); + return -1; + } + } + + if (realloc_output) { + output_buf = heap_caps_malloc(chunk_len, MALLOC_CAP_DMA); + + if (output_buf == NULL) { + mbedtls_platform_zeroize(output, len); + ESP_LOGE(TAG, "Failed to allocate memory"); + return -1; + } + } else { + output_buf = output; + } + + while (len) { + chunk_len = MIN(AES_MAX_CHUNK_WRITE_SIZE, len); + + /* If input needs realloc then copy it, else use the input with offset*/ + if (realloc_input) { + memcpy(input_buf, input + offset, chunk_len); + dma_input = input_buf; + } else { + dma_input = input + offset; + } + + if (esp_aes_process_dma(ctx, dma_input, output_buf, chunk_len, stream_out) != 0) { + ret = -1; + goto cleanup; + } + + if (realloc_output) { + memcpy(output + offset, output_buf, chunk_len); + } else { + output_buf = output + offset + chunk_len; + } + + len -= chunk_len; + offset += chunk_len; + } + +cleanup: + + if (realloc_input) { + free(input_buf); + } + if (realloc_output) { + free(output_buf); + } + + return ret; +} + +/* Encrypt/decrypt the input using DMA + * The function esp_aes_process_dma zeroises the output buffer in the case of following conditions: + * 1. If key is not written in the hardware + * 2. Memory allocation failures + * 3. If AES interrupt is enabled and ISR initialisation fails + * 4. Failure in any of the AES operations + */ +static int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, uint8_t *stream_out) +{ + lldesc_t *in_desc_head = NULL, *out_desc_head = NULL; + lldesc_t *out_desc_tail = NULL; /* pointer to the final output descriptor */ + lldesc_t *block_desc = NULL, *block_in_desc = NULL, *block_out_desc = NULL; + size_t lldesc_num; + unsigned stream_bytes = len % AES_BLOCK_BYTES; // bytes which aren't in a full block + unsigned block_bytes = len - stream_bytes; // bytes which are in a full block + unsigned blocks = (block_bytes / AES_BLOCK_BYTES) + ((stream_bytes > 0) ? 1 : 0); + bool use_intr = false; + bool input_needs_realloc = false; + bool output_needs_realloc = false; + int ret = 0; + + assert(len > 0); // caller shouldn't ever have len set to zero + assert(stream_bytes == 0 || stream_out != NULL); // stream_out can be NULL if we're processing full block(s) + + /* If no key is written to hardware yet, either the user hasn't called + mbedtls_aes_setkey_enc/mbedtls_aes_setkey_dec - meaning we also don't + know which mode to use - or a fault skipped the + key write to hardware. Treat this as a fatal error and zero the output block. + */ + if (ctx->key_in_hardware != ctx->key_bytes) { + mbedtls_platform_zeroize(output, len); + return MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH; + } + + if (block_bytes > 0) { + /* Flush cache if input in external ram */ +#if (CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE) + if (esp_ptr_external_ram(input)) { + Cache_WriteBack_Addr((uint32_t)input, len); + } + if (esp_ptr_external_ram(output)) { + if ((((intptr_t)(output) & (DCACHE_LINE_SIZE - 1)) != 0) || (block_bytes % DCACHE_LINE_SIZE != 0)) { + // Non aligned ext-mem buffer + output_needs_realloc = true; + } + } +#endif + /* DMA cannot access memory in the iCache range, copy input to internal ram */ + if (!s_check_dma_capable(input)) { + input_needs_realloc = true; + } + + if (!s_check_dma_capable(output)) { + output_needs_realloc = true; + } + + /* If either input or output is unaccessible to the DMA then they need to be reallocated */ + if (input_needs_realloc || output_needs_realloc) { + return esp_aes_process_dma_ext_ram(ctx, input, output, len, stream_out, input_needs_realloc, output_needs_realloc); + } + + /* Set up dma descriptors for input and output considering the 16 byte alignment requirement for EDMA */ + lldesc_num = lldesc_get_required_num_constrained(block_bytes, LLDESC_MAX_NUM_PER_DESC_16B_ALIGNED); + + /* Allocate both in and out descriptors to save a malloc/free per function call */ + block_desc = heap_caps_calloc(lldesc_num * 2, sizeof(lldesc_t), MALLOC_CAP_DMA); + if (block_desc == NULL) { + mbedtls_platform_zeroize(output, len); + ESP_LOGE(TAG, "Failed to allocate memory"); + return -1; + } + + block_in_desc = block_desc; + block_out_desc = block_desc + lldesc_num; + + lldesc_setup_link(block_in_desc, input, block_bytes, 0); + //Limit max inlink descriptor length to be 16 byte aligned, require for EDMA + lldesc_setup_link_constrained(block_out_desc, output, block_bytes, LLDESC_MAX_NUM_PER_DESC_16B_ALIGNED, 0); + + /* Setup in/out start descriptors */ + lldesc_append(&in_desc_head, block_in_desc); + lldesc_append(&out_desc_head, block_out_desc); + + out_desc_tail = &block_out_desc[lldesc_num - 1]; + } + + /* Any leftover bytes which are appended as an additional DMA list */ + if (stream_bytes > 0) { + + memset(&s_stream_in_desc, 0, sizeof(lldesc_t)); + memset(&s_stream_out_desc, 0, sizeof(lldesc_t)); + + memset(s_stream_in, 0, AES_BLOCK_BYTES); + memset(s_stream_out, 0, AES_BLOCK_BYTES); + + memcpy(s_stream_in, input + block_bytes, stream_bytes); + + lldesc_setup_link(&s_stream_in_desc, s_stream_in, AES_BLOCK_BYTES, 0); + lldesc_setup_link(&s_stream_out_desc, s_stream_out, AES_BLOCK_BYTES, 0); + + /* Link with block descriptors */ + lldesc_append(&in_desc_head, &s_stream_in_desc); + lldesc_append(&out_desc_head, &s_stream_out_desc); + + out_desc_tail = &s_stream_out_desc; + } + +#if defined (CONFIG_MBEDTLS_AES_USE_INTERRUPT) + /* Only use interrupt for long AES operations */ + if (len > AES_DMA_INTR_TRIG_LEN) { + use_intr = true; + if (esp_aes_isr_initialise() != ESP_OK) { + ESP_LOGE(TAG, "ESP-AES ISR initialisation failed"); + ret = -1; + goto cleanup; + } + } else +#endif + { + aes_hal_interrupt_enable(false); + } + + if (esp_aes_dma_start(in_desc_head, out_desc_head) != ESP_OK) { + ESP_LOGE(TAG, "esp_aes_dma_start failed, no DMA channel available"); + ret = -1; + goto cleanup; + } + + aes_hal_transform_dma_start(blocks); + + if (esp_aes_dma_wait_complete(use_intr, out_desc_tail) < 0) { + ESP_LOGE(TAG, "esp_aes_dma_wait_complete failed"); + ret = -1; + goto cleanup; + } + +#if (CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE) + if (block_bytes > 0) { + if (esp_ptr_external_ram(output)) { + Cache_Invalidate_Addr((uint32_t)output, block_bytes); + } + } +#endif + aes_hal_transform_dma_finish(); + + if (stream_bytes > 0) { + memcpy(output + block_bytes, s_stream_out, stream_bytes); + memcpy(stream_out, s_stream_out, AES_BLOCK_BYTES); + } + +cleanup: + if (ret != 0) { + mbedtls_platform_zeroize(output, len); + } + free(block_desc); + return ret; +} + + +#if CONFIG_MBEDTLS_HARDWARE_GCM + +/* Encrypt/decrypt with AES-GCM the input using DMA + * The function esp_aes_process_dma_gcm zeroises the output buffer in the case of following conditions: + * 1. If key is not written in the hardware + * 2. Memory allocation failures + * 3. If AES interrupt is enabled and ISR initialisation fails + * 4. Failure in any of the AES operations + */ +int esp_aes_process_dma_gcm(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, lldesc_t *aad_desc, size_t aad_len) +{ + lldesc_t *in_desc_head = NULL, *out_desc_head = NULL, *len_desc = NULL; + lldesc_t *out_desc_tail = NULL; /* pointer to the final output descriptor */ + lldesc_t stream_in_desc, stream_out_desc; + lldesc_t *block_desc = NULL, *block_in_desc = NULL, *block_out_desc = NULL; + size_t lldesc_num; + uint32_t len_buf[4] = {}; + uint8_t stream_in[16] = {}; + uint8_t stream_out[16] = {}; + unsigned stream_bytes = len % AES_BLOCK_BYTES; // bytes which aren't in a full block + unsigned block_bytes = len - stream_bytes; // bytes which are in a full block + + unsigned blocks = (block_bytes / AES_BLOCK_BYTES) + ((stream_bytes > 0) ? 1 : 0); + + bool use_intr = false; + int ret = 0; + + /* If no key is written to hardware yet, either the user hasn't called + mbedtls_aes_setkey_enc/mbedtls_aes_setkey_dec - meaning we also don't + know which mode to use - or a fault skipped the + key write to hardware. Treat this as a fatal error and zero the output block. + */ + if (ctx->key_in_hardware != ctx->key_bytes) { + mbedtls_platform_zeroize(output, len); + return MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* Set up dma descriptors for input and output */ + lldesc_num = lldesc_get_required_num(block_bytes); + + /* Allocate both in and out descriptors to save a malloc/free per function call, add 1 for length descriptor */ + block_desc = heap_caps_calloc( (lldesc_num * 2) + 1, sizeof(lldesc_t), MALLOC_CAP_DMA); + if (block_desc == NULL) { + mbedtls_platform_zeroize(output, len); + ESP_LOGE(TAG, "Failed to allocate memory"); + return -1; + } + + block_in_desc = block_desc; + len_desc = block_desc + lldesc_num; + block_out_desc = block_desc + lldesc_num + 1; + + if (aad_desc != NULL) { + lldesc_append(&in_desc_head, aad_desc); + } + + if (block_bytes > 0) { + lldesc_setup_link(block_in_desc, input, block_bytes, 0); + lldesc_setup_link(block_out_desc, output, block_bytes, 0); + + lldesc_append(&in_desc_head, block_in_desc); + lldesc_append(&out_desc_head, block_out_desc); + + out_desc_tail = &block_out_desc[lldesc_num - 1]; + } + + /* Any leftover bytes which are appended as an additional DMA list */ + if (stream_bytes > 0) { + memcpy(stream_in, input + block_bytes, stream_bytes); + + lldesc_setup_link(&stream_in_desc, stream_in, AES_BLOCK_BYTES, 0); + lldesc_setup_link(&stream_out_desc, stream_out, AES_BLOCK_BYTES, 0); + + lldesc_append(&in_desc_head, &stream_in_desc); + lldesc_append(&out_desc_head, &stream_out_desc); + + out_desc_tail = &stream_out_desc; + } + + + len_buf[1] = __builtin_bswap32(aad_len * 8); + len_buf[3] = __builtin_bswap32(len * 8); + + len_desc->length = sizeof(len_buf); + len_desc->size = sizeof(len_buf); + len_desc->owner = 1; + len_desc->eof = 1; + len_desc->buf = (uint8_t *)len_buf; + + lldesc_append(&in_desc_head, len_desc); + +#if defined (CONFIG_MBEDTLS_AES_USE_INTERRUPT) + /* Only use interrupt for long AES operations */ + if (len > AES_DMA_INTR_TRIG_LEN) { + use_intr = true; + if (esp_aes_isr_initialise() != ESP_OK) { + ESP_LOGE(TAG, "ESP-AES ISR initialisation failed"); + ret = -1; + goto cleanup; + } + } else +#endif + { + aes_hal_interrupt_enable(false); + } + + /* Start AES operation */ + if (esp_aes_dma_start(in_desc_head, out_desc_head) != ESP_OK) { + ESP_LOGE(TAG, "esp_aes_dma_start failed, no DMA channel available"); + ret = -1; + goto cleanup; + } + + aes_hal_transform_dma_gcm_start(blocks); + + if (esp_aes_dma_wait_complete(use_intr, out_desc_tail) < 0) { + ESP_LOGE(TAG, "esp_aes_dma_wait_complete failed"); + ret = -1; + goto cleanup; + } + + aes_hal_transform_dma_finish(); + + if (stream_bytes > 0) { + memcpy(output + block_bytes, stream_out, stream_bytes); + } + +cleanup: + if (ret != 0) { + mbedtls_platform_zeroize(output, len); + } + free(block_desc); + return ret; +} + +#endif //CONFIG_MBEDTLS_HARDWARE_GCM + +static int esp_aes_validate_input(esp_aes_context *ctx, const unsigned char *input, + unsigned char *output ) +{ + if (!ctx) { + ESP_LOGE(TAG, "No AES context supplied"); + return -1; + } + if (!input) { + ESP_LOGE(TAG, "No input supplied"); + return -1; + } + if (!output) { + ESP_LOGE(TAG, "No output supplied"); + return -1; + } + + return 0; +} + + +/* + * AES-ECB single block encryption + */ +int esp_internal_aes_encrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_ENCRYPT); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_ECB); + r = esp_aes_process_dma(ctx, input, output, AES_BLOCK_BYTES, NULL); + esp_aes_release_hardware(); + + return r; +} + +void esp_aes_encrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + esp_internal_aes_encrypt(ctx, input, output); +} + +/* + * AES-ECB single block decryption + */ +int esp_internal_aes_decrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_DECRYPT); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_ECB); + r = esp_aes_process_dma(ctx, input, output, AES_BLOCK_BYTES, NULL); + esp_aes_release_hardware(); + + return r; +} + +void esp_aes_decrypt(esp_aes_context *ctx, + const unsigned char input[16], + unsigned char output[16] ) +{ + esp_internal_aes_decrypt(ctx, input, output); +} + + +/* + * AES-ECB block encryption/decryption + */ +int esp_aes_crypt_ecb(esp_aes_context *ctx, + int mode, + const unsigned char input[16], + unsigned char output[16] ) +{ + int r = -1; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_ECB); + r = esp_aes_process_dma(ctx, input, output, AES_BLOCK_BYTES, NULL); + esp_aes_release_hardware(); + + return r; +} + +/* + * AES-CBC buffer encryption/decryption + */ +int esp_aes_crypt_cbc(esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int r = -1; + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + /* For CBC input length should be multiple of + * AES BLOCK BYTES + * */ + if ( (length % AES_BLOCK_BYTES) || (length == 0) ) { + return ERR_ESP_AES_INVALID_INPUT_LENGTH; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_CBC); + aes_hal_set_iv(iv); + + r = esp_aes_process_dma(ctx, input, output, length, NULL); + if (r != 0) { + goto cleanup; + } + + aes_hal_read_iv(iv); + +cleanup: + esp_aes_release_hardware(); + return r; +} + +/* + * AES-CFB8 buffer encryption/decryption + */ +int esp_aes_crypt_cfb8(esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + int r = -1; + unsigned char c; + unsigned char ov[17]; + size_t block_bytes = length - (length % AES_BLOCK_BYTES); + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + /* The DMA engine will only output correct IV if it runs + full blocks of input in CFB8 mode + */ + esp_aes_acquire_hardware(); + + if (block_bytes > 0) { + + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_CFB8); + aes_hal_set_iv(iv); + r = esp_aes_process_dma(ctx, input, output, block_bytes, NULL); + if (r != 0) { + goto cleanup; + } + + aes_hal_read_iv(iv); + + length -= block_bytes; + input += block_bytes; + output += block_bytes; + } + + // Process remaining bytes block-at-a-time in ECB mode + if (length > 0) { + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, MBEDTLS_AES_ENCRYPT); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_ECB); + + while ( length-- ) { + memcpy( ov, iv, 16 ); + + r = esp_aes_process_dma(ctx, iv, iv, AES_BLOCK_BYTES, NULL); + if (r != 0) { + goto cleanup; + } + + if ( mode == MBEDTLS_AES_DECRYPT ) { + ov[16] = *input; + } + + c = *output++ = ( iv[0] ^ *input++ ); + + if ( mode == MBEDTLS_AES_ENCRYPT ) { + ov[16] = c; + } + memcpy( iv, ov + 1, 16 ); + } + + } + r = 0; + +cleanup: + esp_aes_release_hardware(); + return r; +} + +/* + * AES-CFB128 buffer encryption/decryption + */ +int esp_aes_crypt_cfb128(esp_aes_context *ctx, + int mode, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) + +{ + uint8_t c; + size_t stream_bytes = 0; + size_t n; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv_off) { + ESP_LOGE(TAG, "No IV offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + n = *iv_off; + + /* First process the *iv_off bytes + * which are pending from the previous call to this API + */ + while (n > 0 && length > 0) { + if (mode == MBEDTLS_AES_ENCRYPT) { + iv[n] = *output++ = *input++ ^ iv[n]; + } else { + c = *input++; + *output++ = c ^ iv[n]; + iv[n] = c; + } + n = (n + 1) % AES_BLOCK_BYTES; + length--; + } + + + if (length > 0) { + stream_bytes = length % AES_BLOCK_BYTES; + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, mode); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_CFB128); + aes_hal_set_iv(iv); + + int r = esp_aes_process_dma(ctx, input, output, length, iv); + if (r != 0) { + esp_aes_release_hardware(); + return r; + } + + if (stream_bytes == 0) { + // if we didn't need the partial 'stream block' then the new IV is in the IV register + aes_hal_read_iv(iv); + } else { + // if we did process a final partial block the new IV is already processed via DMA (and has some bytes of output in it), + // In decrypt mode any partial bytes are output plaintext (iv ^ c) and need to be swapped back to ciphertext (as the next + // block uses ciphertext as its IV input) + // + // Note: It may be more efficient to not process the partial block via DMA in this case. + if (mode == MBEDTLS_AES_DECRYPT) { + memcpy(iv, input + length - stream_bytes, stream_bytes); + } + } + esp_aes_release_hardware(); + } + + *iv_off = n + stream_bytes; + return 0; +} + +/* + * AES-OFB (Output Feedback Mode) buffer encryption/decryption + */ + +int esp_aes_crypt_ofb(esp_aes_context *ctx, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ) +{ + size_t n; + size_t stream_bytes = 0; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!iv_off) { + ESP_LOGE(TAG, "No IV offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + n = *iv_off; + + /* If there is an offset then use the output of the previous AES block + (the updated IV) to calculate the new output */ + while (n > 0 && length > 0) { + *output++ = (*input++ ^ iv[n]); + n = (n + 1) & 0xF; + length--; + } + if (length > 0) { + stream_bytes = (length % AES_BLOCK_BYTES); + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_DECRYPT); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_OFB); + aes_hal_set_iv(iv); + + int r = esp_aes_process_dma(ctx, input, output, length, iv); + if (r != 0) { + esp_aes_release_hardware(); + return r; + } + + aes_hal_read_iv(iv); + esp_aes_release_hardware(); + } + + *iv_off = n + stream_bytes; + + return 0; +} + +/* + * AES-CTR buffer encryption/decryption + */ +int esp_aes_crypt_ctr(esp_aes_context *ctx, + size_t length, + size_t *nc_off, + unsigned char nonce_counter[16], + unsigned char stream_block[16], + const unsigned char *input, + unsigned char *output ) +{ + size_t n; + + if (esp_aes_validate_input(ctx, input, output)) { + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!stream_block) { + ESP_LOGE(TAG, "No stream supplied"); + return -1; + } + + if (!nonce_counter) { + ESP_LOGE(TAG, "No nonce supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + if (!nc_off) { + ESP_LOGE(TAG, "No nonce offset supplied"); + return MBEDTLS_ERR_AES_BAD_INPUT_DATA; + } + + n = *nc_off; + + if (!valid_key_length(ctx)) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + /* Process any unprocessed bytes left in stream block from + last operation */ + while (n > 0 && length > 0) { + *output++ = (unsigned char)(*input++ ^ stream_block[n]); + n = (n + 1) & 0xF; + length--; + } + + if (length > 0) { + + esp_aes_acquire_hardware(); + ctx->key_in_hardware = 0; + ctx->key_in_hardware = aes_hal_setkey(ctx->key, ctx->key_bytes, ESP_AES_DECRYPT); + + aes_hal_mode_init(ESP_AES_BLOCK_MODE_CTR); + aes_hal_set_iv(nonce_counter); + + int r = esp_aes_process_dma(ctx, input, output, length, stream_block); + + if (r != 0) { + esp_aes_release_hardware(); + return r; + } + + aes_hal_read_iv(nonce_counter); + + esp_aes_release_hardware(); + + } + *nc_off = n + (length % AES_BLOCK_BYTES); + + return 0; +} + +static bool s_check_dma_capable(const void *p) +{ + bool is_capable = false; +#if CONFIG_SPIRAM + is_capable |= esp_ptr_dma_ext_capable(p); +#endif + is_capable |= esp_ptr_dma_capable(p); + + return is_capable; +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_crypto_dma_impl.c b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_crypto_dma_impl.c new file mode 100644 index 000000000..df8fd4bf4 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_crypto_dma_impl.c @@ -0,0 +1,42 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include "esp_aes_dma_priv.h" + +#include "soc/soc_caps.h" +#include "soc/crypto_dma_reg.h" +#include "hal/crypto_dma_ll.h" + + +esp_err_t esp_aes_dma_start(const lldesc_t *input, const lldesc_t *output) +{ + crypto_dma_ll_reset(); + crypto_dma_ll_set_mode(CRYPTO_DMA_AES); + + /* Set descriptors, input to AES comes from outlink DMA and viceversa */ + crypto_dma_ll_outlink_set((uint32_t)input); + crypto_dma_ll_inlink_set((uint32_t)output); + + /* Start transfer */ + crypto_dma_ll_outlink_start(); + crypto_dma_ll_inlink_start(); + + return ESP_OK; +} + +bool esp_aes_dma_done(const lldesc_t *output) +{ + return (crypto_dma_ll_inlink_is_eof() && (output->owner == 0)); +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_gdma_impl.c b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_gdma_impl.c new file mode 100644 index 000000000..94e970709 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/dma/esp_aes_gdma_impl.c @@ -0,0 +1,27 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_aes_dma_priv.h" +#include "hal/gdma_ll.h" +#include "esp_crypto_shared_gdma.h" + +esp_err_t esp_aes_dma_start(const lldesc_t *input, const lldesc_t *output) +{ + return esp_crypto_shared_gdma_start(input, output, GDMA_TRIG_PERIPH_AES); +} + +bool esp_aes_dma_done(const lldesc_t *output) +{ + return (output->owner == 0); +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/dma/include/esp_aes_dma_priv.h b/components/mbedtls/mbedtls_v3/port/aes/dma/include/esp_aes_dma_priv.h new file mode 100644 index 000000000..d8ddac8d2 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/dma/include/esp_aes_dma_priv.h @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/lldesc.h" +#include "soc/soc_caps.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Start the DMA engine + * + * @param input AES input descriptor (outlink) + * @param output AES output descriptor (inlink) + * @return + * - ESP_OK: Successfully started the DMA + * - ESP_ERR_INVALID_STATE: No DMA channel available + */ +esp_err_t esp_aes_dma_start(const lldesc_t *input, const lldesc_t *output); + +/** + * @brief Check if the DMA engine is finished reading the result + * + * @param output AES output descriptor (inlink) + * @return + * - true: DMA finished + * - false: DMA not yet finished + */ +bool esp_aes_dma_done(const lldesc_t *output); + +/** + * @brief Allocate AES peripheral interrupt handler + */ +void esp_aes_intr_alloc(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/aes/esp_aes_common.c b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_common.c new file mode 100644 index 000000000..578ec6656 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_common.c @@ -0,0 +1,79 @@ +/* + * AES block cipher, ESP hardware accelerated version, common + * Based on mbedTLS FIPS-197 compliant version. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. + * + * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + */ +#include "sdkconfig.h" +#include "aes/esp_aes_internal.h" +#include "mbedtls/aes.h" +#include "hal/aes_hal.h" +#include "hal/aes_types.h" +#include "soc/soc_caps.h" +#include "mbedtls/error.h" + +#include +#include "mbedtls/platform.h" + +#if SOC_AES_SUPPORT_DMA +#include "esp_aes_dma_priv.h" +#endif + +bool valid_key_length(const esp_aes_context *ctx) +{ + bool valid_len = (ctx->key_bytes == AES_128_KEY_BYTES) || (ctx->key_bytes == AES_256_KEY_BYTES); + +#if SOC_AES_SUPPORT_AES_192 + valid_len |= ctx->key_bytes == AES_192_KEY_BYTES; +#endif + + return valid_len; +} + +void esp_aes_init(esp_aes_context *ctx) +{ + bzero(ctx, sizeof(esp_aes_context)); +#if SOC_AES_SUPPORT_DMA && CONFIG_MBEDTLS_AES_USE_INTERRUPT + esp_aes_intr_alloc(); +#endif +} + +void esp_aes_free( esp_aes_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + bzero( ctx, sizeof( esp_aes_context ) ); +} + +/* + * AES key schedule (same for encryption or decryption, as hardware handles schedule) + * + */ +int esp_aes_setkey( esp_aes_context *ctx, const unsigned char *key, + unsigned int keybits ) +{ +#if !SOC_AES_SUPPORT_AES_192 + if (keybits == 192) { + return MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; + } +#endif + if (keybits != 128 && keybits != 192 && keybits != 256) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + ctx->key_bytes = keybits / 8; + memcpy(ctx->key, key, ctx->key_bytes); + ctx->key_in_hardware = 0; + return 0; +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/esp_aes_gcm.c b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_gcm.c new file mode 100644 index 000000000..17c2ec84d --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_gcm.c @@ -0,0 +1,820 @@ +/* + * GCM block cipher, ESP DMA hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2024 Espressif Systems (Shanghai) CO LTD + */ +/* + * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. + * + * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + */ +#include + +#include "aes/esp_aes.h" +#include "aes/esp_aes_gcm.h" +#include "aes/esp_aes_internal.h" +#include "hal/aes_hal.h" + +#include "mbedtls/aes.h" +#include "mbedtls/error.h" +#include "mbedtls/gcm.h" + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "soc/soc_caps.h" +#include "soc/soc_memory_layout.h" + +#include "sdkconfig.h" + +#if SOC_AES_SUPPORT_DMA +#include "esp_aes_dma_priv.h" +#endif + +#define ESP_PUT_BE64(a, val) \ + do { \ + *(uint64_t*)(a) = __builtin_bswap64( (uint64_t)(val) ); \ + } while (0) + +/* For simplicity limit the maxium amount of aad bytes to a single DMA descriptor + This should cover all normal, e.g. mbedtls, use cases */ +#define ESP_AES_GCM_AAD_MAX_BYTES 4080 + +static const char *TAG = "esp-aes-gcm"; + +static void esp_gcm_ghash(esp_gcm_context *ctx, const unsigned char *x, size_t x_len, uint8_t *z); + +/* + * Calculates the Initial Counter Block, J0 + * and copies to to the esp_gcm_context + */ +static void esp_gcm_derive_J0(esp_gcm_context *ctx) +{ + uint8_t len_buf[16]; + + memset(ctx->J0, 0, AES_BLOCK_BYTES); + memset(len_buf, 0, AES_BLOCK_BYTES); + + /* If IV is 96 bits J0 = ( IV || 0^31 || 1 ) */ + if (ctx->iv_len == 12) { + memcpy(ctx->J0, ctx->iv, ctx->iv_len); + ctx->J0[AES_BLOCK_BYTES - 1] |= 1; + } else { + /* For IV != 96 bit, J0 = GHASH(IV || 0[s+64] || [len(IV)]64) */ + /* First calculate GHASH on IV */ + esp_gcm_ghash(ctx, ctx->iv, ctx->iv_len, ctx->J0); + /* Next create 128 bit block which is equal to + 64 bit 0 + iv length truncated to 64 bits */ + ESP_PUT_BE64(len_buf + 8, ctx->iv_len * 8); + /* Calculate GHASH on last block */ + esp_gcm_ghash(ctx, len_buf, 16, ctx->J0); + + + } +} + + +/* + * Increment J0 as per GCM spec, by applying the Standard Incrementing + Function INC_32 to it. + * j is the counter which needs to be incremented which is + * copied to ctx->J0 after incrementing + */ +static void increment32_j0(esp_gcm_context *ctx, uint8_t *j) +{ + uint8_t j_len = AES_BLOCK_BYTES; + memcpy(j, ctx->J0, AES_BLOCK_BYTES); + if (j) { + for (uint32_t i = j_len; i > (j_len - 4); i--) { + if (++j[i - 1] != 0) { + break; + } + } + memcpy(ctx->J0, j, AES_BLOCK_BYTES); + } +} + +/* Function to xor two data blocks */ +static void xor_data(uint8_t *d, const uint8_t *s) +{ + for (int i = 0; i < AES_BLOCK_BYTES; i++) { + d[i] ^= s[i]; + } +} + + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} +#endif + +/* Based on MbedTLS's implemenation + * + * Precompute small multiples of H, that is set + * HH[i] || HL[i] = H times i, + * where i is seen as a field element as in [MGV], ie high-order bits + * correspond to low powers of P. The result is stored in the same way, that + * is the high-order bit of HH corresponds to P^0 and the low-order bit of HL + * corresponds to P^127. + */ +static int gcm_gen_table( esp_gcm_context *ctx ) +{ + int i, j; + uint64_t hi, lo; + uint64_t vl, vh; + unsigned char *h; + + h = ctx->H; + + /* pack h as two 64-bits ints, big-endian */ + GET_UINT32_BE( hi, h, 0 ); + GET_UINT32_BE( lo, h, 4 ); + vh = (uint64_t) hi << 32 | lo; + + GET_UINT32_BE( hi, h, 8 ); + GET_UINT32_BE( lo, h, 12 ); + vl = (uint64_t) hi << 32 | lo; + + /* 8 = 1000 corresponds to 1 in GF(2^128) */ + ctx->HL[8] = vl; + ctx->HH[8] = vh; + + /* 0 corresponds to 0 in GF(2^128) */ + ctx->HH[0] = 0; + ctx->HL[0] = 0; + + for ( i = 4; i > 0; i >>= 1 ) { + uint32_t T = ( vl & 1 ) * 0xe1000000U; + vl = ( vh << 63 ) | ( vl >> 1 ); + vh = ( vh >> 1 ) ^ ( (uint64_t) T << 32); + + ctx->HL[i] = vl; + ctx->HH[i] = vh; + } + + for ( i = 2; i <= 8; i *= 2 ) { + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i; + vh = *HiH; + vl = *HiL; + for ( j = 1; j < i; j++ ) { + HiH[j] = vh ^ ctx->HH[j]; + HiL[j] = vl ^ ctx->HL[j]; + } + } + + return ( 0 ); +} +/* + * Shoup's method for multiplication use this table with + * last4[x] = x times P^128 + * where x and last4[x] are seen as elements of GF(2^128) as in [MGV] + */ +static const uint32_t last4[16] = { + 0x00000000, 0x1c200000, 0x38400000, 0x24600000, + 0x70800000, 0x6ca00000, 0x48c00000, 0x54e00000, + 0xe1000000, 0xfd200000, 0xd9400000, 0xc5600000, + 0x91800000, 0x8da00000, 0xa9c00000, 0xb5e00000 +}; +/* Based on MbedTLS's implemenation + * + * Sets output to x times H using the precomputed tables. + * x and output are seen as elements of GF(2^128) as in [MGV]. + */ +static void gcm_mult( esp_gcm_context *ctx, const unsigned char x[16], + unsigned char output[16] ) +{ + int i = 0; + unsigned char lo, hi, rem; + uint64_t zh, zl; + + lo = x[15] & 0xf; + hi = x[15] >> 4; + + zh = ctx->HH[lo]; + zl = ctx->HL[lo]; + + rem = (unsigned char) zl & 0xf; + zl = ( zh << 60 ) | ( zl >> 4 ); + zh = ( zh >> 4 ); + zh ^= (uint64_t) last4[rem] << 32; + zh ^= ctx->HH[hi]; + zl ^= ctx->HL[hi]; + + for ( i = 14; i >= 0; i-- ) { + lo = x[i] & 0xf; + hi = x[i] >> 4; + + rem = (unsigned char) zl & 0xf; + zl = ( zh << 60 ) | ( zl >> 4 ); + zh = ( zh >> 4 ); + zh ^= (uint64_t) last4[rem] << 32; + zh ^= ctx->HH[lo]; + zl ^= ctx->HL[lo]; + + rem = (unsigned char) zl & 0xf; + zl = ( zh << 60 ) | ( zl >> 4 ); + zh = ( zh >> 4 ); + zh ^= (uint64_t) last4[rem] << 32; + zh ^= ctx->HH[hi]; + zl ^= ctx->HL[hi]; + } + + PUT_UINT32_BE( zh >> 32, output, 0 ); + PUT_UINT32_BE( zh, output, 4 ); + PUT_UINT32_BE( zl >> 32, output, 8 ); + PUT_UINT32_BE( zl, output, 12 ); +} + + + +/* Update the key value in gcm context */ +int esp_aes_gcm_setkey( esp_gcm_context *ctx, + mbedtls_cipher_id_t cipher, + const unsigned char *key, + unsigned int keybits ) +{ + /* Fallback to software implementation of GCM operation when a non-AES + * cipher is selected, as we support hardware acceleration only for a + * GCM operation using AES cipher. + */ +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + mbedtls_gcm_free_soft(ctx->ctx_soft); + free(ctx->ctx_soft); + ctx->ctx_soft = NULL; + } + + if (cipher != MBEDTLS_CIPHER_ID_AES) { + ctx->ctx_soft = (mbedtls_gcm_context_soft*) malloc(sizeof(mbedtls_gcm_context_soft)); + if (ctx->ctx_soft == NULL) { + return MBEDTLS_ERR_CIPHER_ALLOC_FAILED; + } + mbedtls_gcm_init_soft(ctx->ctx_soft); + return mbedtls_gcm_setkey_soft(ctx->ctx_soft, cipher, key, keybits); + } +#endif + +#if !SOC_AES_SUPPORT_AES_192 + if (keybits == 192) { + return MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED; + } +#endif + if (keybits != 128 && keybits != 192 && keybits != 256) { + return MBEDTLS_ERR_AES_INVALID_KEY_LENGTH; + } + + + ctx->aes_ctx.key_bytes = keybits / 8; + + memcpy(ctx->aes_ctx.key, key, ctx->aes_ctx.key_bytes); + + return ( 0 ); +} + + +/* AES-GCM GHASH calculation z = GHASH(x) using h0 hash key +*/ +static void esp_gcm_ghash(esp_gcm_context *ctx, const unsigned char *x, size_t x_len, uint8_t *z) +{ + + uint8_t tmp[AES_BLOCK_BYTES]; + + memset(tmp, 0, AES_BLOCK_BYTES); + /* GHASH(X) is calculated on input string which is multiple of 128 bits + * If input string bit length is not multiple of 128 bits it needs to + * be padded by 0 + * + * Steps: + * 1. Let X1, X2, ... , Xm-1, Xm denote the unique sequence of blocks such + * that X = X1 || X2 || ... || Xm-1 || Xm. + * 2. Let Y0 be the “zero block,†0128. + * 3. Fori=1,...,m,letYi =(Yi-1 ^ Xi)•H. + * 4. Return Ym + */ + + /* If input bit string is >= 128 bits, process full 128 bit blocks */ + while (x_len >= AES_BLOCK_BYTES) { + + xor_data(z, x); + gcm_mult(ctx, z, z); + + x += AES_BLOCK_BYTES; + x_len -= AES_BLOCK_BYTES; + } + + /* If input bit string is not multiple of 128 create last 128 bit + * block by padding necessary 0s + */ + if (x_len) { + memcpy(tmp, x, x_len); + xor_data(z, tmp); + gcm_mult(ctx, z, z); + } +} + + +/* Function to init AES GCM context to zero */ +void esp_aes_gcm_init( esp_gcm_context *ctx) +{ + if (ctx == NULL) { + return; + } + + bzero(ctx, sizeof(esp_gcm_context)); + +#if SOC_AES_SUPPORT_DMA && CONFIG_MBEDTLS_AES_USE_INTERRUPT + esp_aes_intr_alloc(); +#endif + + ctx->gcm_state = ESP_AES_GCM_STATE_INIT; +} + +/* Function to clear AES-GCM context */ +void esp_aes_gcm_free( esp_gcm_context *ctx) +{ + if (ctx == NULL) { + return; + } +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + mbedtls_gcm_free_soft(ctx->ctx_soft); + free(ctx->ctx_soft); + /* Note that the value of ctx->ctx_soft should be NULL'ed out + and here it is taken care by the bzero call below */ + } +#endif + bzero(ctx, sizeof(esp_gcm_context)); +} + +/* Setup AES-GCM */ +int esp_aes_gcm_starts( esp_gcm_context *ctx, + int mode, + const unsigned char *iv, + size_t iv_len ) +{ + if (!ctx) { + ESP_LOGE(TAG, "No AES context supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_starts_soft(ctx->ctx_soft, mode, iv, iv_len); + } +#endif + + /* IV is limited to 2^32 bits, so 2^29 bytes */ + /* IV is not allowed to be zero length */ + if ( iv_len == 0 || + ( (uint32_t) iv_len ) >> 29 != 0 ) { + return ( MBEDTLS_ERR_GCM_BAD_INPUT ); + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + + /* Initialize AES-GCM context */ + memset(ctx->ghash, 0, sizeof(ctx->ghash)); + ctx->data_len = 0; + ctx->aad = NULL; + ctx->aad_len = 0; + + ctx->iv = iv; + ctx->iv_len = iv_len; + ctx->mode = mode; + + /* H and the lookup table are only generated once per ctx */ + if (ctx->gcm_state == ESP_AES_GCM_STATE_INIT) { + /* Lock the AES engine to calculate ghash key H in hardware */ +#if CONFIG_MBEDTLS_HARDWARE_GCM + esp_aes_acquire_hardware(); + ctx->aes_ctx.key_in_hardware = aes_hal_setkey(ctx->aes_ctx.key, ctx->aes_ctx.key_bytes, mode); + aes_hal_mode_init(ESP_AES_BLOCK_MODE_GCM); + + aes_hal_gcm_calc_hash(ctx->H); + + esp_aes_release_hardware(); +#else + memset(ctx->H, 0, sizeof(ctx->H)); + int ret = esp_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, ctx->H, ctx->H); + if (ret != 0) { + return ret; + } +#endif + gcm_gen_table(ctx); + } + + /* Once H is obtained we need to derive J0 (Initial Counter Block) */ + esp_gcm_derive_J0(ctx); + + /* The initial counter block keeps updating during the esp_gcm_update call + * however to calculate final authentication tag T we need original J0 + * so we make a copy here + */ + memcpy(ctx->ori_j0, ctx->J0, 16); + + ctx->gcm_state = ESP_AES_GCM_STATE_START; + + return ( 0 ); +} + +int esp_aes_gcm_update_ad( esp_gcm_context *ctx, + const unsigned char *aad, + size_t aad_len ) +{ + if (!ctx) { + ESP_LOGE(TAG, "No AES context supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_update_ad_soft(ctx->ctx_soft, aad, aad_len); + } +#endif + + /* AD are limited to 2^32 bits, so 2^29 bytes */ + if ( ( (uint32_t) aad_len ) >> 29 != 0 ) { + return ( MBEDTLS_ERR_GCM_BAD_INPUT ); + } + + if ( (aad_len > 0) && !aad) { + ESP_LOGE(TAG, "No aad supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + + if (ctx->gcm_state != ESP_AES_GCM_STATE_START) { + ESP_LOGE(TAG, "AES context in invalid state!"); + return -1; + } + + /* Initialise associated data */ + ctx->aad = aad; + ctx->aad_len = aad_len; + + esp_gcm_ghash(ctx, ctx->aad, ctx->aad_len, ctx->ghash); + + return ( 0 ); +} + +/* Perform AES-GCM operation */ +int esp_aes_gcm_update( esp_gcm_context *ctx, + const unsigned char *input, size_t input_length, + unsigned char *output, size_t output_size, + size_t *output_length ) +{ + if (!ctx) { + ESP_LOGE(TAG, "No GCM context supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_update_soft(ctx->ctx_soft, input, input_length, output, output_size, output_length); + } +#endif + + size_t nc_off = 0; + uint8_t nonce_counter[AES_BLOCK_BYTES] = {0}; + uint8_t stream[AES_BLOCK_BYTES] = {0}; + + if (!output_length) { + ESP_LOGE(TAG, "No output length supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + *output_length = input_length; + + if (!input) { + ESP_LOGE(TAG, "No input supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + if (!output) { + ESP_LOGE(TAG, "No output supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + + if ( output > input && (size_t) ( output - input ) < input_length ) { + return ( MBEDTLS_ERR_GCM_BAD_INPUT ); + } + /* If this is the first time esp_gcm_update is getting called + * calculate GHASH on aad and preincrement the ICB + */ + if (ctx->gcm_state == ESP_AES_GCM_STATE_START) { + /* Jo needs to be incremented first time, later the CTR + * operation will auto update it + */ + increment32_j0(ctx, nonce_counter); + ctx->gcm_state = ESP_AES_GCM_STATE_UPDATE; + } else if (ctx->gcm_state == ESP_AES_GCM_STATE_UPDATE) { + memcpy(nonce_counter, ctx->J0, AES_BLOCK_BYTES); + } + + /* Perform intermediate GHASH on "encrypted" data during decryption */ + if (ctx->mode == ESP_AES_DECRYPT) { + esp_gcm_ghash(ctx, input, input_length, ctx->ghash); + } + + /* Output = GCTR(J0, Input): Encrypt/Decrypt the input */ + int ret = esp_aes_crypt_ctr(&ctx->aes_ctx, input_length, &nc_off, nonce_counter, stream, input, output); + if (ret != 0) { + return ret; + } + + /* ICB gets auto incremented after GCTR operation here so update the context */ + memcpy(ctx->J0, nonce_counter, AES_BLOCK_BYTES); + + /* Keep updating the length counter for final tag calculation */ + ctx->data_len += input_length; + + /* Perform intermediate GHASH on "encrypted" data during encryption*/ + if (ctx->mode == ESP_AES_ENCRYPT) { + esp_gcm_ghash(ctx, output, input_length, ctx->ghash); + } + + return 0; +} + +/* Function to read the tag value */ +int esp_aes_gcm_finish( esp_gcm_context *ctx, + unsigned char *output, size_t output_size, + size_t *output_length, + unsigned char *tag, size_t tag_len ) +{ +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_finish_soft(ctx->ctx_soft, output, output_size, output_length, tag, tag_len); + } +#endif + size_t nc_off = 0; + uint8_t len_block[AES_BLOCK_BYTES] = {0}; + uint8_t stream[AES_BLOCK_BYTES] = {0}; + + if ( tag_len > 16 || tag_len < 4 ) { + return ( MBEDTLS_ERR_GCM_BAD_INPUT ); + } + + /* Calculate final GHASH on aad_len, data length */ + ESP_PUT_BE64(len_block, ctx->aad_len * 8); + ESP_PUT_BE64(len_block + 8, ctx->data_len * 8); + esp_gcm_ghash(ctx, len_block, AES_BLOCK_BYTES, ctx->ghash); + + /* Tag T = GCTR(J0, ) where T is truncated to tag_len */ + return esp_aes_crypt_ctr(&ctx->aes_ctx, tag_len, &nc_off, ctx->ori_j0, stream, ctx->ghash, tag); +} + +#if CONFIG_MBEDTLS_HARDWARE_GCM +/* Due to restrictions in the hardware (e.g. need to do the whole conversion in one go), + some combinations of inputs are not supported */ +static bool esp_aes_gcm_input_support_hw_accel(size_t length, const unsigned char *aad, size_t aad_len, + const unsigned char *input, unsigned char *output, uint8_t *stream_in) +{ + bool support_hw_accel = true; + + if (aad_len > ESP_AES_GCM_AAD_MAX_BYTES) { + support_hw_accel = false; + } else if (!esp_ptr_dma_capable(aad) && aad_len > 0) { + /* aad in non internal DMA memory */ + support_hw_accel = false; + } else if (!esp_ptr_dma_capable(input) && length > 0) { + /* input in non internal DMA memory */ + support_hw_accel = false; + } else if (!esp_ptr_dma_capable(output) && length > 0) { + /* output in non internal DMA memory */ + support_hw_accel = false; + } else if (!esp_ptr_dma_capable(stream_in)) { + /* Stream in (and therefor other descriptors and buffers that come from the stack) + in non internal DMA memory */ + support_hw_accel = false; + } else if (length == 0) { + support_hw_accel = false; + } + + + return support_hw_accel; +} +#endif + +static int esp_aes_gcm_crypt_and_tag_partial_hw( esp_gcm_context *ctx, + int mode, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *aad, + size_t aad_len, + const unsigned char *input, + unsigned char *output, + size_t tag_len, + unsigned char *tag ) +{ + int ret = 0; + size_t olen; + + if ( ( ret = esp_aes_gcm_starts( ctx, mode, iv, iv_len ) ) != 0 ) { + return ( ret ); + } + + if ( ( ret = esp_aes_gcm_update_ad( ctx, aad, aad_len ) ) != 0 ) { + return ( ret ); + } + + if ( ( ret = esp_aes_gcm_update( ctx, input, length, output, 0, &olen ) ) != 0 ) { + return ( ret ); + } + + if ( ( ret = esp_aes_gcm_finish( ctx, output, 0, &olen, tag, tag_len ) ) != 0 ) { + return ( ret ); + } + + return ret; +} + +int esp_aes_gcm_crypt_and_tag( esp_gcm_context *ctx, + int mode, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *aad, + size_t aad_len, + const unsigned char *input, + unsigned char *output, + size_t tag_len, + unsigned char *tag ) +{ + if (!ctx) { + ESP_LOGE(TAG, "No AES context supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_crypt_and_tag_soft(ctx->ctx_soft, mode, length, iv, iv_len, aad, aad_len, input, output, tag_len, tag); + } +#endif +#if CONFIG_MBEDTLS_HARDWARE_GCM + int ret; + lldesc_t aad_desc[2] = {}; + lldesc_t *aad_head_desc = NULL; + size_t remainder_bit; + uint8_t stream_in[AES_BLOCK_BYTES] = {}; + unsigned stream_bytes = aad_len % AES_BLOCK_BYTES; // bytes which aren't in a full block + unsigned block_bytes = aad_len - stream_bytes; // bytes which are in a full block + + /* Due to hardware limition only certain cases are fully supported in HW */ + if (!esp_aes_gcm_input_support_hw_accel(length, aad, aad_len, input, output, stream_in)) { + return esp_aes_gcm_crypt_and_tag_partial_hw(ctx, mode, length, iv, iv_len, aad, aad_len, input, output, tag_len, tag); + } + + /* Limit aad len to a single DMA descriptor to simplify DMA handling + In practice, e.g. with mbedtls the length of aad will always be short + */ + if (aad_len > LLDESC_MAX_NUM_PER_DESC) { + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + /* IV and AD are limited to 2^32 bits, so 2^29 bytes */ + /* IV is not allowed to be zero length */ + if ( iv_len == 0 || + ( (uint32_t) iv_len ) >> 29 != 0 || + ( (uint32_t) aad_len ) >> 29 != 0 ) { + return ( MBEDTLS_ERR_GCM_BAD_INPUT ); + } + + if (!iv) { + ESP_LOGE(TAG, "No IV supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + + if ( (aad_len > 0) && !aad) { + ESP_LOGE(TAG, "No aad supplied"); + return MBEDTLS_ERR_GCM_BAD_INPUT; + } + + /* Initialize AES-GCM context */ + memset(ctx->ghash, 0, sizeof(ctx->ghash)); + ctx->data_len = 0; + + ctx->iv = iv; + ctx->iv_len = iv_len; + ctx->aad = aad; + ctx->aad_len = aad_len; + ctx->mode = mode; + + esp_aes_acquire_hardware(); + ctx->aes_ctx.key_in_hardware = 0; + ctx->aes_ctx.key_in_hardware = aes_hal_setkey(ctx->aes_ctx.key, ctx->aes_ctx.key_bytes, mode); + + if (block_bytes > 0) { + aad_desc[0].length = block_bytes; + aad_desc[0].size = block_bytes; + aad_desc[0].owner = 1; + aad_desc[0].buf = aad; + } + + if (stream_bytes > 0) { + memcpy(stream_in, aad + block_bytes, stream_bytes); + + aad_desc[0].empty = (uint32_t)&aad_desc[1]; + aad_desc[1].length = AES_BLOCK_BYTES; + aad_desc[1].size = AES_BLOCK_BYTES; + aad_desc[1].owner = 1; + aad_desc[1].buf = stream_in; + } + + if (block_bytes > 0) { + aad_head_desc = &aad_desc[0]; + } else if (stream_bytes > 0) { + aad_head_desc = &aad_desc[1]; + } + + aes_hal_mode_init(ESP_AES_BLOCK_MODE_GCM); + + /* See TRM GCM chapter for description of this calculation */ + remainder_bit = (8 * length) % 128; + aes_hal_gcm_init( (aad_len + AES_BLOCK_BYTES - 1) / AES_BLOCK_BYTES, remainder_bit); + aes_hal_gcm_calc_hash(ctx->H); + + gcm_gen_table(ctx); + esp_gcm_derive_J0(ctx); + + aes_hal_gcm_set_j0(ctx->J0); + + ret = esp_aes_process_dma_gcm(&ctx->aes_ctx, input, output, length, aad_head_desc, aad_len); + if (ret != 0) { + esp_aes_release_hardware(); + return ret; + } + + aes_hal_gcm_read_tag(tag, tag_len); + + esp_aes_release_hardware(); + + return ( ret ); +#else + return esp_aes_gcm_crypt_and_tag_partial_hw(ctx, mode, length, iv, iv_len, aad, aad_len, input, output, tag_len, tag); +#endif +} + + +int esp_aes_gcm_auth_decrypt( esp_gcm_context *ctx, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *aad, + size_t aad_len, + const unsigned char *tag, + size_t tag_len, + const unsigned char *input, + unsigned char *output ) +{ +#if defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + if (ctx->ctx_soft != NULL) { + return mbedtls_gcm_auth_decrypt_soft(ctx->ctx_soft, length, iv, iv_len, aad, aad_len, tag, tag_len, input, output); + } +#endif + int ret; + unsigned char check_tag[16]; + size_t i; + int diff; + + if ( ( ret = esp_aes_gcm_crypt_and_tag( ctx, ESP_AES_DECRYPT, length, + iv, iv_len, aad, aad_len, + input, output, tag_len, check_tag ) ) != 0 ) { + return ( ret ); + } + + /* Check tag in "constant-time" */ + for ( diff = 0, i = 0; i < tag_len; i++ ) { + diff |= tag[i] ^ check_tag[i]; + } + + if ( diff != 0 ) { + bzero( output, length ); + return ( MBEDTLS_ERR_GCM_AUTH_FAILED ); + } + + return ( 0 ); +} diff --git a/components/mbedtls/mbedtls_v3/port/aes/esp_aes_xts.c b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_xts.c new file mode 100644 index 000000000..d91f17e4e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/aes/esp_aes_xts.c @@ -0,0 +1,284 @@ +/** + * \brief AES block cipher, ESP32-S2 hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016-2020, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* + * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. + * + * http://csrc.nist.gov/encryption/aes/rijndael/Rijndael.pdf + * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + */ + + +/* Below XTS implementation is copied aes.c of mbedtls library. + * When MBEDTLS_AES_ALT is defined mbedtls expects alternate + * definition of XTS functions to be available. Even if this + * could have been avoided, it is done for consistency reason. + */ + +#include +#include +#include +#include "mbedtls/aes.h" + +#include "aes/esp_aes.h" + +void esp_aes_xts_init( esp_aes_xts_context *ctx ) +{ + esp_aes_init( &ctx->crypt ); + esp_aes_init( &ctx->tweak ); +} + +void esp_aes_xts_free( esp_aes_xts_context *ctx ) +{ + esp_aes_free( &ctx->crypt ); + esp_aes_free( &ctx->tweak ); +} + +static int esp_aes_xts_decode_keys( const unsigned char *key, + unsigned int keybits, + const unsigned char **key1, + unsigned int *key1bits, + const unsigned char **key2, + unsigned int *key2bits ) +{ + const unsigned int half_keybits = keybits / 2; + const unsigned int half_keybytes = half_keybits / 8; + + switch ( keybits ) { + case 256: break; + case 512: break; + default : return ( MBEDTLS_ERR_AES_INVALID_KEY_LENGTH ); + } + + *key1bits = half_keybits; + *key2bits = half_keybits; + *key1 = &key[0]; + *key2 = &key[half_keybytes]; + + return 0; +} + +int esp_aes_xts_setkey_enc( esp_aes_xts_context *ctx, + const unsigned char *key, + unsigned int keybits) +{ + int ret; + const unsigned char *key1, *key2; + unsigned int key1bits, key2bits; + + ret = esp_aes_xts_decode_keys( key, keybits, &key1, &key1bits, + &key2, &key2bits ); + if ( ret != 0 ) { + return ( ret ); + } + + /* Set the tweak key. Always set tweak key for the encryption mode. */ + ret = esp_aes_setkey( &ctx->tweak, key2, key2bits ); + if ( ret != 0 ) { + return ( ret ); + } + + /* Set crypt key for encryption. */ + return esp_aes_setkey( &ctx->crypt, key1, key1bits ); +} + +int esp_aes_xts_setkey_dec( esp_aes_xts_context *ctx, + const unsigned char *key, + unsigned int keybits) +{ + int ret; + const unsigned char *key1, *key2; + unsigned int key1bits, key2bits; + + ret = esp_aes_xts_decode_keys( key, keybits, &key1, &key1bits, + &key2, &key2bits ); + if ( ret != 0 ) { + return ( ret ); + } + + /* Set the tweak key. Always set tweak key for encryption. */ + ret = esp_aes_setkey( &ctx->tweak, key2, key2bits ); + if ( ret != 0 ) { + return ( ret ); + } + + /* Set crypt key for decryption. */ + return esp_aes_setkey( &ctx->crypt, key1, key1bits ); +} + +/* Endianess with 64 bits values */ +#ifndef GET_UINT64_LE +#define GET_UINT64_LE(n,b,i) \ +{ \ + (n) = ( (uint64_t) (b)[(i) + 7] << 56 ) \ + | ( (uint64_t) (b)[(i) + 6] << 48 ) \ + | ( (uint64_t) (b)[(i) + 5] << 40 ) \ + | ( (uint64_t) (b)[(i) + 4] << 32 ) \ + | ( (uint64_t) (b)[(i) + 3] << 24 ) \ + | ( (uint64_t) (b)[(i) + 2] << 16 ) \ + | ( (uint64_t) (b)[(i) + 1] << 8 ) \ + | ( (uint64_t) (b)[(i) ] ); \ +} +#endif + +#ifndef PUT_UINT64_LE +#define PUT_UINT64_LE(n,b,i) \ +{ \ + (b)[(i) + 7] = (unsigned char) ( (n) >> 56 ); \ + (b)[(i) + 6] = (unsigned char) ( (n) >> 48 ); \ + (b)[(i) + 5] = (unsigned char) ( (n) >> 40 ); \ + (b)[(i) + 4] = (unsigned char) ( (n) >> 32 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) ] = (unsigned char) ( (n) ); \ +} +#endif + +/* + * GF(2^128) multiplication function + * + * This function multiplies a field element by x in the polynomial field + * representation. It uses 64-bit word operations to gain speed but compensates + * for machine endianess and hence works correctly on both big and little + * endian machines. + */ +static void esp_gf128mul_x_ble( unsigned char r[16], + const unsigned char x[16] ) +{ + uint64_t a, b, ra, rb; + + GET_UINT64_LE( a, x, 0 ); + GET_UINT64_LE( b, x, 8 ); + + ra = ( a << 1 ) ^ 0x0087 >> ( 8 - ( ( b >> 63 ) << 3 ) ); + rb = ( a >> 63 ) | ( b << 1 ); + + PUT_UINT64_LE( ra, r, 0 ); + PUT_UINT64_LE( rb, r, 8 ); +} + +/* + * AES-XTS buffer encryption/decryption + */ +int esp_aes_crypt_xts( esp_aes_xts_context *ctx, + int mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output ) +{ + int ret; + size_t blocks = length / 16; + size_t leftover = length % 16; + unsigned char tweak[16]; + unsigned char prev_tweak[16]; + unsigned char tmp[16]; + + /* Sectors must be at least 16 bytes. */ + if ( length < 16 ) { + return MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* NIST SP 80-38E disallows data units larger than 2**20 blocks. */ + if ( length > ( 1 << 20 ) * 16 ) { + return MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* Compute the tweak. */ + ret = esp_aes_crypt_ecb( &ctx->tweak, MBEDTLS_AES_ENCRYPT, + data_unit, tweak ); + if ( ret != 0 ) { + return ( ret ); + } + + while ( blocks-- ) { + size_t i; + + if ( leftover && ( mode == MBEDTLS_AES_DECRYPT ) && blocks == 0 ) { + /* We are on the last block in a decrypt operation that has + * leftover bytes, so we need to use the next tweak for this block, + * and this tweak for the lefover bytes. Save the current tweak for + * the leftovers and then update the current tweak for use on this, + * the last full block. */ + memcpy( prev_tweak, tweak, sizeof( tweak ) ); + esp_gf128mul_x_ble( tweak, tweak ); + } + + for ( i = 0; i < 16; i++ ) { + tmp[i] = input[i] ^ tweak[i]; + } + + ret = esp_aes_crypt_ecb( &ctx->crypt, mode, tmp, tmp ); + if ( ret != 0 ) { + return ( ret ); + } + + for ( i = 0; i < 16; i++ ) { + output[i] = tmp[i] ^ tweak[i]; + } + + /* Update the tweak for the next block. */ + esp_gf128mul_x_ble( tweak, tweak ); + + output += 16; + input += 16; + } + + if ( leftover ) { + /* If we are on the leftover bytes in a decrypt operation, we need to + * use the previous tweak for these bytes (as saved in prev_tweak). */ + unsigned char *t = mode == MBEDTLS_AES_DECRYPT ? prev_tweak : tweak; + + /* We are now on the final part of the data unit, which doesn't divide + * evenly by 16. It's time for ciphertext stealing. */ + size_t i; + unsigned char *prev_output = output - 16; + + /* Copy ciphertext bytes from the previous block to our output for each + * byte of cyphertext we won't steal. At the same time, copy the + * remainder of the input for this final round (since the loop bounds + * are the same). */ + for ( i = 0; i < leftover; i++ ) { + output[i] = prev_output[i]; + tmp[i] = input[i] ^ t[i]; + } + + /* Copy ciphertext bytes from the previous block for input in this + * round. */ + for ( ; i < 16; i++ ) { + tmp[i] = prev_output[i] ^ t[i]; + } + + ret = esp_aes_crypt_ecb( &ctx->crypt, mode, tmp, tmp ); + if ( ret != 0 ) { + return ret; + } + + /* Write the result back to the previous block, overriding the previous + * output we copied. */ + for ( i = 0; i < 16; i++ ) { + prev_output[i] = tmp[i] ^ t[i]; + } + } + + return ( 0 ); +} diff --git a/components/mbedtls/mbedtls_v3/port/crypto_shared_gdma/esp_crypto_shared_gdma.c b/components/mbedtls/mbedtls_v3/port/crypto_shared_gdma/esp_crypto_shared_gdma.c new file mode 100644 index 000000000..b9899c344 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/crypto_shared_gdma/esp_crypto_shared_gdma.c @@ -0,0 +1,148 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_crypto_shared_gdma.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "hal/gdma_ll.h" +#include "soc/soc_caps.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_crypto_lock.h" + +#define NEW_CHANNEL_TIMEOUT_MS 1000 +#define NEW_CHANNEL_DELAY_MS 100 + +static const char *TAG = "crypto_shared_gdma"; + +static gdma_channel_handle_t rx_channel; +static gdma_channel_handle_t tx_channel; + +/* Allocate a new GDMA channel, will keep trying until NEW_CHANNEL_TIMEOUT_MS */ +static inline esp_err_t crypto_shared_gdma_new_channel(gdma_channel_alloc_config_t *channel_config, gdma_channel_handle_t *channel) +{ + esp_err_t ret; + int time_waited_ms = 0; + + while (1) { + ret = gdma_new_channel(channel_config, channel); + + if (ret == ESP_OK) { + break; + } else if (time_waited_ms >= NEW_CHANNEL_TIMEOUT_MS) { + *channel = NULL; + break; + } + + time_waited_ms += NEW_CHANNEL_DELAY_MS; + vTaskDelay(NEW_CHANNEL_DELAY_MS / portTICK_PERIOD_MS); + } + return ret; +} + +/* Initialize GDMA module and channels */ +static esp_err_t crypto_shared_gdma_init(void) +{ + esp_err_t ret; + + gdma_channel_alloc_config_t channel_config_tx = { + .direction = GDMA_CHANNEL_DIRECTION_TX, + }; + + gdma_channel_alloc_config_t channel_config_rx = { + .direction = GDMA_CHANNEL_DIRECTION_RX, + }; + + gdma_transfer_ability_t transfer_ability = { + .sram_trans_align = 1, + }; + + + ret = crypto_shared_gdma_new_channel(&channel_config_tx, &tx_channel); + if (ret != ESP_OK) { + goto err; + } + + ret = crypto_shared_gdma_new_channel(&channel_config_rx, &rx_channel); + if (ret != ESP_OK) { + gdma_del_channel(tx_channel); // Clean up already allocated TX channel + goto err; + } + + + gdma_set_transfer_ability(tx_channel, &transfer_ability); + gdma_set_transfer_ability(rx_channel, &transfer_ability); + + gdma_connect(rx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_AES, 0)); + gdma_connect(tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_AES, 0)); + + return ESP_OK; + +err: + ESP_LOGE(TAG, "Failed to acquire DMA channel, Err=%d", ret); + tx_channel = NULL; + rx_channel = NULL; + + return ret; +} + + +esp_err_t esp_crypto_shared_gdma_start(const lldesc_t *input, const lldesc_t *output, gdma_trigger_peripheral_t peripheral) +{ + int rx_ch_id = 0; + esp_err_t ret = ESP_OK; + + if (tx_channel == NULL) { + /* Allocate a pair of RX and TX for crypto, should only happen the first time we use the GMDA + or if user called esp_crypto_shared_gdma_release */ + ret = crypto_shared_gdma_init(); + } + + if (ret != ESP_OK) { + return ret; + } + + /* Tx channel is shared between AES and SHA, need to connect to peripheral every time */ + gdma_disconnect(tx_channel); + + if (peripheral == GDMA_TRIG_PERIPH_SHA) { + gdma_connect(tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SHA, 0)); + } else if (peripheral == GDMA_TRIG_PERIPH_AES) { + gdma_connect(tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_AES, 0)); + } else { + return ESP_ERR_INVALID_ARG; + } + + /* tx channel is reset by gdma_connect(), also reset rx to ensure a known state */ + gdma_get_channel_id(rx_channel, &rx_ch_id); + gdma_ll_rx_reset_channel(&GDMA, rx_ch_id); + + gdma_start(tx_channel, (intptr_t)input); + gdma_start(rx_channel, (intptr_t)output); + + return ESP_OK; +} + +void esp_crypto_shared_gdma_free() +{ + esp_crypto_sha_aes_lock_acquire(); + + if (rx_channel != NULL) { + gdma_disconnect(rx_channel); + gdma_del_channel(rx_channel); + rx_channel = NULL; + } + + if (tx_channel != NULL) { + gdma_disconnect(tx_channel); + gdma_del_channel(tx_channel); + tx_channel = NULL; + } + + esp_crypto_sha_aes_lock_release(); +} diff --git a/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.c b/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.c new file mode 100644 index 000000000..d76f6b504 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.c @@ -0,0 +1,555 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_mbedtls_dynamic_impl.h" +#include "sdkconfig.h" + +#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE +#include "esp_crt_bundle.h" +#endif + +#define COUNTER_SIZE (8) +#define CACHE_IV_SIZE (16) +#define CACHE_BUFFER_SIZE (CACHE_IV_SIZE + COUNTER_SIZE) + +#define TX_IDLE_BUFFER_SIZE (MBEDTLS_SSL_HEADER_LEN + CACHE_BUFFER_SIZE) + +static const char *TAG = "Dynamic Impl"; + +static void esp_mbedtls_set_buf_state(unsigned char *buf, esp_mbedtls_ssl_buf_states state) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + temp->state = state; +} + +static esp_mbedtls_ssl_buf_states esp_mbedtls_get_buf_state(unsigned char *buf) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + return temp->state; +} + +void esp_mbedtls_free_buf(unsigned char *buf) +{ + struct esp_mbedtls_ssl_buf *temp = __containerof(buf, struct esp_mbedtls_ssl_buf, buf[0]); + ESP_LOGV(TAG, "free buffer @ %p", temp); + mbedtls_free(temp); +} + +static void esp_mbedtls_init_ssl_buf(struct esp_mbedtls_ssl_buf *buf, unsigned int len) +{ + if (buf) { + buf->state = ESP_MBEDTLS_SSL_BUF_CACHED; + buf->len = len; + } +} + +static void esp_mbedtls_parse_record_header(mbedtls_ssl_context *ssl) +{ + ssl->MBEDTLS_PRIVATE(in_msgtype) = ssl->MBEDTLS_PRIVATE(in_hdr)[0]; + ssl->MBEDTLS_PRIVATE(in_msglen) = (ssl->MBEDTLS_PRIVATE(in_len)[0] << 8) | ssl->MBEDTLS_PRIVATE(in_len)[1]; +} + +static int tx_buffer_len(mbedtls_ssl_context *ssl, int len) +{ + (void)ssl; + + if (!len) { + return MBEDTLS_SSL_OUT_BUFFER_LEN; + } else { + return len + MBEDTLS_SSL_HEADER_LEN + + MBEDTLS_MAX_IV_LENGTH + + MBEDTLS_SSL_MAC_ADD + + MBEDTLS_SSL_PADDING_ADD + + MBEDTLS_SSL_MAX_CID_EXPANSION; + } +} + +static void init_tx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) +{ + /** + * In mbedtls, ssl->MBEDTLS_PRIVATE(out_msg) = ssl->MBEDTLS_PRIVATE(out_buf) + offset; + */ + if (!buf) { + int out_msg_off = (int)ssl->MBEDTLS_PRIVATE(out_msg) - (int)ssl->MBEDTLS_PRIVATE(out_buf); + + if (!out_msg_off) { + out_msg_off = MBEDTLS_SSL_HEADER_LEN; + } + + ssl->MBEDTLS_PRIVATE(out_buf) = NULL; + ssl->MBEDTLS_PRIVATE(out_ctr) = NULL; + ssl->MBEDTLS_PRIVATE(out_hdr) = NULL; + ssl->MBEDTLS_PRIVATE(out_len) = NULL; + ssl->MBEDTLS_PRIVATE(out_iv) = NULL; + ssl->MBEDTLS_PRIVATE(out_msg) = (unsigned char *)out_msg_off; + } else { + int out_msg_off = (int)ssl->MBEDTLS_PRIVATE(out_msg); + + ssl->MBEDTLS_PRIVATE(out_buf) = buf; + ssl->MBEDTLS_PRIVATE(out_ctr) = ssl->MBEDTLS_PRIVATE(out_buf); + ssl->MBEDTLS_PRIVATE(out_hdr) = ssl->MBEDTLS_PRIVATE(out_buf) + 8; + ssl->MBEDTLS_PRIVATE(out_len) = ssl->MBEDTLS_PRIVATE(out_buf) + 11; + ssl->MBEDTLS_PRIVATE(out_iv) = ssl->MBEDTLS_PRIVATE(out_buf) + MBEDTLS_SSL_HEADER_LEN; + ssl->MBEDTLS_PRIVATE(out_msg) = ssl->MBEDTLS_PRIVATE(out_buf) + out_msg_off; + + ESP_LOGV(TAG, "out msg offset is %d", out_msg_off); + } + + ssl->MBEDTLS_PRIVATE(out_msgtype) = 0; + ssl->MBEDTLS_PRIVATE(out_msglen) = 0; + ssl->MBEDTLS_PRIVATE(out_left) = 0; +} + +static void init_rx_buffer(mbedtls_ssl_context *ssl, unsigned char *buf) +{ + /** + * In mbedtls, ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) + offset; + */ + if (!buf) { + int in_msg_off = (int)ssl->MBEDTLS_PRIVATE(in_msg) - (int)ssl->MBEDTLS_PRIVATE(in_buf); + + if (!in_msg_off) { + in_msg_off = MBEDTLS_SSL_HEADER_LEN; + } + + ssl->MBEDTLS_PRIVATE(in_buf) = NULL; + ssl->MBEDTLS_PRIVATE(in_ctr) = NULL; + ssl->MBEDTLS_PRIVATE(in_hdr) = NULL; + ssl->MBEDTLS_PRIVATE(in_len) = NULL; + ssl->MBEDTLS_PRIVATE(in_iv) = NULL; + ssl->MBEDTLS_PRIVATE(in_msg) = (unsigned char *)in_msg_off; + } else { + int in_msg_off = (int)ssl->MBEDTLS_PRIVATE(in_msg); + + ssl->MBEDTLS_PRIVATE(in_buf) = buf; + ssl->MBEDTLS_PRIVATE(in_ctr) = ssl->MBEDTLS_PRIVATE(in_buf); + ssl->MBEDTLS_PRIVATE(in_hdr) = ssl->MBEDTLS_PRIVATE(in_buf) + 8; + ssl->MBEDTLS_PRIVATE(in_len) = ssl->MBEDTLS_PRIVATE(in_buf) + 11; + ssl->MBEDTLS_PRIVATE(in_iv) = ssl->MBEDTLS_PRIVATE(in_buf) + MBEDTLS_SSL_HEADER_LEN; + ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) + in_msg_off; + + ESP_LOGV(TAG, "in msg offset is %d", in_msg_off); + } + + ssl->MBEDTLS_PRIVATE(in_msgtype) = 0; + ssl->MBEDTLS_PRIVATE(in_msglen) = 0; + ssl->MBEDTLS_PRIVATE(in_left) = 0; +} + +static int esp_mbedtls_alloc_tx_buf(mbedtls_ssl_context *ssl, int len) +{ + struct esp_mbedtls_ssl_buf *esp_buf; + + if (ssl->MBEDTLS_PRIVATE(out_buf)) { + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(out_buf)); + ssl->MBEDTLS_PRIVATE(out_buf) = NULL; + } + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + len); + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + ESP_LOGV(TAG, "add out buffer %d bytes @ %p", len, esp_buf->buf); + + esp_mbedtls_init_ssl_buf(esp_buf, len); + /** + * Mark the out_msg offset from ssl->MBEDTLS_PRIVATE(out_buf). + * + * In mbedtls, ssl->MBEDTLS_PRIVATE(out_msg) = ssl->MBEDTLS_PRIVATE(out_buf) + offset; + */ + ssl->MBEDTLS_PRIVATE(out_msg) = (unsigned char *)MBEDTLS_SSL_HEADER_LEN; + + init_tx_buffer(ssl, esp_buf->buf); + + return 0; +} + +int esp_mbedtls_setup_tx_buffer(mbedtls_ssl_context *ssl) +{ + CHECK_OK(esp_mbedtls_alloc_tx_buf(ssl, TX_IDLE_BUFFER_SIZE)); + + /* mark the out buffer has no data cached */ + esp_mbedtls_set_buf_state(ssl->MBEDTLS_PRIVATE(out_buf), ESP_MBEDTLS_SSL_BUF_NO_CACHED); + + return 0; +} + +void esp_mbedtls_setup_rx_buffer(mbedtls_ssl_context *ssl) +{ + ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) = NULL; + init_rx_buffer(ssl, NULL); +} + +int esp_mbedtls_reset_add_tx_buffer(mbedtls_ssl_context *ssl) +{ + return esp_mbedtls_alloc_tx_buf(ssl, MBEDTLS_SSL_OUT_BUFFER_LEN); +} + +int esp_mbedtls_reset_free_tx_buffer(mbedtls_ssl_context *ssl) +{ + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(out_buf)); + init_tx_buffer(ssl, NULL); + + CHECK_OK(esp_mbedtls_setup_tx_buffer(ssl)); + + return 0; +} + +int esp_mbedtls_reset_add_rx_buffer(mbedtls_ssl_context *ssl) +{ + struct esp_mbedtls_ssl_buf *esp_buf; + + if (ssl->MBEDTLS_PRIVATE(in_buf)) { + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(in_buf)); + ssl->MBEDTLS_PRIVATE(in_buf) = NULL; + } + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + MBEDTLS_SSL_IN_BUFFER_LEN); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + MBEDTLS_SSL_IN_BUFFER_LEN); + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + ESP_LOGV(TAG, "add in buffer %d bytes @ %p", MBEDTLS_SSL_IN_BUFFER_LEN, esp_buf->buf); + + esp_mbedtls_init_ssl_buf(esp_buf, MBEDTLS_SSL_IN_BUFFER_LEN); + /** + * Mark the in_msg offset from ssl->MBEDTLS_PRIVATE(in_buf). + * + * In mbedtls, ssl->MBEDTLS_PRIVATE(in_msg) = ssl->MBEDTLS_PRIVATE(in_buf) + offset; + */ + ssl->MBEDTLS_PRIVATE(in_msg) = (unsigned char *)MBEDTLS_SSL_HEADER_LEN; + + init_rx_buffer(ssl, esp_buf->buf); + + return 0; +} + +void esp_mbedtls_reset_free_rx_buffer(mbedtls_ssl_context *ssl) +{ + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(in_buf)); + init_rx_buffer(ssl, NULL); +} + +int esp_mbedtls_add_tx_buffer(mbedtls_ssl_context *ssl, size_t buffer_len) +{ + int ret = 0; + int cached = 0; + struct esp_mbedtls_ssl_buf *esp_buf; + unsigned char cache_buf[CACHE_BUFFER_SIZE]; + + ESP_LOGV(TAG, "--> add out"); + + if (ssl->MBEDTLS_PRIVATE(out_buf)) { + if (esp_mbedtls_get_buf_state(ssl->MBEDTLS_PRIVATE(out_buf)) == ESP_MBEDTLS_SSL_BUF_CACHED) { + ESP_LOGV(TAG, "out buffer is not empty"); + ret = 0; + goto exit; + } else { + memcpy(cache_buf, ssl->MBEDTLS_PRIVATE(out_buf), CACHE_BUFFER_SIZE); + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(out_buf)); + init_tx_buffer(ssl, NULL); + cached = 1; + } + } + + buffer_len = tx_buffer_len(ssl, buffer_len); + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%zu bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; + goto exit; + } + + ESP_LOGV(TAG, "add out buffer %zu bytes @ %p", buffer_len, esp_buf->buf); + + esp_mbedtls_init_ssl_buf(esp_buf, buffer_len); + init_tx_buffer(ssl, esp_buf->buf); + + if (cached) { + memcpy(ssl->MBEDTLS_PRIVATE(out_ctr), cache_buf, COUNTER_SIZE); + memcpy(ssl->MBEDTLS_PRIVATE(out_iv), cache_buf + COUNTER_SIZE, CACHE_IV_SIZE); + } + + ESP_LOGV(TAG, "ssl->MBEDTLS_PRIVATE(out_buf)=%p ssl->MBEDTLS_PRIVATE(out_msg)=%p", ssl->MBEDTLS_PRIVATE(out_buf), ssl->MBEDTLS_PRIVATE(out_msg)); + +exit: + ESP_LOGV(TAG, "<-- add out"); + + return ret; +} + + +int esp_mbedtls_free_tx_buffer(mbedtls_ssl_context *ssl) +{ + int ret = 0; + unsigned char buf[CACHE_BUFFER_SIZE]; + struct esp_mbedtls_ssl_buf *esp_buf; + + ESP_LOGV(TAG, "--> free out"); + + if (!ssl->MBEDTLS_PRIVATE(out_buf) || (ssl->MBEDTLS_PRIVATE(out_buf) && (esp_mbedtls_get_buf_state(ssl->MBEDTLS_PRIVATE(out_buf)) == ESP_MBEDTLS_SSL_BUF_NO_CACHED))) { + ret = 0; + goto exit; + } + + memcpy(buf, ssl->MBEDTLS_PRIVATE(out_ctr), COUNTER_SIZE); + memcpy(buf + COUNTER_SIZE, ssl->MBEDTLS_PRIVATE(out_iv), CACHE_IV_SIZE); + + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(out_buf)); + init_tx_buffer(ssl, NULL); + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + TX_IDLE_BUFFER_SIZE); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + TX_IDLE_BUFFER_SIZE); + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + + esp_mbedtls_init_ssl_buf(esp_buf, TX_IDLE_BUFFER_SIZE); + memcpy(esp_buf->buf, buf, CACHE_BUFFER_SIZE); + init_tx_buffer(ssl, esp_buf->buf); + esp_mbedtls_set_buf_state(ssl->MBEDTLS_PRIVATE(out_buf), ESP_MBEDTLS_SSL_BUF_NO_CACHED); +exit: + ESP_LOGV(TAG, "<-- free out"); + + return ret; +} + +int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl) +{ + int cached = 0; + int ret = 0; + int buffer_len; + struct esp_mbedtls_ssl_buf *esp_buf; + unsigned char cache_buf[16]; + unsigned char msg_head[5]; + size_t in_msglen, in_left; + + ESP_LOGV(TAG, "--> add rx"); + + if (ssl->MBEDTLS_PRIVATE(in_buf)) { + if (esp_mbedtls_get_buf_state(ssl->MBEDTLS_PRIVATE(in_buf)) == ESP_MBEDTLS_SSL_BUF_CACHED) { + ESP_LOGV(TAG, "in buffer is not empty"); + ret = 0; + goto exit; + } else { + cached = 1; + } + } + + ssl->MBEDTLS_PRIVATE(in_hdr) = msg_head; + ssl->MBEDTLS_PRIVATE(in_len) = msg_head + 3; + + if ((ret = mbedtls_ssl_fetch_input(ssl, mbedtls_ssl_in_hdr_len(ssl))) != 0) { + if (ret == MBEDTLS_ERR_SSL_TIMEOUT) { + ESP_LOGD(TAG, "mbedtls_ssl_fetch_input reads data times out"); + } else if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + ESP_LOGD(TAG, "mbedtls_ssl_fetch_input wants to read more data"); + } else { + ESP_LOGE(TAG, "mbedtls_ssl_fetch_input error=%d", -ret); + } + + goto exit; + } + + esp_mbedtls_parse_record_header(ssl); + + in_left = ssl->MBEDTLS_PRIVATE(in_left); + in_msglen = ssl->MBEDTLS_PRIVATE(in_msglen); + buffer_len = tx_buffer_len(ssl, in_msglen); + + ESP_LOGV(TAG, "message length is %d RX buffer length should be %d left is %d", + (int)in_msglen, (int)buffer_len, (int)ssl->MBEDTLS_PRIVATE(in_left)); + + if (cached) { + memcpy(cache_buf, ssl->MBEDTLS_PRIVATE(in_buf), 16); + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(in_buf)); + init_rx_buffer(ssl, NULL); + } + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + buffer_len); + ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; + goto exit; + } + + ESP_LOGV(TAG, "add in buffer %d bytes @ %p", buffer_len, esp_buf->buf); + + esp_mbedtls_init_ssl_buf(esp_buf, buffer_len); + init_rx_buffer(ssl, esp_buf->buf); + + if (cached) { + memcpy(ssl->MBEDTLS_PRIVATE(in_ctr), cache_buf, 8); + memcpy(ssl->MBEDTLS_PRIVATE(in_iv), cache_buf + 8, 8); + } + + memcpy(ssl->MBEDTLS_PRIVATE(in_hdr), msg_head, in_left); + ssl->MBEDTLS_PRIVATE(in_left) = in_left; + ssl->MBEDTLS_PRIVATE(in_msglen) = 0; + +exit: + ESP_LOGV(TAG, "<-- add rx"); + + return ret; +} + +int esp_mbedtls_free_rx_buffer(mbedtls_ssl_context *ssl) +{ + int ret = 0; + unsigned char buf[16]; + struct esp_mbedtls_ssl_buf *esp_buf; + + ESP_LOGV(TAG, "--> free rx"); + + /** + * When have read multi messages once, can't free the input buffer directly. + */ + if (!ssl->MBEDTLS_PRIVATE(in_buf) || (ssl->MBEDTLS_PRIVATE(in_hslen) && (ssl->MBEDTLS_PRIVATE(in_hslen) < ssl->MBEDTLS_PRIVATE(in_msglen))) || + (ssl->MBEDTLS_PRIVATE(in_buf) && (esp_mbedtls_get_buf_state(ssl->MBEDTLS_PRIVATE(in_buf)) == ESP_MBEDTLS_SSL_BUF_NO_CACHED))) { + ret = 0; + goto exit; + } + + /** + * The previous processing is just skipped, so "ssl->MBEDTLS_PRIVATE(in_msglen) = 0" + */ + if (!ssl->MBEDTLS_PRIVATE(in_msgtype) +#if defined(MBEDTLS_SSL_SRV_C) + /** + * The ssl server read ClientHello manually without mbedtls_ssl_read_record(), so in_msgtype is not set and is zero. + * ClientHello has been processed and rx buffer should be freed. + * After processing ClientHello, the ssl state has been changed to MBEDTLS_SSL_SERVER_HELLO. + */ + && !(ssl->MBEDTLS_PRIVATE(conf)->MBEDTLS_PRIVATE(endpoint) == MBEDTLS_SSL_IS_SERVER && ssl->MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_SERVER_HELLO) +#endif + ) { + goto exit; + } + + memcpy(buf, ssl->MBEDTLS_PRIVATE(in_ctr), 8); + memcpy(buf + 8, ssl->MBEDTLS_PRIVATE(in_iv), 8); + + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(in_buf)); + init_rx_buffer(ssl, NULL); + + esp_buf = mbedtls_calloc(1, SSL_BUF_HEAD_OFFSET_SIZE + 16); + if (!esp_buf) { + ESP_LOGE(TAG, "alloc(%d bytes) failed", SSL_BUF_HEAD_OFFSET_SIZE + 16); + ret = MBEDTLS_ERR_SSL_ALLOC_FAILED; + goto exit; + } + + esp_mbedtls_init_ssl_buf(esp_buf, 16); + memcpy(esp_buf->buf, buf, 16); + init_rx_buffer(ssl, esp_buf->buf); + esp_mbedtls_set_buf_state(ssl->MBEDTLS_PRIVATE(in_buf), ESP_MBEDTLS_SSL_BUF_NO_CACHED); +exit: + ESP_LOGV(TAG, "<-- free rx"); + + return ret; +} + +size_t esp_mbedtls_get_crt_size(mbedtls_x509_crt *cert, size_t *num) +{ + size_t n = 0; + size_t bytes = 0; + + while (cert) { + bytes += cert->raw.len; + n++; + + cert = cert->next; + } + + *num = n; + + return bytes; +} + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA +void esp_mbedtls_free_dhm(mbedtls_ssl_context *ssl) +{ +#ifdef CONFIG_MBEDTLS_DHM_C + const mbedtls_ssl_config *conf = mbedtls_ssl_context_get_config(ssl); + mbedtls_mpi_free((mbedtls_mpi *)&conf->MBEDTLS_PRIVATE(dhm_P)); + mbedtls_mpi_free((mbedtls_mpi *)&conf->MBEDTLS_PRIVATE(dhm_G)); +#endif /* CONFIG_MBEDTLS_DHM_C */ +} + +void esp_mbedtls_free_keycert(mbedtls_ssl_context *ssl) +{ + mbedtls_ssl_config *conf = (mbedtls_ssl_config * )mbedtls_ssl_context_get_config(ssl); + mbedtls_ssl_key_cert *keycert = conf->MBEDTLS_PRIVATE(key_cert), *next; + + while (keycert) { + next = keycert->next; + + if (keycert) { + mbedtls_free(keycert); + } + + keycert = next; + } + + conf->MBEDTLS_PRIVATE(key_cert) = NULL; +} + +void esp_mbedtls_free_keycert_key(mbedtls_ssl_context *ssl) +{ + const mbedtls_ssl_config *conf = mbedtls_ssl_context_get_config(ssl); + mbedtls_ssl_key_cert *keycert = conf->MBEDTLS_PRIVATE(key_cert); + + while (keycert) { + if (keycert->key) { + mbedtls_pk_free(keycert->key); + keycert->key = NULL; + } + keycert = keycert->next; + } +} + +void esp_mbedtls_free_keycert_cert(mbedtls_ssl_context *ssl) +{ + const mbedtls_ssl_config *conf = mbedtls_ssl_context_get_config(ssl); + mbedtls_ssl_key_cert *keycert = conf->MBEDTLS_PRIVATE(key_cert); + + while (keycert) { + if (keycert->cert) { + mbedtls_x509_crt_free(keycert->cert); + keycert->cert = NULL; + } + keycert = keycert->next; + } +} +#endif /* CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA */ + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT +void esp_mbedtls_free_cacert(mbedtls_ssl_context *ssl) +{ + if (ssl->MBEDTLS_PRIVATE(conf)->MBEDTLS_PRIVATE(ca_chain)) { + mbedtls_ssl_config *conf = (mbedtls_ssl_config * )mbedtls_ssl_context_get_config(ssl); + +#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE + /* In case of mbedtls certificate bundle, we attach a "static const" + * dummy cert, thus we need to avoid the write operations (memset()) + * performed by `mbedtls_x509_crt_free()` + */ + if (!esp_crt_bundle_in_use(conf->MBEDTLS_PRIVATE(ca_chain))) { + mbedtls_x509_crt_free(conf->MBEDTLS_PRIVATE(ca_chain)); + } +#else + mbedtls_x509_crt_free(conf->MBEDTLS_PRIVATE(ca_chain)); +#endif + + conf->MBEDTLS_PRIVATE(ca_chain) = NULL; + } +} +#endif /* CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT */ diff --git a/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.h b/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.h new file mode 100644 index 000000000..ad7a716be --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/dynamic/esp_mbedtls_dynamic_impl.h @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _DYNAMIC_IMPL_H_ +#define _DYNAMIC_IMPL_H_ + +#include +#include +#include + +/* TODO: Remove this once the appropriate solution is found + * + * ssl_misc.h header uses private elements from + * mbedtls, which become undefined if the following flag + * is not defined + */ +#define MBEDTLS_ALLOW_PRIVATE_ACCESS + +// located at mbedtls/library/ssl_misc.h +#include "ssl_misc.h" + +#include "mbedtls/ssl.h" +#include "mbedtls/platform.h" +#include "esp_log.h" +#include "sdkconfig.h" + +#define TRACE_CHECK(_fn, _state) \ +({ \ + ESP_LOGV(TAG, "%d " _state " to do \"%s\"", __LINE__, # _fn); \ +}) + +#define CHECK_OK(_fn) \ +({ \ + int _ret; \ + \ + TRACE_CHECK(_fn, "state"); \ + \ + if ((_ret = _fn) != 0) { \ + ESP_LOGV(TAG, "\"%s\" result is %d", # _fn, -_ret); \ + TRACE_CHECK(_fn, "fail"); \ + return _ret; \ + } \ + \ + TRACE_CHECK(_fn, "end"); \ + \ +}) + +typedef enum { + ESP_MBEDTLS_SSL_BUF_CACHED, + ESP_MBEDTLS_SSL_BUF_NO_CACHED, +} esp_mbedtls_ssl_buf_states; + +struct esp_mbedtls_ssl_buf { + esp_mbedtls_ssl_buf_states state; + unsigned int len; + unsigned char buf[]; +}; + +#define SSL_BUF_HEAD_OFFSET_SIZE ((int)offsetof(struct esp_mbedtls_ssl_buf, buf)) + +void esp_mbedtls_free_buf(unsigned char *buf); + +int esp_mbedtls_setup_tx_buffer(mbedtls_ssl_context *ssl); + +void esp_mbedtls_setup_rx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_reset_add_tx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_reset_add_rx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_reset_free_tx_buffer(mbedtls_ssl_context *ssl); + +void esp_mbedtls_reset_free_rx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_add_tx_buffer(mbedtls_ssl_context *ssl, size_t buffer_len); + +int esp_mbedtls_add_rx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_free_tx_buffer(mbedtls_ssl_context *ssl); + +int esp_mbedtls_free_rx_buffer(mbedtls_ssl_context *ssl); + +size_t esp_mbedtls_get_crt_size(mbedtls_x509_crt *cert, size_t *num); + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA +void esp_mbedtls_free_dhm(mbedtls_ssl_context *ssl); + +void esp_mbedtls_free_keycert(mbedtls_ssl_context *ssl); + +void esp_mbedtls_free_keycert_cert(mbedtls_ssl_context *ssl); + +void esp_mbedtls_free_keycert_key(mbedtls_ssl_context *ssl); +#endif + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT +void esp_mbedtls_free_cacert(mbedtls_ssl_context *ssl); +#endif + +#endif /* _DYNAMIC_IMPL_H_ */ diff --git a/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_cli.c b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_cli.c new file mode 100644 index 000000000..0ea78df59 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_cli.c @@ -0,0 +1,221 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_mbedtls_dynamic_impl.h" + +int __real_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl); +int __real_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl); + +int __wrap_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl); +int __wrap_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl); + +static const char *TAG = "SSL client"; + +static int manage_resource(mbedtls_ssl_context *ssl, bool add) +{ + int state = add ? ssl->MBEDTLS_PRIVATE(state) : ssl->MBEDTLS_PRIVATE(state) - 1; + + if (mbedtls_ssl_is_handshake_over(ssl) || ssl->MBEDTLS_PRIVATE(handshake) == NULL) { + return 0; + } + + if (!add) { + if (!ssl->MBEDTLS_PRIVATE(out_left)) { + CHECK_OK(esp_mbedtls_free_tx_buffer(ssl)); + } + } + + /* Change state now, so that it is right in mbedtls_ssl_read_record(), used + * by DTLS for dropping out-of-sequence ChangeCipherSpec records */ +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + if( ssl->state == MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC && + ssl->handshake->new_session_ticket != 0 ) + { + ssl->state = MBEDTLS_SSL_NEW_SESSION_TICKET; + } +#endif + + switch (state) { + case MBEDTLS_SSL_HELLO_REQUEST: + break; + case MBEDTLS_SSL_CLIENT_HELLO: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + + + case MBEDTLS_SSL_SERVER_HELLO: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + case MBEDTLS_SSL_SERVER_CERTIFICATE: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT + esp_mbedtls_free_cacert(ssl); +#endif + } + break; + case MBEDTLS_SSL_SERVER_KEY_EXCHANGE: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + if (!ssl->MBEDTLS_PRIVATE(keep_current_message)) { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + } + break; + case MBEDTLS_SSL_CERTIFICATE_REQUEST: + if (add) { + if (!ssl->MBEDTLS_PRIVATE(keep_current_message)) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } + } else { + if (!ssl->MBEDTLS_PRIVATE(keep_current_message)) { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + } + break; + case MBEDTLS_SSL_SERVER_HELLO_DONE: + if (add) { + if (!ssl->MBEDTLS_PRIVATE(keep_current_message)) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + + + case MBEDTLS_SSL_CLIENT_CERTIFICATE: + if (add) { + size_t buffer_len = 3; + + const mbedtls_ssl_config *conf = mbedtls_ssl_context_get_config(ssl); + mbedtls_ssl_key_cert *key_cert = conf->MBEDTLS_PRIVATE(key_cert); + + while (key_cert && key_cert->cert) { + size_t num; + + buffer_len += esp_mbedtls_get_crt_size(key_cert->cert, &num); + buffer_len += num * 3; + + key_cert = key_cert->next; + } + + buffer_len = MAX(buffer_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_CERTIFICATE_VERIFY: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } else { +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + esp_mbedtls_free_dhm(ssl); + esp_mbedtls_free_keycert_key(ssl); + esp_mbedtls_free_keycert(ssl); +#endif + } + break; + case MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_CLIENT_FINISHED: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + + +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + case MBEDTLS_SSL_NEW_SESSION_TICKET: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; +#endif + + + case MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + case MBEDTLS_SSL_SERVER_FINISHED: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + case MBEDTLS_SSL_FLUSH_BUFFERS: + break; + case MBEDTLS_SSL_HANDSHAKE_WRAPUP: +#if defined(MBEDTLS_SSL_RENEGOTIATION) + if (add && ssl->MBEDTLS_PRIVATE(renego_status)) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } +#endif + break; + default: + break; + } + + return 0; +} + +int __wrap_mbedtls_ssl_handshake_client_step(mbedtls_ssl_context *ssl) +{ + CHECK_OK(manage_resource(ssl, true)); + + CHECK_OK(__real_mbedtls_ssl_handshake_client_step(ssl)); + + CHECK_OK(manage_resource(ssl, false)); + + return 0; +} + +int __wrap_mbedtls_ssl_write_client_hello(mbedtls_ssl_context *ssl) +{ + CHECK_OK(manage_resource(ssl, true)); + + CHECK_OK(__real_mbedtls_ssl_write_client_hello(ssl)); + + CHECK_OK(manage_resource(ssl, false)); + + return 0; +} diff --git a/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_srv.c b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_srv.c new file mode 100644 index 000000000..5a657b56c --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_srv.c @@ -0,0 +1,219 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_mbedtls_dynamic_impl.h" + +int __real_mbedtls_ssl_handshake_server_step(mbedtls_ssl_context *ssl); + +int __wrap_mbedtls_ssl_handshake_server_step(mbedtls_ssl_context *ssl); + +static const char *TAG = "SSL Server"; + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA +/** + * Check if ciphersuite uses rsa key exchange methods. + */ +static bool ssl_ciphersuite_uses_rsa_key_ex(mbedtls_ssl_context *ssl) +{ + const mbedtls_ssl_ciphersuite_t *ciphersuite_info = + ssl->MBEDTLS_PRIVATE(handshake)->ciphersuite_info; + + if (ciphersuite_info->MBEDTLS_PRIVATE(key_exchange) == MBEDTLS_KEY_EXCHANGE_RSA || + ciphersuite_info->MBEDTLS_PRIVATE(key_exchange) == MBEDTLS_KEY_EXCHANGE_RSA_PSK) { + return true; + } else { + return false; + } +} +#endif + +static int manage_resource(mbedtls_ssl_context *ssl, bool add) +{ + int state = add ? ssl->MBEDTLS_PRIVATE(state) : ssl->MBEDTLS_PRIVATE(state) - 1; + + if (mbedtls_ssl_is_handshake_over(ssl) || ssl->MBEDTLS_PRIVATE(handshake) == NULL) { + return 0; + } + + if (!add) { + if (!ssl->MBEDTLS_PRIVATE(out_left)) { + CHECK_OK(esp_mbedtls_free_tx_buffer(ssl)); + } + } + + switch (state) { + case MBEDTLS_SSL_HELLO_REQUEST: + break; + case MBEDTLS_SSL_CLIENT_HELLO: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + + + case MBEDTLS_SSL_SERVER_HELLO: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_SERVER_CERTIFICATE: + if (add) { + size_t buffer_len = 3; + + const mbedtls_ssl_config *conf = mbedtls_ssl_context_get_config(ssl); + mbedtls_ssl_key_cert *key_cert = conf->MBEDTLS_PRIVATE(key_cert); + + while (key_cert && key_cert->cert) { + size_t num; + + buffer_len += esp_mbedtls_get_crt_size(key_cert->cert, &num); + buffer_len += num * 3; + + key_cert = key_cert->next; + } + + buffer_len = MAX(buffer_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } else { +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + /** + * Not free keycert->cert until MBEDTLS_SSL_CLIENT_KEY_EXCHANGE for rsa key exchange methods. + * For ssl server will use keycert->cert to parse client key exchange. + */ + if (!ssl_ciphersuite_uses_rsa_key_ex(ssl)) { + esp_mbedtls_free_keycert_cert(ssl); + } +#endif + } + break; + case MBEDTLS_SSL_SERVER_KEY_EXCHANGE: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } else { +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + esp_mbedtls_free_dhm(ssl); + /** + * Not free keycert->key and keycert until MBEDTLS_SSL_CLIENT_KEY_EXCHANGE for rsa key exchange methods. + * For ssl server will use keycert->key to parse client key exchange. + */ + if (!ssl_ciphersuite_uses_rsa_key_ex(ssl)) { + esp_mbedtls_free_keycert_key(ssl); + esp_mbedtls_free_keycert(ssl); + } +#endif + } + break; + case MBEDTLS_SSL_CERTIFICATE_REQUEST: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_SERVER_HELLO_DONE: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + + + case MBEDTLS_SSL_CLIENT_CERTIFICATE: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT + esp_mbedtls_free_cacert(ssl); +#endif + } + break; + case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + +#ifdef CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA + /** + * Free keycert after MBEDTLS_SSL_CLIENT_KEY_EXCHANGE for rsa key exchange methods. + * For ssl server will use keycert->cert and keycert->key to parse client key exchange. + */ + if (ssl_ciphersuite_uses_rsa_key_ex(ssl)) { + esp_mbedtls_free_keycert_cert(ssl); + esp_mbedtls_free_keycert_key(ssl); + esp_mbedtls_free_keycert(ssl); + } +#endif + } + break; + case MBEDTLS_SSL_CERTIFICATE_VERIFY: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + case MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + case MBEDTLS_SSL_CLIENT_FINISHED: + if (add) { + CHECK_OK(esp_mbedtls_add_rx_buffer(ssl)); + } else { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + break; + + + case MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_SERVER_FINISHED: + if (add) { + size_t buffer_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, buffer_len)); + } + break; + case MBEDTLS_SSL_FLUSH_BUFFERS: + break; + case MBEDTLS_SSL_HANDSHAKE_WRAPUP: + break; + default: + break; + } + + return 0; +} + +int __wrap_mbedtls_ssl_handshake_server_step(mbedtls_ssl_context *ssl) +{ + CHECK_OK(manage_resource(ssl, true)); + + CHECK_OK(__real_mbedtls_ssl_handshake_server_step(ssl)); + + CHECK_OK(manage_resource(ssl, false)); + + return 0; +} diff --git a/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_tls.c b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_tls.c new file mode 100644 index 000000000..f12bc2bf7 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/dynamic/esp_ssl_tls.c @@ -0,0 +1,398 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "mbedtls/error.h" +#include "esp_mbedtls_dynamic_impl.h" + +int __real_mbedtls_ssl_write(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); +int __real_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); +void __real_mbedtls_ssl_free(mbedtls_ssl_context *ssl); +int __real_mbedtls_ssl_session_reset(mbedtls_ssl_context *ssl); +int __real_mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf); +int __real_mbedtls_ssl_send_alert_message(mbedtls_ssl_context *ssl, unsigned char level, unsigned char message); +int __real_mbedtls_ssl_close_notify(mbedtls_ssl_context *ssl); + +int __wrap_mbedtls_ssl_write(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); +int __wrap_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); +void __wrap_mbedtls_ssl_free(mbedtls_ssl_context *ssl); +int __wrap_mbedtls_ssl_session_reset(mbedtls_ssl_context *ssl); +int __wrap_mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf); +int __wrap_mbedtls_ssl_send_alert_message(mbedtls_ssl_context *ssl, unsigned char level, unsigned char message); +int __wrap_mbedtls_ssl_close_notify(mbedtls_ssl_context *ssl); + +static const char *TAG = "SSL TLS"; + +static int tx_done(mbedtls_ssl_context *ssl) +{ + if (!ssl->MBEDTLS_PRIVATE(out_left)) + return 1; + + return 0; +} + +static int rx_done(mbedtls_ssl_context *ssl) +{ + if (!ssl->MBEDTLS_PRIVATE(in_msglen)) { + return 1; + } + + ESP_LOGD(TAG, "RX left %zu bytes", ssl->MBEDTLS_PRIVATE(in_msglen)); + return 0; +} + +static int ssl_update_checksum_start( mbedtls_ssl_context *ssl, + const unsigned char *buf, size_t len ) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; +#if defined(MBEDTLS_SHA256_C) + ret = mbedtls_md_update( &ssl->handshake->fin_sha256, buf, len ); +#endif +#if defined(MBEDTLS_SHA512_C) + ret = mbedtls_md_update( &ssl->handshake->fin_sha384, buf, len ); +#endif + return ret; +} + +static void ssl_handshake_params_init( mbedtls_ssl_handshake_params *handshake ) +{ + memset( handshake, 0, sizeof( mbedtls_ssl_handshake_params ) ); + +#if defined(MBEDTLS_SHA256_C) + mbedtls_md_init( &handshake->fin_sha256 ); + mbedtls_md_setup( &handshake->fin_sha256, + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), + 0 ); + mbedtls_md_starts( &handshake->fin_sha256 ); +#endif +#if defined(MBEDTLS_SHA512_C) + mbedtls_md_init( &handshake->fin_sha384 ); + mbedtls_md_setup( &handshake->fin_sha384, + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384), + 0 ); + mbedtls_md_starts( &handshake->fin_sha384 ); +#endif + + handshake->update_checksum = ssl_update_checksum_start; + +#if defined(MBEDTLS_DHM_C) + mbedtls_dhm_init( &handshake->dhm_ctx ); +#endif +#if defined(MBEDTLS_ECDH_C) + mbedtls_ecdh_init( &handshake->ecdh_ctx ); +#endif +#if defined(MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED) + mbedtls_ecjpake_init( &handshake->ecjpake_ctx ); +#if defined(MBEDTLS_SSL_CLI_C) + handshake->ecjpake_cache = NULL; + handshake->ecjpake_cache_len = 0; +#endif +#endif + +#if defined(MBEDTLS_SSL_ECP_RESTARTABLE) + mbedtls_x509_crt_restart_init( &handshake->ecrs_ctx ); +#endif + +#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + handshake->sni_authmode = MBEDTLS_SSL_VERIFY_UNSET; +#endif + +#if defined(MBEDTLS_X509_CRT_PARSE_C) && \ + !defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) + mbedtls_pk_init( &handshake->peer_pubkey ); +#endif +} + +static int ssl_handshake_init( mbedtls_ssl_context *ssl ) +{ + /* Clear old handshake information if present */ + if( ssl->transform_negotiate ) + mbedtls_ssl_transform_free( ssl->transform_negotiate ); + if( ssl->session_negotiate ) + mbedtls_ssl_session_free( ssl->session_negotiate ); + if( ssl->handshake ) + mbedtls_ssl_handshake_free( ssl ); + + /* + * Either the pointers are now NULL or cleared properly and can be freed. + * Now allocate missing structures. + */ + if( ssl->transform_negotiate == NULL ) + { + ssl->transform_negotiate = mbedtls_calloc( 1, sizeof(mbedtls_ssl_transform) ); + } + + if( ssl->session_negotiate == NULL ) + { + ssl->session_negotiate = mbedtls_calloc( 1, sizeof(mbedtls_ssl_session) ); + } + + if( ssl->handshake == NULL ) + { + ssl->handshake = mbedtls_calloc( 1, sizeof(mbedtls_ssl_handshake_params) ); + } +#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) + /* If the buffers are too small - reallocate */ + + handle_buffer_resizing( ssl, 0, MBEDTLS_SSL_IN_BUFFER_LEN, + MBEDTLS_SSL_OUT_BUFFER_LEN ); +#endif + + /* All pointers should exist and can be directly freed without issue */ + if( ssl->handshake == NULL || + ssl->transform_negotiate == NULL || + ssl->session_negotiate == NULL ) + { + ESP_LOGD(TAG, "alloc() of ssl sub-contexts failed"); + + mbedtls_free( ssl->handshake ); + mbedtls_free( ssl->transform_negotiate ); + mbedtls_free( ssl->session_negotiate ); + + ssl->handshake = NULL; + ssl->transform_negotiate = NULL; + ssl->session_negotiate = NULL; + + return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); + } + + /* Initialize structures */ + mbedtls_ssl_session_init( ssl->session_negotiate ); + mbedtls_ssl_transform_init( ssl->transform_negotiate ); + ssl_handshake_params_init( ssl->handshake ); + +/* + * curve_list is translated to IANA TLS group identifiers here because + * mbedtls_ssl_conf_curves returns void and so can't return + * any error codes. + */ +#if defined(MBEDTLS_ECP_C) +#if !defined(MBEDTLS_DEPRECATED_REMOVED) + /* Heap allocate and translate curve_list from internal to IANA group ids */ + if ( ssl->conf->curve_list != NULL ) + { + size_t length; + const mbedtls_ecp_group_id *curve_list = ssl->conf->curve_list; + + for( length = 0; ( curve_list[length] != MBEDTLS_ECP_DP_NONE ) && + ( length < MBEDTLS_ECP_DP_MAX ); length++ ) {} + + /* Leave room for zero termination */ + uint16_t *group_list = mbedtls_calloc( length + 1, sizeof(uint16_t) ); + if ( group_list == NULL ) + return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); + + for( size_t i = 0; i < length; i++ ) + { + const mbedtls_ecp_curve_info *info = + mbedtls_ecp_curve_info_from_grp_id( curve_list[i] ); + if ( info == NULL ) + { + mbedtls_free( group_list ); + return( MBEDTLS_ERR_SSL_BAD_CONFIG ); + } + group_list[i] = info->tls_id; + } + + group_list[length] = 0; + + ssl->handshake->group_list = group_list; + ssl->handshake->group_list_heap_allocated = 1; + } + else + { + ssl->handshake->group_list = ssl->conf->group_list; + ssl->handshake->group_list_heap_allocated = 0; + } +#endif /* MBEDTLS_DEPRECATED_REMOVED */ +#endif /* MBEDTLS_ECP_C */ + +#if defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED) +#if !defined(MBEDTLS_DEPRECATED_REMOVED) +#if defined(MBEDTLS_SSL_PROTO_TLS1_2) + /* Heap allocate and translate sig_hashes from internal hash identifiers to + signature algorithms IANA identifiers. */ + if ( mbedtls_ssl_conf_is_tls12_only( ssl->conf ) && + ssl->conf->sig_hashes != NULL ) + { + const int *md; + const int *sig_hashes = ssl->conf->sig_hashes; + size_t sig_algs_len = 0; + uint16_t *p; + +#if defined(static_assert) + static_assert( MBEDTLS_SSL_MAX_SIG_ALG_LIST_LEN + <= ( SIZE_MAX - ( 2 * sizeof(uint16_t) ) ), + "MBEDTLS_SSL_MAX_SIG_ALG_LIST_LEN too big" ); +#endif + + for( md = sig_hashes; *md != MBEDTLS_MD_NONE; md++ ) + { + if( mbedtls_ssl_hash_from_md_alg( *md ) == MBEDTLS_SSL_HASH_NONE ) + continue; +#if defined(MBEDTLS_ECDSA_C) + sig_algs_len += sizeof( uint16_t ); +#endif + +#if defined(MBEDTLS_RSA_C) + sig_algs_len += sizeof( uint16_t ); +#endif + if( sig_algs_len > MBEDTLS_SSL_MAX_SIG_ALG_LIST_LEN ) + return( MBEDTLS_ERR_SSL_BAD_CONFIG ); + } + + if( sig_algs_len < MBEDTLS_SSL_MIN_SIG_ALG_LIST_LEN ) + return( MBEDTLS_ERR_SSL_BAD_CONFIG ); + + ssl->handshake->sig_algs = mbedtls_calloc( 1, sig_algs_len + + sizeof( uint16_t )); + if( ssl->handshake->sig_algs == NULL ) + return( MBEDTLS_ERR_SSL_ALLOC_FAILED ); + + p = (uint16_t *)ssl->handshake->sig_algs; + for( md = sig_hashes; *md != MBEDTLS_MD_NONE; md++ ) + { + unsigned char hash = mbedtls_ssl_hash_from_md_alg( *md ); + if( hash == MBEDTLS_SSL_HASH_NONE ) + continue; +#if defined(MBEDTLS_ECDSA_C) + *p = (( hash << 8 ) | MBEDTLS_SSL_SIG_ECDSA); + p++; +#endif +#if defined(MBEDTLS_RSA_C) + *p = (( hash << 8 ) | MBEDTLS_SSL_SIG_RSA); + p++; +#endif + } + *p = MBEDTLS_TLS_SIG_NONE; + ssl->handshake->sig_algs_heap_allocated = 1; + } + else +#endif /* MBEDTLS_SSL_PROTO_TLS1_2 */ + { + ssl->handshake->sig_algs_heap_allocated = 0; + } +#endif /* !MBEDTLS_DEPRECATED_REMOVED */ +#endif /* MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED */ + + return( 0 ); +} + +int __wrap_mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf) +{ + ssl->conf = conf; + ssl->tls_version = ssl->conf->max_tls_version; + + CHECK_OK(ssl_handshake_init(ssl)); + + mbedtls_free(ssl->MBEDTLS_PRIVATE(out_buf)); + ssl->MBEDTLS_PRIVATE(out_buf) = NULL; + CHECK_OK(esp_mbedtls_setup_tx_buffer(ssl)); + + mbedtls_free(ssl->MBEDTLS_PRIVATE(in_buf)); + ssl->MBEDTLS_PRIVATE(in_buf) = NULL; + esp_mbedtls_setup_rx_buffer(ssl); + + return 0; +} + +int __wrap_mbedtls_ssl_write(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) +{ + int ret; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, 0)); + + ret = __real_mbedtls_ssl_write(ssl, buf, len); + + if (tx_done(ssl)) { + CHECK_OK(esp_mbedtls_free_tx_buffer(ssl)); + } + + return ret; +} + +int __wrap_mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len) +{ + int ret; + + ESP_LOGD(TAG, "add mbedtls RX buffer"); + ret = esp_mbedtls_add_rx_buffer(ssl); + if (ret == MBEDTLS_ERR_SSL_CONN_EOF) { + ESP_LOGD(TAG, "fail, the connection indicated an EOF"); + return 0; + } else if (ret < 0) { + ESP_LOGD(TAG, "fail, error=%d", -ret); + return ret; + } + ESP_LOGD(TAG, "end"); + + ret = __real_mbedtls_ssl_read(ssl, buf, len); + + if (rx_done(ssl)) { + CHECK_OK(esp_mbedtls_free_rx_buffer(ssl)); + } + + return ret; +} + +void __wrap_mbedtls_ssl_free(mbedtls_ssl_context *ssl) +{ + if (ssl->MBEDTLS_PRIVATE(out_buf)) { + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(out_buf)); + ssl->MBEDTLS_PRIVATE(out_buf) = NULL; + } + + if (ssl->MBEDTLS_PRIVATE(in_buf)) { + esp_mbedtls_free_buf(ssl->MBEDTLS_PRIVATE(in_buf)); + ssl->MBEDTLS_PRIVATE(in_buf) = NULL; + } + + __real_mbedtls_ssl_free(ssl); +} + +int __wrap_mbedtls_ssl_session_reset(mbedtls_ssl_context *ssl) +{ + CHECK_OK(esp_mbedtls_reset_add_tx_buffer(ssl)); + + CHECK_OK(esp_mbedtls_reset_add_rx_buffer(ssl)); + + CHECK_OK(__real_mbedtls_ssl_session_reset(ssl)); + + CHECK_OK(esp_mbedtls_reset_free_tx_buffer(ssl)); + + esp_mbedtls_reset_free_rx_buffer(ssl); + + return 0; +} + +int __wrap_mbedtls_ssl_send_alert_message(mbedtls_ssl_context *ssl, unsigned char level, unsigned char message) +{ + int ret; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, 0)); + + ret = __real_mbedtls_ssl_send_alert_message(ssl, level, message); + + if (tx_done(ssl)) { + CHECK_OK(esp_mbedtls_free_tx_buffer(ssl)); + } + + return ret; +} + +int __wrap_mbedtls_ssl_close_notify(mbedtls_ssl_context *ssl) +{ + int ret; + + CHECK_OK(esp_mbedtls_add_tx_buffer(ssl, 0)); + + ret = __real_mbedtls_ssl_close_notify(ssl); + + if (tx_done(ssl)) { + CHECK_OK(esp_mbedtls_free_tx_buffer(ssl)); + } + + return ret; +} diff --git a/components/mbedtls/mbedtls_v3/port/ecc/ecc_alt.c b/components/mbedtls/mbedtls_v3/port/ecc/ecc_alt.c new file mode 100644 index 000000000..ef0b76fe0 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/ecc/ecc_alt.c @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "soc/hwcrypto_periph.h" +#include "ecc_impl.h" + +#include "mbedtls/ecp.h" +#include "mbedtls/platform_util.h" + +#if defined(MBEDTLS_ECP_MUL_ALT) || defined(MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK) + +#define MAX_SIZE 32 // 256 bits + +static int esp_mbedtls_ecp_point_multiply(const mbedtls_ecp_group *grp, mbedtls_ecp_point *R, + const mbedtls_mpi *m, const mbedtls_ecp_point *P) +{ + int ret = MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + uint8_t x_tmp[MAX_SIZE] = {0}; + uint8_t y_tmp[MAX_SIZE] = {0}; + + uint8_t m_le[MAX_SIZE] = {0}; + ecc_point_t p_pt = {0}; + ecc_point_t r_pt = {0}; + + p_pt.len = grp->pbits / 8; + + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary_le(&P->MBEDTLS_PRIVATE(X), p_pt.x, MAX_SIZE)); + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary_le(&P->MBEDTLS_PRIVATE(Y), p_pt.y, MAX_SIZE)); + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary_le(m, m_le, MAX_SIZE)); + + ret = esp_ecc_point_multiply(&p_pt, m_le, &r_pt, false); + + for (int i = 0; i < MAX_SIZE; i++) { + x_tmp[MAX_SIZE - i - 1] = r_pt.x[i]; + y_tmp[MAX_SIZE - i - 1] = r_pt.y[i]; + } + + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&R->MBEDTLS_PRIVATE(X), x_tmp, MAX_SIZE)); + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&R->MBEDTLS_PRIVATE(Y), y_tmp, MAX_SIZE)); + MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&R->MBEDTLS_PRIVATE(Z), 1)); + return ret; + +cleanup: + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; +} + +int ecp_mul_restartable_internal( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, + const mbedtls_mpi *m, const mbedtls_ecp_point *P, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + mbedtls_ecp_restart_ctx *rs_ctx ) +{ + int ret = MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + if (grp->id != MBEDTLS_ECP_DP_SECP192R1 && grp->id != MBEDTLS_ECP_DP_SECP256R1) { +#if defined(MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK) + return ecp_mul_restartable_internal_soft(grp, R, m, P, f_rng, p_rng, rs_ctx); +#else + return ret; +#endif + } + + /* Common sanity checks to conform with mbedTLS return values */ + MBEDTLS_MPI_CHK( mbedtls_ecp_check_privkey(grp, m) ); + MBEDTLS_MPI_CHK( mbedtls_ecp_check_pubkey(grp, P) ); + + MBEDTLS_MPI_CHK( esp_mbedtls_ecp_point_multiply(grp, R, m, P) ); +cleanup: + return( ret ); +} + +#endif /* defined(MBEDTLS_ECP_MUL_ALT) || defined(MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK) */ + +#if defined(MBEDTLS_ECP_VERIFY_ALT) || defined(MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK) + +int mbedtls_ecp_check_pubkey( const mbedtls_ecp_group *grp, + const mbedtls_ecp_point *pt ) +{ + int res; + ecc_point_t point; + + if (grp->id != MBEDTLS_ECP_DP_SECP192R1 && grp->id != MBEDTLS_ECP_DP_SECP256R1) { +#if defined(MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK) + return mbedtls_ecp_check_pubkey_soft(grp, pt); +#else + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; +#endif + } + + if (grp == NULL || pt == NULL) { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + /* Must use affine coordinates */ + if( mbedtls_mpi_cmp_int( &pt->MBEDTLS_PRIVATE(Z), 1 ) != 0 ) + return( MBEDTLS_ERR_ECP_INVALID_KEY ); + + mbedtls_platform_zeroize((void *)&point, sizeof(ecc_point_t)); + + memcpy(&point.x, pt->MBEDTLS_PRIVATE(X).MBEDTLS_PRIVATE(p), mbedtls_mpi_size(&pt->MBEDTLS_PRIVATE(X))); + memcpy(&point.y, pt->MBEDTLS_PRIVATE(Y).MBEDTLS_PRIVATE(p), mbedtls_mpi_size(&pt->MBEDTLS_PRIVATE(Y))); + + point.len = grp->pbits / 8; + + res = esp_ecc_point_verify(&point); + if (res == 1) { + return 0; + } else { + return MBEDTLS_ERR_ECP_INVALID_KEY; + } +} +#endif /* defined(MBEDTLS_ECP_VERIFY_ALT) || defined(MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK) */ diff --git a/components/mbedtls/mbedtls_v3/port/ecc/esp_ecc.c b/components/mbedtls/mbedtls_v3/port/ecc/esp_ecc.c new file mode 100644 index 000000000..ecd87a256 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/ecc/esp_ecc.c @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "esp_crypto_lock.h" +#include "esp_private/periph_ctrl.h" +#include "ecc_impl.h" +#include "hal/ecc_hal.h" +#include "hal/ecc_ll.h" + +static void esp_ecc_acquire_hardware(void) +{ + esp_crypto_ecc_lock_acquire(); + + periph_module_enable(PERIPH_ECC_MODULE); + ecc_ll_power_up(); +} + +static void esp_ecc_release_hardware(void) +{ + periph_module_disable(PERIPH_ECC_MODULE); + ecc_ll_power_down(); + + esp_crypto_ecc_lock_release(); +} + +int esp_ecc_point_multiply(const ecc_point_t *point, const uint8_t *scalar, ecc_point_t *result, bool verify_first) +{ + int ret = -1; + uint16_t len = point->len; + ecc_mode_t work_mode = verify_first ? ECC_MODE_VERIFY_THEN_POINT_MUL : ECC_MODE_POINT_MUL; + + esp_ecc_acquire_hardware(); + + ecc_hal_write_mul_param(scalar, point->x, point->y, len); + ecc_hal_set_mode(work_mode); + ecc_hal_start_calc(); + + memset(result, 0, sizeof(ecc_point_t)); + + result->len = len; + + while (!ecc_hal_is_calc_finished()) { + ; + } + + ret = ecc_hal_read_mul_result(result->x, result->y, len); + + esp_ecc_release_hardware(); + + return ret; +} + +int esp_ecc_point_verify(const ecc_point_t *point) +{ + int result; + + esp_ecc_acquire_hardware(); + ecc_hal_write_verify_param(point->x, point->y, point->len); + ecc_hal_set_mode(ECC_MODE_VERIFY); + ecc_hal_start_calc(); + + while (!ecc_hal_is_calc_finished()) { + ; + } + + result = ecc_hal_read_verify_result(); + + esp_ecc_release_hardware(); + + return result; +} diff --git a/components/mbedtls/mbedtls_v3/port/ecdsa/ecdsa_alt.c b/components/mbedtls/mbedtls_v3/port/ecdsa/ecdsa_alt.c new file mode 100644 index 000000000..8f9158c37 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/ecdsa/ecdsa_alt.c @@ -0,0 +1,553 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "hal/ecdsa_hal.h" +#include "esp_crypto_lock.h" +#include "esp_efuse.h" +#include "mbedtls/ecp.h" +#include "mbedtls/error.h" +#include "mbedtls/ecdsa.h" +#include "mbedtls/asn1.h" +#include "mbedtls/asn1write.h" +#include "mbedtls/platform_util.h" +#include "esp_private/periph_ctrl.h" +#include "ecdsa/ecdsa_alt.h" +#include "hal/ecc_ll.h" + +#define ECDSA_KEY_MAGIC (short) 0xECD5A +#define ECDSA_SHA_LEN 32 +#define MAX_ECDSA_COMPONENT_LEN 32 + +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM +#include "esp_timer.h" + +#if CONFIG_ESP_CRYPTO_DPA_PROTECTION_LEVEL_HIGH +/* + * This is the maximum time (in us) required for performing 1 ECDSA signature + * in this configuration along some additional margin considerations + */ +#define ECDSA_MAX_SIG_TIME 24000 +#else /* CONFIG_ESP_CRYPTO_DPA_PROTECTION_LEVEL_HIGH */ +#define ECDSA_MAX_SIG_TIME 17500 +#endif /* !CONFIG_ESP_CRYPTO_DPA_PROTECTION_LEVEL_HIGH */ + +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_MASKING_CM +#define DUMMY_OP_COUNT ECDSA_SIGN_MAX_DUMMY_OP_COUNT +#else /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_MASKING_CM */ +#define DUMMY_OP_COUNT 0 +#endif /* !CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_MASKING_CM */ +#define ECDSA_CM_FIXED_SIG_TIME ECDSA_MAX_SIG_TIME * (DUMMY_OP_COUNT + 1) + +#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM */ + +__attribute__((unused)) static const char *TAG = "ecdsa_alt"; + +static void esp_ecdsa_acquire_hardware(void) +{ + esp_crypto_ecdsa_lock_acquire(); + + periph_module_enable(PERIPH_ECDSA_MODULE); + ecc_ll_power_up(); +} + +static void esp_ecdsa_release_hardware(void) +{ + periph_module_disable(PERIPH_ECDSA_MODULE); + ecc_ll_power_down(); + + esp_crypto_ecdsa_lock_release(); +} + +static void ecdsa_be_to_le(const uint8_t* be_point, uint8_t *le_point, uint8_t len) +{ + /* When the size is 24 bytes, it should be padded with 0 bytes*/ + memset(le_point, 0x0, 32); + + for(int i = 0; i < len; i++) { + le_point[i] = be_point[len - i - 1]; + } +} + +#ifdef CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN +int esp_ecdsa_privkey_load_mpi(mbedtls_mpi *key, int efuse_blk) +{ + if (!key) { + ESP_LOGE(TAG, "Invalid memory"); + return -1; + } + + if (efuse_blk < EFUSE_BLK_KEY0 || efuse_blk >= EFUSE_BLK_KEY_MAX) { + ESP_LOGE(TAG, "Invalid efuse block"); + return -1; + } + + mbedtls_mpi_init(key); + + /* We use the mbedtls_mpi struct to pass our own context to hardware ECDSA peripheral + * MPI struct expects `s` to be either 1 or -1, by setting it to 0xECD5A, we ensure that it does + * not collide with a valid MPI. This is done to differentiate between using the private key stored in efuse + * or using the private key provided by software + * + * `n` is used to store the efuse block which should be used as key + */ + key->MBEDTLS_PRIVATE(s) = ECDSA_KEY_MAGIC; + key->MBEDTLS_PRIVATE(n) = efuse_blk; + key->MBEDTLS_PRIVATE(p) = NULL; + + return 0; +} + +int esp_ecdsa_privkey_load_pk_context(mbedtls_pk_context *key_ctx, int efuse_blk) +{ + const mbedtls_pk_info_t *pk_info; + mbedtls_ecp_keypair *keypair; + + if (!key_ctx) { + ESP_LOGE(TAG, "Invalid memory"); + return -1; + } + + if (efuse_blk < EFUSE_BLK_KEY0 || efuse_blk >= EFUSE_BLK_KEY_MAX) { + ESP_LOGE(TAG, "Invalid efuse block"); + return -1; + } + + mbedtls_pk_init(key_ctx); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_ECDSA); + mbedtls_pk_setup(key_ctx, pk_info); + keypair = mbedtls_pk_ec(*key_ctx); + + return esp_ecdsa_privkey_load_mpi(&(keypair->MBEDTLS_PRIVATE(d)), efuse_blk); +} + +static int esp_ecdsa_sign(mbedtls_ecp_group *grp, mbedtls_mpi* r, mbedtls_mpi* s, + const mbedtls_mpi *d, const unsigned char* msg, size_t msg_len) +{ + ecdsa_curve_t curve; + esp_efuse_block_t blk; + uint16_t len; + uint8_t zeroes[MAX_ECDSA_COMPONENT_LEN] = {0}; + uint8_t sha_le[ECDSA_SHA_LEN]; + uint8_t r_le[MAX_ECDSA_COMPONENT_LEN]; + uint8_t s_le[MAX_ECDSA_COMPONENT_LEN]; + + if (!grp || !r || !s || !d || !msg) { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (msg_len != ECDSA_SHA_LEN) { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (grp->id == MBEDTLS_ECP_DP_SECP192R1) { + curve = ECDSA_CURVE_SECP192R1; + len = 24; + } else if (grp->id == MBEDTLS_ECP_DP_SECP256R1) { + curve = ECDSA_CURVE_SECP256R1; + len = 32; + } else { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (!esp_efuse_find_purpose(ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY, &blk)) { + ESP_LOGE(TAG, "No efuse block with purpose ECDSA_KEY found"); + return MBEDTLS_ERR_ECP_INVALID_KEY; + } + + ecdsa_be_to_le(msg, sha_le, len); + + esp_ecdsa_acquire_hardware(); + + bool process_again = false; + + do { + ecdsa_hal_config_t conf = { + .mode = ECDSA_MODE_SIGN_GEN, + .curve = curve, + .sha_mode = ECDSA_Z_USER_PROVIDED, + .efuse_key_blk = d->MBEDTLS_PRIVATE(n), + }; + +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM + uint64_t sig_time = esp_timer_get_time(); +#endif + ecdsa_hal_gen_signature(&conf, sha_le, r_le, s_le, len); +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN_CONSTANT_TIME_CM + sig_time = esp_timer_get_time() - sig_time; + if (sig_time < ECDSA_CM_FIXED_SIG_TIME) { + esp_rom_delay_us(ECDSA_CM_FIXED_SIG_TIME - sig_time); + } +#endif + process_again = !ecdsa_hal_get_operation_result() + || !memcmp(r_le, zeroes, len) + || !memcmp(s_le, zeroes, len); + + } while (process_again); + + esp_ecdsa_release_hardware(); + + mbedtls_mpi_read_binary_le(r, r_le, len); + mbedtls_mpi_read_binary_le(s, s_le, len); + + return 0; +} + +/* + * Compute ECDSA signature of a hashed message; + */ +extern int __real_mbedtls_ecdsa_sign(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + +int __wrap_mbedtls_ecdsa_sign(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + +int __wrap_mbedtls_ecdsa_sign(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) +{ + /* + * Check `d` whether it contains the hardware key + */ + if (d->MBEDTLS_PRIVATE(s) == ECDSA_KEY_MAGIC) { + // Use hardware ECDSA peripheral + return esp_ecdsa_sign(grp, r, s, d, buf, blen); + } else { + return __real_mbedtls_ecdsa_sign(grp, r, s, d, buf, blen, f_rng, p_rng); + } +} + +extern int __real_mbedtls_ecdsa_sign_restartable(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + int (*f_rng_blind)(void *, unsigned char *, size_t), void *p_rng_blind, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_sign_restartable(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + int (*f_rng_blind)(void *, unsigned char *, size_t), void *p_rng_blind, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_sign_restartable(mbedtls_ecp_group *grp, mbedtls_mpi *r, mbedtls_mpi *s, + const mbedtls_mpi *d, const unsigned char *buf, size_t blen, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + int (*f_rng_blind)(void *, unsigned char *, size_t), void *p_rng_blind, + mbedtls_ecdsa_restart_ctx *rs_ctx) +{ + /* + * Check `d` whether it contains the hardware key + */ + if (d->MBEDTLS_PRIVATE(s) == ECDSA_KEY_MAGIC) { + // Use hardware ECDSA peripheral + return esp_ecdsa_sign(grp, r, s, d, buf, blen); + } else { + return __real_mbedtls_ecdsa_sign_restartable(grp, r, s, d, buf, blen, f_rng, p_rng, f_rng_blind, p_rng_blind, rs_ctx); + } +} + +int __real_mbedtls_ecdsa_write_signature_restartable(mbedtls_ecdsa_context *ctx, + mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hlen, + unsigned char *sig, size_t sig_size, size_t *slen, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_write_signature_restartable(mbedtls_ecdsa_context *ctx, + mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hlen, + unsigned char *sig, size_t sig_size, size_t *slen, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +/* + * Convert a signature (given by context) to ASN.1 + */ +static int ecdsa_signature_to_asn1(const mbedtls_mpi *r, const mbedtls_mpi *s, + unsigned char *sig, size_t sig_size, + size_t *slen) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char buf[MBEDTLS_ECDSA_MAX_LEN] = { 0 }; + // Setting the pointer p to the end of the buffer as the functions used afterwards write in backwards manner in the given buffer. + unsigned char *p = buf + sizeof(buf); + size_t len = 0; + + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_mpi(&p, buf, s)); + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_mpi(&p, buf, r)); + + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&p, buf, len)); + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&p, buf, + MBEDTLS_ASN1_CONSTRUCTED | + MBEDTLS_ASN1_SEQUENCE)); + + if (len > sig_size) { + return MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL; + } + + memcpy(sig, p, len); + *slen = len; + + return 0; +} + +int __wrap_mbedtls_ecdsa_write_signature_restartable(mbedtls_ecdsa_context *ctx, + mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hlen, + unsigned char *sig, size_t sig_size, size_t *slen, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng, + mbedtls_ecdsa_restart_ctx *rs_ctx) +{ + if (ctx->MBEDTLS_PRIVATE(d).MBEDTLS_PRIVATE(s) != ECDSA_KEY_MAGIC) { + return __real_mbedtls_ecdsa_write_signature_restartable(ctx, md_alg, hash, hlen, sig, sig_size, slen, f_rng, p_rng, rs_ctx); + } + + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + mbedtls_mpi r, s; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + /* + * Check `d` whether it contains the hardware key + */ + if (ctx->MBEDTLS_PRIVATE(d).MBEDTLS_PRIVATE(s) == ECDSA_KEY_MAGIC) { + // Use hardware ECDSA peripheral + + MBEDTLS_MPI_CHK(esp_ecdsa_sign(&ctx->MBEDTLS_PRIVATE(grp), &r, &s, &ctx->MBEDTLS_PRIVATE(d), hash, hlen)); + } + + MBEDTLS_MPI_CHK(ecdsa_signature_to_asn1(&r, &s, sig, sig_size, slen)); + +cleanup: + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + + return ret; +} + +int __wrap_mbedtls_ecdsa_write_signature(mbedtls_ecdsa_context *ctx, + mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hlen, + unsigned char *sig, size_t sig_size, size_t *slen, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng); + +int __wrap_mbedtls_ecdsa_write_signature(mbedtls_ecdsa_context *ctx, + mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hlen, + unsigned char *sig, size_t sig_size, size_t *slen, + int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) +{ + return __wrap_mbedtls_ecdsa_write_signature_restartable( + ctx, md_alg, hash, hlen, sig, sig_size, slen, + f_rng, p_rng, NULL); +} +#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN */ + +#ifdef CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY +static int esp_ecdsa_verify(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s) +{ + ecdsa_curve_t curve; + uint16_t len; + uint8_t r_le[MAX_ECDSA_COMPONENT_LEN]; + uint8_t s_le[MAX_ECDSA_COMPONENT_LEN]; + uint8_t qx_le[MAX_ECDSA_COMPONENT_LEN]; + uint8_t qy_le[MAX_ECDSA_COMPONENT_LEN]; + uint8_t sha_le[ECDSA_SHA_LEN]; + + if (!grp || !buf || !Q || !r || !s) { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (blen != ECDSA_SHA_LEN) { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (grp->id == MBEDTLS_ECP_DP_SECP192R1) { + curve = ECDSA_CURVE_SECP192R1; + len = 24; + } else if (grp->id == MBEDTLS_ECP_DP_SECP256R1) { + curve = ECDSA_CURVE_SECP256R1; + len = 32; + } else { + return MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + } + + if (mbedtls_mpi_cmp_int(r, 1) < 0 || mbedtls_mpi_cmp_mpi(r, &grp->N) >= 0 || + mbedtls_mpi_cmp_int(s, 1) < 0 || mbedtls_mpi_cmp_mpi(s, &grp->N) >= 0 ) + { + return MBEDTLS_ERR_ECP_VERIFY_FAILED; + } + + ecdsa_be_to_le(buf, sha_le, len); + + mbedtls_mpi_write_binary_le(&Q->MBEDTLS_PRIVATE(X), qx_le, len); + mbedtls_mpi_write_binary_le(&Q->MBEDTLS_PRIVATE(Y), qy_le, len); + mbedtls_mpi_write_binary_le(r, r_le, len); + mbedtls_mpi_write_binary_le(s, s_le, len); + + esp_ecdsa_acquire_hardware(); + + ecdsa_hal_config_t conf = { + .mode = ECDSA_MODE_SIGN_VERIFY, + .curve = curve, + .sha_mode = ECDSA_Z_USER_PROVIDED, + }; + + int ret = ecdsa_hal_verify_signature(&conf, sha_le, r_le, s_le, qx_le, qy_le, len); + + esp_ecdsa_release_hardware(); + + if (ret != 0) { + return MBEDTLS_ERR_ECP_VERIFY_FAILED; + } + + return ret; +} + +/* + * Verify ECDSA signature of hashed message + */ +extern int __real_mbedtls_ecdsa_verify_restartable(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_verify_restartable(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_verify_restartable(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s, + mbedtls_ecdsa_restart_ctx *rs_ctx) +{ + if ((grp->id == MBEDTLS_ECP_DP_SECP192R1 || grp->id == MBEDTLS_ECP_DP_SECP256R1) && blen == ECDSA_SHA_LEN) { + return esp_ecdsa_verify(grp, buf, blen, Q, r, s); + } else { + return __real_mbedtls_ecdsa_verify_restartable(grp, buf, blen, Q, r, s, rs_ctx); + } +} + +/* + * Verify ECDSA signature of hashed message + */ +extern int __real_mbedtls_ecdsa_verify(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s); + +int __wrap_mbedtls_ecdsa_verify(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s); + +int __wrap_mbedtls_ecdsa_verify(mbedtls_ecp_group *grp, + const unsigned char *buf, size_t blen, + const mbedtls_ecp_point *Q, + const mbedtls_mpi *r, + const mbedtls_mpi *s) +{ + return __wrap_mbedtls_ecdsa_verify_restartable(grp, buf, blen, Q, r, s, NULL); +} + + +int __real_mbedtls_ecdsa_read_signature_restartable(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_read_signature_restartable(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen, + mbedtls_ecdsa_restart_ctx *rs_ctx); + +int __wrap_mbedtls_ecdsa_read_signature_restartable(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen, + mbedtls_ecdsa_restart_ctx *rs_ctx) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char *p = (unsigned char *) sig; + const unsigned char *end = sig + slen; + size_t len; + mbedtls_mpi r, s; + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + if ((ret = mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) { + ret += MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + goto cleanup; + } + + if (p + len != end) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_ECP_BAD_INPUT_DATA, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + goto cleanup; + } + + if ((ret = mbedtls_asn1_get_mpi(&p, end, &r)) != 0 || + (ret = mbedtls_asn1_get_mpi(&p, end, &s)) != 0) { + ret += MBEDTLS_ERR_ECP_BAD_INPUT_DATA; + goto cleanup; + } + + if ((ret = __wrap_mbedtls_ecdsa_verify_restartable(&ctx->MBEDTLS_PRIVATE(grp), hash, hlen, + &ctx->MBEDTLS_PRIVATE(Q), &r, &s, NULL)) != 0) { + goto cleanup; + } + + /* At this point we know that the buffer starts with a valid signature. + * Return 0 if the buffer just contains the signature, and a specific + * error code if the valid signature is followed by more data. */ + if (p != end) { + ret = MBEDTLS_ERR_ECP_SIG_LEN_MISMATCH; + } + +cleanup: + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + + return ret; +} + + +int __real_mbedtls_ecdsa_read_signature(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen); + +int __wrap_mbedtls_ecdsa_read_signature(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen); + +int __wrap_mbedtls_ecdsa_read_signature(mbedtls_ecdsa_context *ctx, + const unsigned char *hash, size_t hlen, + const unsigned char *sig, size_t slen) +{ + return __wrap_mbedtls_ecdsa_read_signature_restartable( + ctx, hash, hlen, sig, slen, NULL); +} +#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY */ diff --git a/components/mbedtls/mbedtls_v3/port/esp_bignum.c b/components/mbedtls/mbedtls_v3/port/esp_bignum.c new file mode 100644 index 000000000..e9596bec8 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_bignum.c @@ -0,0 +1,677 @@ +/* + * Multi-precision integer library + * ESP-IDF hardware accelerated parts based on mbedTLS implementation + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +#include +#include +#include +#include +#include +#include +#include + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#if CONFIG_PM_ENABLE +#include "esp_pm.h" +#endif + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "soc/hwcrypto_periph.h" +#include "soc/periph_defs.h" +#include "soc/soc_caps.h" + +#include "bignum_impl.h" + +#include + + +/* Some implementation notes: + * + * - Naming convention x_words, y_words, z_words for number of words (limbs) used in a particular + * bignum. This number may be less than the size of the bignum + * + * - Naming convention hw_words for the hardware length of the operation. This number maybe be rounded up + * for targets that requres this (e.g. ESP32), and may be larger than any of the numbers + * involved in the calculation. + * + * - Timing behaviour of these functions will depend on the length of the inputs. This is fundamentally + * the same constraint as the software mbedTLS implementations, and relies on the same + * countermeasures (exponent blinding, etc) which are used in mbedTLS. + */ + +static const __attribute__((unused)) char *TAG = "bignum"; + +#define ciL (sizeof(mbedtls_mpi_uint)) /* chars in limb */ +#define biL (ciL << 3) /* bits in limb */ + +#if defined(CONFIG_MBEDTLS_MPI_USE_INTERRUPT) +static SemaphoreHandle_t op_complete_sem; +#if defined(CONFIG_PM_ENABLE) +static esp_pm_lock_handle_t s_pm_cpu_lock; +static esp_pm_lock_handle_t s_pm_sleep_lock; +#endif + +static IRAM_ATTR void esp_mpi_complete_isr(void *arg) +{ + BaseType_t higher_woken; + esp_mpi_interrupt_clear(); + + xSemaphoreGiveFromISR(op_complete_sem, &higher_woken); + if (higher_woken) { + portYIELD_FROM_ISR(); + } +} + + +static esp_err_t esp_mpi_isr_initialise(void) +{ + esp_mpi_interrupt_clear(); + esp_mpi_interrupt_enable(true); + if (op_complete_sem == NULL) { + static StaticSemaphore_t op_sem_buf; + op_complete_sem = xSemaphoreCreateBinaryStatic(&op_sem_buf); + if (op_complete_sem == NULL) { + ESP_LOGE(TAG, "Failed to create intr semaphore"); + return ESP_FAIL; + } + + esp_err_t ret; + ret = esp_intr_alloc(ETS_RSA_INTR_SOURCE, 0, esp_mpi_complete_isr, NULL, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate RSA interrupt %d", ret); + + // This should be treated as fatal error as this API would mostly + // be invoked within mbedTLS interface. There is no way for the system + // to proceed if the MPI interrupt allocation fails here. + abort(); + } + } + + /* MPI is clocked proportionally to CPU clock, take power management lock */ +#ifdef CONFIG_PM_ENABLE + if (s_pm_cpu_lock == NULL) { + if (esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "mpi_sleep", &s_pm_sleep_lock) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create PM sleep lock"); + return ESP_FAIL; + } + if (esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "mpi_cpu", &s_pm_cpu_lock) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create PM CPU lock"); + return ESP_FAIL; + } + } + esp_pm_lock_acquire(s_pm_cpu_lock); + esp_pm_lock_acquire(s_pm_sleep_lock); +#endif + + return ESP_OK; +} + +static int esp_mpi_wait_intr(void) +{ + if (!xSemaphoreTake(op_complete_sem, 2000 / portTICK_PERIOD_MS)) { + ESP_LOGE("MPI", "Timed out waiting for completion of MPI Interrupt"); + return -1; + } + +#ifdef CONFIG_PM_ENABLE + esp_pm_lock_release(s_pm_cpu_lock); + esp_pm_lock_release(s_pm_sleep_lock); +#endif // CONFIG_PM_ENABLE + + esp_mpi_interrupt_enable(false); + + return 0; +} + +#endif // CONFIG_MBEDTLS_MPI_USE_INTERRUPT + +/* Convert bit count to word count + */ +static inline size_t bits_to_words(size_t bits) +{ + return (bits + 31) / 32; +} + +/* Return the number of words actually used to represent an mpi + number. +*/ +#if defined(MBEDTLS_MPI_EXP_MOD_ALT) || defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) +static size_t mpi_words(const mbedtls_mpi *mpi) +{ + for (size_t i = mpi->MBEDTLS_PRIVATE(n); i > 0; i--) { + if (mpi->MBEDTLS_PRIVATE(p[i - 1]) != 0) { + return i; + } + } + return 0; +} + +#endif //(MBEDTLS_MPI_EXP_MOD_ALT || MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) + +/** + * + * There is a need for the value of integer N' such that B^-1(B-1)-N^-1N'=1, + * where B^-1(B-1) mod N=1. Actually, only the least significant part of + * N' is needed, hence the definition N0'=N' mod b. We reproduce below the + * simple algorithm from an article by Dusse and Kaliski to efficiently + * find N0' from N0 and b + */ +static mbedtls_mpi_uint modular_inverse(const mbedtls_mpi *M) +{ + int i; + uint64_t t = 1; + uint64_t two_2_i_minus_1 = 2; /* 2^(i-1) */ + uint64_t two_2_i = 4; /* 2^i */ + uint64_t N = M->MBEDTLS_PRIVATE(p[0]); + + for (i = 2; i <= 32; i++) { + if ((mbedtls_mpi_uint) N * t % two_2_i >= two_2_i_minus_1) { + t += two_2_i_minus_1; + } + + two_2_i_minus_1 <<= 1; + two_2_i <<= 1; + } + + return (mbedtls_mpi_uint)(UINT32_MAX - t + 1); +} + +/* Calculate Rinv = RR^2 mod M, where: + * + * R = b^n where b = 2^32, n=num_words, + * R = 2^N (where N=num_bits) + * RR = R^2 = 2^(2*N) (where N=num_bits=num_words*32) + * + * This calculation is computationally expensive (mbedtls_mpi_mod_mpi) + * so caller should cache the result where possible. + * + * DO NOT call this function while holding esp_mpi_enable_hardware_hw_op(). + * + */ +static int calculate_rinv(mbedtls_mpi *Rinv, const mbedtls_mpi *M, int num_words) +{ + int ret; + size_t num_bits = num_words * 32; + mbedtls_mpi RR; + mbedtls_mpi_init(&RR); + MBEDTLS_MPI_CHK(mbedtls_mpi_set_bit(&RR, num_bits * 2, 1)); + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(Rinv, &RR, M)); + +cleanup: + mbedtls_mpi_free(&RR); + + return ret; +} + + + + + + +/* Z = (X * Y) mod M + + Not an mbedTLS function +*/ +int esp_mpi_mul_mpi_mod(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M) +{ + int ret = 0; + + size_t x_bits = mbedtls_mpi_bitlen(X); + size_t y_bits = mbedtls_mpi_bitlen(Y); + size_t m_bits = mbedtls_mpi_bitlen(M); + size_t z_bits = MIN(m_bits, x_bits + y_bits); + size_t x_words = bits_to_words(x_bits); + size_t y_words = bits_to_words(y_bits); + size_t m_words = bits_to_words(m_bits); + size_t z_words = bits_to_words(z_bits); + size_t hw_words = esp_mpi_hardware_words(MAX(x_words, MAX(y_words, m_words))); /* longest operand */ + mbedtls_mpi Rinv; + mbedtls_mpi_uint Mprime; + + /* Calculate and load the first stage montgomery multiplication */ + mbedtls_mpi_init(&Rinv); + MBEDTLS_MPI_CHK(calculate_rinv(&Rinv, M, hw_words)); + Mprime = modular_inverse(M); + + esp_mpi_enable_hardware_hw_op(); + /* Load and start a (X * Y) mod M calculation */ + esp_mpi_mul_mpi_mod_hw_op(X, Y, M, &Rinv, Mprime, hw_words); + + MBEDTLS_MPI_CHK(mbedtls_mpi_grow(Z, z_words)); + + esp_mpi_read_result_hw_op(Z, z_words); + Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); + +cleanup: + mbedtls_mpi_free(&Rinv); + esp_mpi_disable_hardware_hw_op(); + + return ret; +} + +#if defined(MBEDTLS_MPI_EXP_MOD_ALT) || defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) + +#ifdef ESP_MPI_USE_MONT_EXP +/* + * Return the most significant one-bit. + */ +static size_t mbedtls_mpi_msb( const mbedtls_mpi *X ) +{ + int i, j; + if (X != NULL && X->MBEDTLS_PRIVATE(n) != 0) { + for (i = X->MBEDTLS_PRIVATE(n) - 1; i >= 0; i--) { + if (X->MBEDTLS_PRIVATE(p[i]) != 0) { + for (j = biL - 1; j >= 0; j--) { + if ((X->MBEDTLS_PRIVATE(p[i]) & (1 << j)) != 0) { + return (i * biL) + j; + } + } + } + } + } + return 0; +} + +/* + * Montgomery exponentiation: Z = X ^ Y mod M (HAC 14.94) + */ +static int mpi_montgomery_exp_calc( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, + mbedtls_mpi *Rinv, + size_t hw_words, + mbedtls_mpi_uint Mprime ) +{ + int ret = 0; + mbedtls_mpi X_, one; + + mbedtls_mpi_init(&X_); + mbedtls_mpi_init(&one); + if ( ( ( ret = mbedtls_mpi_grow(&one, hw_words) ) != 0 ) || + ( ( ret = mbedtls_mpi_set_bit(&one, 0, 1) ) != 0 ) ) { + goto cleanup2; + } + + // Algorithm from HAC 14.94 + { + // 0 determine t (highest bit set in y) + int t = mbedtls_mpi_msb(Y); + + esp_mpi_enable_hardware_hw_op(); + + // 1.1 x_ = mont(x, R^2 mod m) + // = mont(x, rb) + MBEDTLS_MPI_CHK( esp_mont_hw_op(&X_, X, Rinv, M, Mprime, hw_words, false) ); + + // 1.2 z = R mod m + // now z = R mod m = Mont (R^2 mod m, 1) mod M (as Mont(x) = X&R^-1 mod M) + MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Rinv, &one, M, Mprime, hw_words, true) ); + + // 2 for i from t down to 0 + for (int i = t; i >= 0; i--) { + // 2.1 z = mont(z,z) + if (i != t) { // skip on the first iteration as is still unity + MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, Z, M, Mprime, hw_words, true) ); + } + + // 2.2 if y[i] = 1 then z = mont(A, x_) + if (mbedtls_mpi_get_bit(Y, i)) { + MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, &X_, M, Mprime, hw_words, true) ); + } + } + + // 3 z = Mont(z, 1) + MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, &one, M, Mprime, hw_words, true) ); + } + +cleanup: + esp_mpi_disable_hardware_hw_op(); + +cleanup2: + mbedtls_mpi_free(&X_); + mbedtls_mpi_free(&one); + return ret; +} + +#endif //USE_MONT_EXPONENATIATION + +/* + * Z = X ^ Y mod M + * + * _Rinv is optional pre-calculated version of Rinv (via calculate_rinv()). + * + * (See RSA Accelerator section in Technical Reference for more about Mprime, Rinv) + * + */ +static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, mbedtls_mpi *_Rinv ) +{ + int ret = 0; + + mbedtls_mpi Rinv_new; /* used if _Rinv == NULL */ + mbedtls_mpi *Rinv; /* points to _Rinv (if not NULL) othwerwise &RR_new */ + mbedtls_mpi_uint Mprime; + + size_t x_words = mpi_words(X); + size_t y_words = mpi_words(Y); + size_t m_words = mpi_words(M); + + /* "all numbers must be the same length", so choose longest number + as cardinal length of operation... + */ + size_t num_words = esp_mpi_hardware_words(MAX(m_words, MAX(x_words, y_words))); + + if (num_words * 32 > SOC_RSA_MAX_BIT_LEN) { + return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + } + + if (mbedtls_mpi_cmp_int(M, 0) <= 0 || (M->MBEDTLS_PRIVATE(p[0]) & 1) == 0) { + return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + } + + if (mbedtls_mpi_cmp_int(Y, 0) < 0) { + return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + } + + if (mbedtls_mpi_cmp_int(Y, 0) == 0) { + return mbedtls_mpi_lset(Z, 1); + } + + /* Determine RR pointer, either _RR for cached value + or local RR_new */ + if (_Rinv == NULL) { + mbedtls_mpi_init(&Rinv_new); + Rinv = &Rinv_new; + } else { + Rinv = _Rinv; + } + if (Rinv->MBEDTLS_PRIVATE(p) == NULL) { + MBEDTLS_MPI_CHK(calculate_rinv(Rinv, M, num_words)); + } + + Mprime = modular_inverse(M); + + // Montgomery exponentiation: Z = X ^ Y mod M (HAC 14.94) +#ifdef ESP_MPI_USE_MONT_EXP + ret = mpi_montgomery_exp_calc(Z, X, Y, M, Rinv, num_words, Mprime) ; + MBEDTLS_MPI_CHK(ret); +#else + esp_mpi_enable_hardware_hw_op(); + +#if defined (CONFIG_MBEDTLS_MPI_USE_INTERRUPT) + if (esp_mpi_isr_initialise() != ESP_OK) { + ret = -1; + esp_mpi_disable_hardware_hw_op(); + goto cleanup; + } +#endif + + esp_mpi_exp_mpi_mod_hw_op(X, Y, M, Rinv, Mprime, num_words); + ret = mbedtls_mpi_grow(Z, m_words); + if (ret != 0) { + esp_mpi_disable_hardware_hw_op(); + goto cleanup; + } + +#if defined(CONFIG_MBEDTLS_MPI_USE_INTERRUPT) + ret = esp_mpi_wait_intr(); + if (ret != 0) { + esp_mpi_disable_hardware_hw_op(); + goto cleanup; + } +#endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT + + esp_mpi_read_result_hw_op(Z, m_words); + esp_mpi_disable_hardware_hw_op(); +#endif + + // Compensate for negative X + if (X->MBEDTLS_PRIVATE(s) == -1 && (Y->MBEDTLS_PRIVATE(p[0]) & 1) != 0) { + Z->MBEDTLS_PRIVATE(s) = -1; + MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(Z, M, Z)); + } else { + Z->MBEDTLS_PRIVATE(s) = 1; + } + +cleanup: + if (_Rinv == NULL) { + mbedtls_mpi_free(&Rinv_new); + } + return ret; +} + +#endif /* (MBEDTLS_MPI_EXP_MOD_ALT || MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) */ + +/* + * Sliding-window exponentiation: X = A^E mod N (HAC 14.85) + */ +int mbedtls_mpi_exp_mod( mbedtls_mpi *X, const mbedtls_mpi *A, + const mbedtls_mpi *E, const mbedtls_mpi *N, + mbedtls_mpi *_RR ) +{ + int ret; +#if defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) + /* Try hardware API first and then fallback to software */ + ret = esp_mpi_exp_mod( X, A, E, N, _RR ); + if( ret == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE ) { + ret = mbedtls_mpi_exp_mod_soft( X, A, E, N, _RR ); + } +#else + /* Hardware approach */ + ret = esp_mpi_exp_mod( X, A, E, N, _RR ); +#endif + /* Note: For software only approach, it gets handled in mbedTLS library. + This file is not part of build objects for that case */ + + return ret; +} + +#if defined(MBEDTLS_MPI_MUL_MPI_ALT) /* MBEDTLS_MPI_MUL_MPI_ALT */ + +static int mpi_mult_mpi_failover_mod_mult( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t z_words); +static int mpi_mult_mpi_overlong(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t y_words, size_t z_words); + +/* Z = X * Y */ +int mbedtls_mpi_mul_mpi( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y ) +{ + int ret = 0; + size_t x_bits = mbedtls_mpi_bitlen(X); + size_t y_bits = mbedtls_mpi_bitlen(Y); + size_t x_words = bits_to_words(x_bits); + size_t y_words = bits_to_words(y_bits); + size_t z_words = bits_to_words(x_bits + y_bits); + size_t hw_words = esp_mpi_hardware_words(MAX(x_words, y_words)); // length of one operand in hardware + + /* Short-circuit eval if either argument is 0 or 1. + + This is needed as the mpi modular division + argument will sometimes call in here when one + argument is too large for the hardware unit, but the other + argument is zero or one. + */ + if (x_bits == 0 || y_bits == 0) { + mbedtls_mpi_lset(Z, 0); + return 0; + } + if (x_bits == 1) { + ret = mbedtls_mpi_copy(Z, Y); + Z->MBEDTLS_PRIVATE(s) *= X->MBEDTLS_PRIVATE(s); + return ret; + } + if (y_bits == 1) { + ret = mbedtls_mpi_copy(Z, X); + Z->MBEDTLS_PRIVATE(s) *= Y->MBEDTLS_PRIVATE(s); + return ret; + } + + /* Grow Z to result size early, avoid interim allocations */ + MBEDTLS_MPI_CHK( mbedtls_mpi_grow(Z, z_words) ); + + /* If either factor is over 2048 bits, we can't use the standard hardware multiplier + (it assumes result is double longest factor, and result is max 4096 bits.) + + However, we can fail over to mod_mult for up to 4096 bits of result (modulo + multiplication doesn't have the same restriction, so result is simply the + number of bits in X plus number of bits in in Y.) + */ + if (hw_words * 32 > SOC_RSA_MAX_BIT_LEN/2) { + if (z_words * 32 <= SOC_RSA_MAX_BIT_LEN) { + /* Note: it's possible to use mpi_mult_mpi_overlong + for this case as well, but it's very slightly + slower and requires a memory allocation. + */ + return mpi_mult_mpi_failover_mod_mult(Z, X, Y, z_words); + } else { + /* Still too long for the hardware unit... */ + if (y_words > x_words) { + return mpi_mult_mpi_overlong(Z, X, Y, y_words, z_words); + } else { + return mpi_mult_mpi_overlong(Z, Y, X, x_words, z_words); + } + } + } + + /* Otherwise, we can use the (faster) multiply hardware unit */ + esp_mpi_enable_hardware_hw_op(); + + esp_mpi_mul_mpi_hw_op(X, Y, hw_words); + esp_mpi_read_result_hw_op(Z, z_words); + + esp_mpi_disable_hardware_hw_op(); + + Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); + +cleanup: + return ret; +} + +int mbedtls_mpi_mul_int( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_uint b ) +{ + mbedtls_mpi _B; + mbedtls_mpi_uint p[1]; + + _B.MBEDTLS_PRIVATE(s) = 1; + _B.MBEDTLS_PRIVATE(n) = 1; + _B.MBEDTLS_PRIVATE(p) = p; + p[0] = b; + + return( mbedtls_mpi_mul_mpi( X, A, &_B ) ); +} + +/* Deal with the case when X & Y are too long for the hardware unit, by splitting one operand + into two halves. + + Y must be the longer operand + + Slice Y into Yp, Ypp such that: + Yp = lower 'b' bits of Y + Ypp = upper 'b' bits of Y (right shifted) + + Such that + Z = X * Y + Z = X * (Yp + Ypp<MBEDTLS_PRIVATE(p), + .MBEDTLS_PRIVATE(n) = words_slice, + .MBEDTLS_PRIVATE(s) = Y->MBEDTLS_PRIVATE(s) + }; + /* Ypp holds upper bits of Y, right shifted (also reuses Y's array contents) */ + const mbedtls_mpi Ypp = { + .MBEDTLS_PRIVATE(p) = Y->MBEDTLS_PRIVATE(p) + words_slice, + .MBEDTLS_PRIVATE(n) = y_words - words_slice, + .MBEDTLS_PRIVATE(s) = Y->MBEDTLS_PRIVATE(s) + }; + mbedtls_mpi_init(&Ztemp); + + /* Get result Ztemp = Yp * X (need temporary variable Ztemp) */ + MBEDTLS_MPI_CHK( mbedtls_mpi_mul_mpi(&Ztemp, X, &Yp) ); + + /* Z = Ypp * Y */ + MBEDTLS_MPI_CHK( mbedtls_mpi_mul_mpi(Z, X, &Ypp) ); + + /* Z = Z << b */ + MBEDTLS_MPI_CHK( mbedtls_mpi_shift_l(Z, words_slice * 32) ); + + /* Z += Ztemp */ + MBEDTLS_MPI_CHK( mbedtls_mpi_add_mpi(Z, Z, &Ztemp) ); + +cleanup: + mbedtls_mpi_free(&Ztemp); + + return ret; +} + +/* Special-case of mbedtls_mpi_mult_mpi(), where we use hardware montgomery mod + multiplication to calculate an mbedtls_mpi_mult_mpi result where either + A or B are >2048 bits so can't use the standard multiplication method. + + Result (number of words, based on A bits + B bits) must still be less than 4096 bits. + + This case is simpler than the general case modulo multiply of + esp_mpi_mul_mpi_mod() because we can control the other arguments: + + * Modulus is chosen with M=(2^num_bits - 1) (ie M=R-1), so output + * Mprime and Rinv are therefore predictable as follows: + isn't actually modulo anything. + Mprime 1 + Rinv 1 + + (See RSA Accelerator section in Technical Reference for more about Mprime, Rinv) +*/ + +static int mpi_mult_mpi_failover_mod_mult( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t z_words) +{ + int ret; + size_t hw_words = esp_mpi_hardware_words(z_words); + + esp_mpi_enable_hardware_hw_op(); + + esp_mpi_mult_mpi_failover_mod_mult_hw_op(X, Y, hw_words ); + MBEDTLS_MPI_CHK( mbedtls_mpi_grow(Z, hw_words) ); + esp_mpi_read_result_hw_op(Z, hw_words); + + Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); + /* + * Relevant: https://github.com/espressif/esp-idf/issues/11850 + * If the first condition fails then most likely hardware peripheral + * has produced an incorrect result for MPI operation. This can + * happen if data fed to the peripheral register was incorrect. + * + * z_words is calculated as the worst-case possible size of the result + * MPI Z. The difference between z_words and the actual words taken by + * the MPI result (mpi_words(Z)) can be a maximum of 1 word. + * The value z_bits (actual bits taken by the MPI result) is calculated + * as x_bits + y_bits bits, however, in some cases, z_bits can be + * x_bits + y_bits - 1 bits (see example below). + * 0b1111 * 0b1111 = 0b11100001 -> 8 bits + * 0b1000 * 0b1000 = 0b01000000 -> 7 bits. + * The code rounds up to the nearest word size, so the maximum difference + * could be of only 1 word. The second condition handles this. + */ + assert((z_words >= mpi_words(Z)) && (z_words - mpi_words(Z) <= (size_t)1)); +cleanup: + esp_mpi_disable_hardware_hw_op(); + return ret; +} + +#endif /* MBEDTLS_MPI_MUL_MPI_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/esp_ds/esp_rsa_sign_alt.c b/components/mbedtls/mbedtls_v3/port/esp_ds/esp_rsa_sign_alt.c new file mode 100644 index 000000000..023c33671 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_ds/esp_rsa_sign_alt.c @@ -0,0 +1,273 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_ds.h" +#include "rsa_sign_alt.h" +#include "esp_memory_utils.h" + +#ifdef CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/digital_signature.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32c3/rom/digital_signature.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/digital_signature.h" +#elif CONFIG_IDF_TARGET_ESP32C6 +#include "esp32c6/rom/digital_signature.h" +#elif CONFIG_IDF_TARGET_ESP32H2 +#include "esp32h2/rom/digital_signature.h" +#else +#error "Selected target does not support esp_rsa_sign_alt (for DS)" +#endif + +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include +static const char *TAG = "ESP_RSA_SIGN_ALT"; +#define SWAP_INT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24)) + +#include "mbedtls/rsa.h" +#include "mbedtls/oid.h" +#include "mbedtls/platform_util.h" +#include + +static hmac_key_id_t s_esp_ds_hmac_key_id; +static esp_ds_data_t *s_ds_data; +static SemaphoreHandle_t s_ds_lock; +static int s_timeout_ms = 0; + +/* key length in bytes = (esp_digital_signature_length_t key + 1 ) * FACTOR_KEYLEN_IN_BYTES */ +#define FACTOR_KEYLEN_IN_BYTES 4 + +/* Lock for the DS session, other TLS connections trying to use the DS peripheral will be blocked + * till this DS session is completed (i.e. TLS handshake for this connection is completed) */ +static void __attribute__((constructor)) esp_ds_conn_lock (void) +{ + if ((s_ds_lock = xSemaphoreCreateMutex()) == NULL) { + ESP_EARLY_LOGE(TAG, "mutex for the DS session lock could not be created"); + } +} + +void esp_ds_set_session_timeout(int timeout) +{ + /* add additional offset of 1000 ms to have enough time for deleting the TLS connection and free the previous ds context after exceeding timeout value (this offset also helps when timeout is set to 0) */ + if (timeout > s_timeout_ms) { + s_timeout_ms = timeout + 1000; + } +} + +esp_err_t esp_ds_init_data_ctx(esp_ds_data_ctx_t *ds_data) +{ + if (ds_data == NULL || ds_data->esp_ds_data == NULL) { + return ESP_ERR_INVALID_ARG; + } + /* mutex is given back when the DS context is freed after the TLS handshake is completed or in case of failure (at cleanup) */ + if ((xSemaphoreTake(s_ds_lock, s_timeout_ms / portTICK_PERIOD_MS) != pdTRUE)) { + ESP_LOGE(TAG, "ds_lock could not be obtained in specified time"); + return ESP_FAIL; + } + s_ds_data = ds_data->esp_ds_data; + ESP_LOGD(TAG, "Using DS with key block %u, RSA length %u", ds_data->efuse_key_id, ds_data->rsa_length_bits); + s_esp_ds_hmac_key_id = (hmac_key_id_t) ds_data->efuse_key_id; + + const unsigned rsa_length_int = (ds_data->rsa_length_bits / 32) - 1; + if (esp_ptr_byte_accessible(s_ds_data)) { + /* calculate the rsa_length in terms of esp_digital_signature_length_t which is required for the internal DS API */ + s_ds_data->rsa_length = rsa_length_int; + } else if (s_ds_data->rsa_length != rsa_length_int) { + /* + * Configuration data is most likely from DROM segment and it + * is not properly formatted for all parameters consideration. + * Moreover, we can not modify as it is read-only and hence + * the error. + */ + ESP_LOGE(TAG, "RSA length mismatch %u, %u", s_ds_data->rsa_length, rsa_length_int); + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +void esp_ds_release_ds_lock(void) +{ + if (xSemaphoreGetMutexHolder(s_ds_lock) == xTaskGetCurrentTaskHandle()) { + /* Give back the semaphore (DS lock) */ + xSemaphoreGive(s_ds_lock); + } +} + +size_t esp_ds_get_keylen(void *ctx) +{ + /* calculating the rsa_length in bytes */ + return ((s_ds_data->rsa_length + 1) * FACTOR_KEYLEN_IN_BYTES); +} + +static int rsa_rsassa_pkcs1_v15_encode( mbedtls_md_type_t md_alg, + unsigned int hashlen, + const unsigned char *hash, + size_t dst_len, + unsigned char *dst ) +{ + size_t oid_size = 0; + size_t nb_pad = dst_len; + unsigned char *p = dst; + const char *oid = NULL; + + /* Are we signing hashed or raw data? */ + if ( md_alg != MBEDTLS_MD_NONE ) { + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type( md_alg ); + if ( md_info == NULL ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + + if ( mbedtls_oid_get_oid_by_md( md_alg, &oid, &oid_size ) != 0 ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + + hashlen = mbedtls_md_get_size( md_info ); + + /* Double-check that 8 + hashlen + oid_size can be used as a + * 1-byte ASN.1 length encoding and that there's no overflow. */ + if ( 8 + hashlen + oid_size >= 0x80 || + 10 + hashlen < hashlen || + 10 + hashlen + oid_size < 10 + hashlen ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + + /* + * Static bounds check: + * - Need 10 bytes for five tag-length pairs. + * (Insist on 1-byte length encodings to protect against variants of + * Bleichenbacher's forgery attack against lax PKCS#1v1.5 verification) + * - Need hashlen bytes for hash + * - Need oid_size bytes for hash alg OID. + */ + if ( nb_pad < 10 + hashlen + oid_size ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + nb_pad -= 10 + hashlen + oid_size; + } else { + if ( nb_pad < hashlen ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + + nb_pad -= hashlen; + } + + /* Need space for signature header and padding delimiter (3 bytes), + * and 8 bytes for the minimal padding */ + if ( nb_pad < 3 + 8 ) { + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + nb_pad -= 3; + + /* Now nb_pad is the amount of memory to be filled + * with padding, and at least 8 bytes long. */ + + /* Write signature header and padding */ + *p++ = 0; + *p++ = MBEDTLS_RSA_SIGN; + memset( p, 0xFF, nb_pad ); + p += nb_pad; + *p++ = 0; + + /* Are we signing raw data? */ + if ( md_alg == MBEDTLS_MD_NONE ) { + memcpy( p, hash, hashlen ); + return ( 0 ); + } + + /* Signing hashed data, add corresponding ASN.1 structure + * + * DigestInfo ::= SEQUENCE { + * digestAlgorithm DigestAlgorithmIdentifier, + * digest Digest } + * DigestAlgorithmIdentifier ::= AlgorithmIdentifier + * Digest ::= OCTET STRING + * + * Schematic: + * TAG-SEQ + LEN [ TAG-SEQ + LEN [ TAG-OID + LEN [ OID ] + * TAG-NULL + LEN [ NULL ] ] + * TAG-OCTET + LEN [ HASH ] ] + */ + *p++ = MBEDTLS_ASN1_SEQUENCE | MBEDTLS_ASN1_CONSTRUCTED; + *p++ = (unsigned char)( 0x08 + oid_size + hashlen ); + *p++ = MBEDTLS_ASN1_SEQUENCE | MBEDTLS_ASN1_CONSTRUCTED; + *p++ = (unsigned char)( 0x04 + oid_size ); + *p++ = MBEDTLS_ASN1_OID; + *p++ = (unsigned char) oid_size; + memcpy( p, oid, oid_size ); + p += oid_size; + *p++ = MBEDTLS_ASN1_NULL; + *p++ = 0x00; + *p++ = MBEDTLS_ASN1_OCTET_STRING; + *p++ = (unsigned char) hashlen; + memcpy( p, hash, hashlen ); + p += hashlen; + + /* Just a sanity-check, should be automatic + * after the initial bounds check. */ + if ( p != dst + dst_len ) { + mbedtls_platform_zeroize( dst, dst_len ); + return ( MBEDTLS_ERR_RSA_BAD_INPUT_DATA ); + } + + return ( 0 ); +} + + +int esp_ds_rsa_sign( void *ctx, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + mbedtls_md_type_t md_alg, unsigned int hashlen, + const unsigned char *hash, unsigned char *sig ) +{ + esp_ds_context_t *esp_ds_ctx; + esp_err_t ds_r; + int ret = -1; + uint32_t *signature = heap_caps_malloc_prefer((s_ds_data->rsa_length + 1) * FACTOR_KEYLEN_IN_BYTES, 2, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); + if (signature == NULL) { + ESP_LOGE(TAG, "Could not allocate memory for internal DS operations"); + return -1; + } + + if ((ret = (rsa_rsassa_pkcs1_v15_encode( md_alg, hashlen, hash, ((s_ds_data->rsa_length + 1) * FACTOR_KEYLEN_IN_BYTES), sig ))) != 0) { + ESP_LOGE(TAG, "Error in pkcs1_v15 encoding, returned %d", ret); + heap_caps_free(signature); + return -1; + } + + for (unsigned int i = 0; i < (s_ds_data->rsa_length + 1); i++) { + signature[i] = SWAP_INT32(((uint32_t *)sig)[(s_ds_data->rsa_length + 1) - (i + 1)]); + } + + ds_r = esp_ds_start_sign((const void *)signature, + s_ds_data, + s_esp_ds_hmac_key_id, + &esp_ds_ctx); + if (ds_r != ESP_OK) { + ESP_LOGE(TAG, "Error in esp_ds_start_sign, returned %d ", ds_r); + heap_caps_free(signature); + return -1; + } + + ds_r = esp_ds_finish_sign((void *)signature, esp_ds_ctx); + if (ds_r != ESP_OK) { + if (ds_r == ESP_ERR_HW_CRYPTO_DS_INVALID_DIGEST) { + ESP_LOGE(TAG, "Invalid digest in DS data reported by esp_ds_finish_sign"); + } else { + ESP_LOGE(TAG, "Error in esp_ds_finish_sign, returned %d ", ds_r); + } + heap_caps_free(signature); + return -1; + } + + for (unsigned int i = 0; i < (s_ds_data->rsa_length + 1); i++) { + ((uint32_t *)sig)[i] = SWAP_INT32(((uint32_t *)signature)[(s_ds_data->rsa_length + 1) - (i + 1)]); + } + heap_caps_free(signature); + return 0; +} diff --git a/components/mbedtls/mbedtls_v3/port/esp_hardware.c b/components/mbedtls/mbedtls_v3/port/esp_hardware.c new file mode 100644 index 000000000..c7efea6a6 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_hardware.c @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include +#include +#include + +#include + +#include + +#ifndef MBEDTLS_ENTROPY_HARDWARE_ALT +#error "MBEDTLS_ENTROPY_HARDWARE_ALT should always be set in ESP-IDF" +#endif + +int mbedtls_hardware_poll( void *data, + unsigned char *output, size_t len, size_t *olen ) +{ + esp_fill_random(output, len); + *olen = len; + return 0; +} diff --git a/components/mbedtls/mbedtls_v3/port/esp_mem.c b/components/mbedtls/mbedtls_v3/port/esp_mem.c new file mode 100644 index 000000000..a716656b9 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_mem.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_mem.h" + +#ifndef CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC + +IRAM_ATTR void *esp_mbedtls_mem_calloc(size_t n, size_t size) +{ + return calloc(n, size); +} + +IRAM_ATTR void esp_mbedtls_mem_free(void *ptr) +{ + return heap_caps_free(ptr); +} + +#endif /* !CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC */ diff --git a/components/mbedtls/mbedtls_v3/port/esp_platform_time.c b/components/mbedtls/mbedtls_v3/port/esp_platform_time.c new file mode 100644 index 000000000..541b664ab --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_platform_time.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mbedtls/build_info.h" +#include "mbedtls/platform_time.h" + +#ifdef MBEDTLS_PLATFORM_MS_TIME_ALT +mbedtls_ms_time_t mbedtls_ms_time() +{ + int ret; + struct timespec tv = {}; + mbedtls_ms_time_t current_ms; + + ret = clock_gettime(CLOCK_MONOTONIC, &tv); + if (ret) { + return time(NULL) * 1000L; + } + + current_ms = tv.tv_sec; + return current_ms * 1000L + tv.tv_nsec / 1000000L; +} +#endif // MBEDTLS_PLATFORM_MS_TIME_ALT diff --git a/components/mbedtls/mbedtls_v3/port/esp_timing.c b/components/mbedtls/mbedtls_v3/port/esp_timing.c new file mode 100644 index 000000000..96858f765 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/esp_timing.c @@ -0,0 +1,94 @@ +/* + * Portable interface to the CPU cycle counter + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +/* + * mbedtls_timing_get_timer()m mbedtls_timing_set_delay() and + * mbedtls_timing_set_delay only abstracted from mbedtls/library/timing.c + * as that does not build on ESP-IDF but these 2 functions are needed for + * DTLS (in particular mbedtls_ssl_set_timer_cb() must be called for DTLS + * which requires these 2 delay functions). + */ + +#include + +#if !defined(MBEDTLS_ESP_TIMING_C) + +#include +#include "mbedtls/timing.h" + +struct _hr_time +{ + struct timeval start; +}; + +unsigned long mbedtls_timing_get_timer( struct mbedtls_timing_hr_time *val, int reset ) +{ + struct _hr_time *t = (struct _hr_time *) val; + + if( reset ) + { + gettimeofday( &t->start, NULL ); + return( 0 ); + } + else + { + unsigned long delta; + struct timeval now; + gettimeofday( &now, NULL ); + delta = ( now.tv_sec - t->start.tv_sec ) * 1000ul + + ( now.tv_usec - t->start.tv_usec ) / 1000; + return( delta ); + } +} + +/* + * Set delays to watch + */ +void mbedtls_timing_set_delay( void *data, uint32_t int_ms, uint32_t fin_ms ) +{ + mbedtls_timing_delay_context *ctx = (mbedtls_timing_delay_context *) data; + + ctx->MBEDTLS_PRIVATE(int_ms) = int_ms; + ctx->MBEDTLS_PRIVATE(fin_ms) = fin_ms; + + if( fin_ms != 0 ) + (void) mbedtls_timing_get_timer( &ctx->MBEDTLS_PRIVATE(timer), 1 ); +} + +/* + * Get number of delays expired + */ +int mbedtls_timing_get_delay( void *data ) +{ + mbedtls_timing_delay_context *ctx = (mbedtls_timing_delay_context *) data; + unsigned long elapsed_ms; + + if( ctx->MBEDTLS_PRIVATE(fin_ms) == 0 ) + return( -1 ); + + elapsed_ms = mbedtls_timing_get_timer( &ctx->MBEDTLS_PRIVATE(timer), 0 ); + + if( elapsed_ms >= ctx->MBEDTLS_PRIVATE(fin_ms) ) + return( 2 ); + + if( elapsed_ms >= ctx->MBEDTLS_PRIVATE(int_ms) ) + return( 1 ); + + return( 0 ); +} + +/* + * Get the final delay. + */ +uint32_t mbedtls_timing_get_final_delay( const mbedtls_timing_delay_context *data ) +{ + return( data->MBEDTLS_PRIVATE(fin_ms) ); +} + +#endif /* MBEDTLS_ESP_TIMING_C */ diff --git a/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes.h b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes.h new file mode 100644 index 000000000..c9c431696 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes.h @@ -0,0 +1,356 @@ +/** + * \brief AES block cipher, ESP hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ + +#pragma once + +#include "esp_types.h" +#include "hal/aes_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#define ERR_ESP_AES_INVALID_KEY_LENGTH -0x0020 /**< Invalid key length. */ +#define ERR_ESP_AES_INVALID_INPUT_LENGTH -0x0022 /**< Invalid data input length. */ + +/** + * \brief AES context structure + * + */ +typedef struct { + uint8_t key_bytes; + volatile uint8_t key_in_hardware; /* This variable is used for fault injection checks, so marked volatile to avoid optimisation */ + uint8_t key[32]; +} esp_aes_context; + +/** + * \brief The AES XTS context-type definition. + */ +typedef struct +{ + esp_aes_context crypt; /*!< The AES context to use for AES block + encryption or decryption. */ + esp_aes_context tweak; /*!< The AES context used for tweak + computation. */ +} esp_aes_xts_context; + + + + +/** + * \brief Lock access to AES hardware unit + * + * AES hardware unit can only be used by one + * consumer at a time. + * + * esp_aes_xxx API calls automatically manage locking & unlocking of + * hardware, this function is only needed if you want to call + * ets_aes_xxx functions directly. + */ +void esp_aes_acquire_hardware( void ); + +/** + * \brief Unlock access to AES hardware unit + * + * esp_aes_xxx API calls automatically manage locking & unlocking of + * hardware, this function is only needed if you want to call + * ets_aes_xxx functions directly. + */ +void esp_aes_release_hardware( void ); + +/** + * \brief Initialize AES context + * + * \param ctx AES context to be initialized + */ +void esp_aes_init( esp_aes_context *ctx ); + +/** + * \brief Clear AES context + * + * \param ctx AES context to be cleared + */ +void esp_aes_free( esp_aes_context *ctx ); + +/** + * \brief This function initializes the specified AES XTS context. + * + * It must be the first API called before using + * the context. + * + * \param ctx The AES XTS context to initialize. + */ +void esp_aes_xts_init( esp_aes_xts_context *ctx ); + +/** + * \brief This function releases and clears the specified AES XTS context. + * + * \param ctx The AES XTS context to clear. + */ +void esp_aes_xts_free( esp_aes_xts_context *ctx ); + +/** + * \brief AES set key schedule (encryption or decryption) + * + * \param ctx AES context to be initialized + * \param key encryption key + * \param keybits must be 128, 192 or 256 + * + * \return 0 if successful, or ERR_AES_INVALID_KEY_LENGTH + */ +int esp_aes_setkey( esp_aes_context *ctx, const unsigned char *key, unsigned int keybits ); + +/** + * \brief AES-ECB block encryption/decryption + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param input 16-byte input block + * \param output 16-byte output block + * + * \return 0 if successful + */ +int esp_aes_crypt_ecb( esp_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16] ); + +/** + * \brief AES-CBC buffer encryption/decryption + * Length should be a multiple of the block + * size (16 bytes) + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful, or ERR_AES_INVALID_INPUT_LENGTH + */ +int esp_aes_crypt_cbc( esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ); + + +/** + * \brief AES-CFB128 buffer encryption/decryption. + * + * Note: Due to the nature of CFB you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * esp_aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv_off offset in IV (updated after use) + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful + */ +int esp_aes_crypt_cfb128( esp_aes_context *ctx, + int mode, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ); + +/** + * \brief AES-CFB8 buffer encryption/decryption. + * + * Note: Due to the nature of CFB you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * esp_aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \note Upon exit, the content of the IV is updated so that you can + * call the function same function again on the following + * block(s) of data and get the same result as if it was + * encrypted in one call. This allows a "streaming" usage. + * If on the other hand you need to retain the contents of the + * IV, you should either save it manually or use the cipher + * module instead. + * + * \param ctx AES context + * \param mode AES_ENCRYPT or AES_DECRYPT + * \param length length of the input data + * \param iv initialization vector (updated after use) + * \param input buffer holding the input data + * \param output buffer holding the output data + * + * \return 0 if successful + */ +int esp_aes_crypt_cfb8( esp_aes_context *ctx, + int mode, + size_t length, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ); + +/** + * \brief AES-CTR buffer encryption/decryption + * + * Warning: You have to keep the maximum use of your counter in mind! + * + * Note: Due to the nature of CTR you should use the same key schedule for + * both encryption and decryption. So a context initialized with + * esp_aes_setkey_enc() for both AES_ENCRYPT and AES_DECRYPT. + * + * \param ctx AES context + * \param length The length of the data + * \param nc_off The offset in the current stream_block (for resuming + * within current cipher stream). The offset pointer to + * should be 0 at the start of a stream. + * \param nonce_counter The 128-bit nonce and counter. + * \param stream_block The saved stream-block for resuming. Is overwritten + * by the function. + * \param input The input data stream + * \param output The output data stream + * + * \return 0 if successful + */ +int esp_aes_crypt_ctr( esp_aes_context *ctx, + size_t length, + size_t *nc_off, + unsigned char nonce_counter[16], + unsigned char stream_block[16], + const unsigned char *input, + unsigned char *output ); + +/** + * \brief This function performs an AES-OFB (Output Feedback Mode) + * encryption or decryption operation. + * + * \param ctx The AES context to use for encryption or decryption. + * It must be initialized and bound to a key. + * \param length The length of the input data. + * \param iv_off The offset in IV (updated after use). + * It must point to a valid \c size_t. + * \param iv The initialization vector (updated after use). + * It must be a readable and writeable buffer of \c 16 Bytes. + * \param input The buffer holding the input data. + * It must be readable and of size \p length Bytes. + * \param output The buffer holding the output data. + * It must be writeable and of size \p length Bytes. + * + * \return \c 0 on success. + */ +int esp_aes_crypt_ofb( esp_aes_context *ctx, + size_t length, + size_t *iv_off, + unsigned char iv[16], + const unsigned char *input, + unsigned char *output ); + +/** + * \brief This function prepares an XTS context for encryption and + * sets the encryption key. + * + * \param ctx The AES XTS context to which the key should be bound. + * \param key The encryption key. This is comprised of the XTS key1 + * concatenated with the XTS key2. + * \param keybits The size of \p key passed in bits. Valid options are: + *
  • 256 bits (each of key1 and key2 is a 128-bit key)
  • + *
  • 512 bits (each of key1 and key2 is a 256-bit key)
+ * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_AES_INVALID_KEY_LENGTH on failure. + */ +int esp_aes_xts_setkey_enc( esp_aes_xts_context *ctx, + const unsigned char *key, + unsigned int keybits ); + +/** + * \brief This function prepares an XTS context for decryption and + * sets the decryption key. + * + * \param ctx The AES XTS context to which the key should be bound. + * \param key The decryption key. This is comprised of the XTS key1 + * concatenated with the XTS key2. + * \param keybits The size of \p key passed in bits. Valid options are: + *
  • 256 bits (each of key1 and key2 is a 128-bit key)
  • + *
  • 512 bits (each of key1 and key2 is a 256-bit key)
+ * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_AES_INVALID_KEY_LENGTH on failure. + */ +int esp_aes_xts_setkey_dec( esp_aes_xts_context *ctx, + const unsigned char *key, + unsigned int keybits ); + + +/** + * \brief Internal AES block encryption function + * (Only exposed to allow overriding it, + * see AES_ENCRYPT_ALT) + * + * \param ctx AES context + * \param input Plaintext block + * \param output Output (ciphertext) block + */ +int esp_internal_aes_encrypt( esp_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); + +/** + * \brief Internal AES block decryption function + * (Only exposed to allow overriding it, + * see AES_DECRYPT_ALT) + * + * \param ctx AES context + * \param input Ciphertext block + * \param output Output (plaintext) block + */ +int esp_internal_aes_decrypt( esp_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); + +/** AES-XTS buffer encryption/decryption */ +int esp_aes_crypt_xts( esp_aes_xts_context *ctx, int mode, size_t length, const unsigned char data_unit[16], const unsigned char *input, unsigned char *output ); + +/** Deprecated, see esp_aes_internal_decrypt */ +void esp_aes_decrypt( esp_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ) __attribute__((deprecated)); + +/** Deprecated, see esp_aes_internal_encrypt */ +void esp_aes_encrypt( esp_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ) __attribute__((deprecated)); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_gcm.h b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_gcm.h new file mode 100644 index 000000000..8efb87f40 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_gcm.h @@ -0,0 +1,293 @@ +/* + * GCM block cipher, ESP DMA hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +#pragma once + +#include "aes/esp_aes.h" +#include "mbedtls/cipher.h" +#include "soc/lldesc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ESP_AES_GCM_STATE_INIT, + ESP_AES_GCM_STATE_START, + ESP_AES_GCM_STATE_UPDATE, + ESP_AES_GCM_STATE_FINISH +} esp_aes_gcm_state; +/** + * \brief The GCM context structure. + */ +typedef struct { + uint8_t H[16]; /*!< Initial hash value */ + uint8_t ghash[16]; /*!< GHASH value. */ + uint8_t J0[16]; + uint64_t HL[16]; /*!< Precalculated HTable low. */ + uint64_t HH[16]; /*!< Precalculated HTable high. */ + uint8_t ori_j0[16]; /*!< J0 from first iteration. */ + const uint8_t *iv; + size_t iv_len; /*!< The length of IV. */ + uint64_t aad_len; /*!< The total length of the additional data. */ + size_t data_len; + int mode; + const unsigned char *aad; /*!< The additional data. */ + esp_aes_context aes_ctx; + esp_aes_gcm_state gcm_state; + /* Software context needed for soft fallback for non-AES ciphers */ + void *ctx_soft; +} esp_gcm_context; + + +/** + * \brief This function initializes the specified GCM context + * + * \param ctx The GCM context to initialize. + */ +void esp_aes_gcm_init( esp_gcm_context *ctx); + +/** + * \brief This function associates a GCM context with a + * key. + * + * \param ctx The GCM context to initialize. + * \param cipher The 128-bit block cipher to use. + * \param key The encryption key. + * \param keybits The key size in bits. Valid options are: + *
  • 128 bits
  • + *
  • 192 bits
  • + *
  • 256 bits
+ * + * \return \c 0 on success. + * \return A cipher-specific error code on failure. + */ +int esp_aes_gcm_setkey( esp_gcm_context *ctx, + mbedtls_cipher_id_t cipher, + const unsigned char *key, + unsigned int keybits ); + +/** + * \brief This function starts a GCM encryption or decryption + * operation. + * + * \param ctx The GCM context. This must be initialized. + * \param mode The operation to perform: #MBEDTLS_GCM_ENCRYPT or + * #MBEDTLS_GCM_DECRYPT. + * \param iv The initialization vector. This must be a readable buffer of + * at least \p iv_len Bytes. + * \param iv_len The length of the IV. + * + * \return \c 0 on success. + */ +int esp_aes_gcm_starts( esp_gcm_context *ctx, + int mode, + const unsigned char *iv, + size_t iv_len ); + +/** + * \brief This function feeds an input buffer as associated data + * (authenticated but not encrypted data) in a GCM + * encryption or decryption operation. + * + * Call this function after mbedtls_gcm_starts() to pass + * the associated data. If the associated data is empty, + * you do not need to call this function. You may not + * call this function after calling mbedtls_cipher_update(). + * + * \param ctx The GCM context. This must have been started with + * mbedtls_gcm_starts() and must not have yet received + * any input with mbedtls_gcm_update(). + * \param aad The buffer holding the additional data, or \c NULL + * if \p aad_len is \c 0. + * \param aad_len The length of the additional data. If \c 0, + * \p add may be \c NULL. + * + * \return \c 0 on success. + */ +int esp_aes_gcm_update_ad( esp_gcm_context *ctx, + const unsigned char *aad, + size_t aad_len ); + +/** + * \brief This function feeds an input buffer into an ongoing GCM + * encryption or decryption operation. + * + * You may call this function zero, one or more times + * to pass successive parts of the input: the plaintext to + * encrypt, or the ciphertext (not including the tag) to + * decrypt. After the last part of the input, call + * mbedtls_gcm_finish(). + * + * This function may produce output in one of the following + * ways: + * - Immediate output: the output length is always equal + * to the input length. + * - Buffered output: the output consists of a whole number + * of 16-byte blocks. If the total input length so far + * (not including associated data) is 16 \* *B* + *A* + * with *A* < 16 then the total output length is 16 \* *B*. + * + * In particular: + * - It is always correct to call this function with + * \p output_size >= \p input_length + 15. + * - If \p input_length is a multiple of 16 for all the calls + * to this function during an operation, then it is + * correct to use \p output_size = \p input_length. + * + * \note For decryption, the output buffer cannot be the same as + * input buffer. If the buffers overlap, the output buffer + * must trail at least 8 Bytes behind the input buffer. + * + * \param ctx The GCM context. This must be initialized. + * \param input The buffer holding the input data. If \p input_length + * is greater than zero, this must be a readable buffer + * of at least \p input_length bytes. + * \param input_length The length of the input data in bytes. + * \param output The buffer for the output data. If \p output_size + * is greater than zero, this must be a writable buffer of + * of at least \p output_size bytes. + * \param output_size The size of the output buffer in bytes. + * See the function description regarding the output size. + * \param output_length On success, \p *output_length contains the actual + * length of the output written in \p output. + * On failure, the content of \p *output_length is + * unspecified. + * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_GCM_BAD_INPUT on failure: + * total input length too long, + * unsupported input/output buffer overlap detected, + * or \p output_size too small. + */ +int esp_aes_gcm_update( esp_gcm_context *ctx, + const unsigned char *input, size_t input_length, + unsigned char *output, size_t output_size, + size_t *output_length ); + +/** + * \brief This function finishes the GCM operation and generates + * the authentication tag. + * + * It wraps up the GCM stream, and generates the + * tag. The tag can have a maximum length of 16 Bytes. + * + * \param ctx The GCM context. This must be initialized. + * \param tag The buffer for holding the tag. This must be a writable + * buffer of at least \p tag_len Bytes. + * \param tag_len The length of the tag to generate. This must be at least + * four. + * \param output The buffer for the final output. + * If \p output_size is nonzero, this must be a writable + * buffer of at least \p output_size bytes. + * \param output_size The size of the \p output buffer in bytes. + * This must be large enough for the output that + * mbedtls_gcm_update() has not produced. In particular: + * - If mbedtls_gcm_update() produces immediate output, + * or if the total input size is a multiple of \c 16, + * then mbedtls_gcm_finish() never produces any output, + * so \p output_size can be \c 0. + * - \p output_size never needs to be more than \c 15. + * \param output_length On success, \p *output_length contains the actual + * length of the output written in \p output. + * On failure, the content of \p *output_length is + * unspecified. + * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_GCM_BAD_INPUT on failure: + * invalid value of \p tag_len, + * or \p output_size too small. + */ +int esp_aes_gcm_finish( esp_gcm_context *ctx, + unsigned char *output, size_t output_size, + size_t *output_length, + unsigned char *tag, size_t tag_len ); + +/** + * \brief This function clears a GCM context + * + * \param ctx The GCM context to clear. + */ +void esp_aes_gcm_free( esp_gcm_context *ctx); + +/** + * \brief This function performs GCM encryption or decryption of a buffer. + * + * \note For encryption, the output buffer can be the same as the + * input buffer. For decryption, the output buffer cannot be + * the same as input buffer. If the buffers overlap, the output + * buffer must trail at least 8 Bytes behind the input buffer. + * + * \param ctx The GCM context to use for encryption or decryption. + * \param mode The operation to perform: #MBEDTLS_GCM_ENCRYPT or + * #MBEDTLS_GCM_DECRYPT. + * \param length The length of the input data. This must be a multiple of + * 16 except in the last call before mbedtls_gcm_finish(). + * \param iv The initialization vector. + * \param iv_len The length of the IV. + * \param aad The buffer holding the additional data. + * \param aad_len The length of the additional data. + * \param input The buffer holding the input data. + * \param output The buffer for holding the output data. + * \param tag_len The length of the tag to generate. + * \param tag The buffer for holding the tag. + * + * \return \c 0 on success. + */ +int esp_aes_gcm_crypt_and_tag( esp_gcm_context *ctx, + int mode, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *aad, + size_t aad_len, + const unsigned char *input, + unsigned char *output, + size_t tag_len, + unsigned char *tag ); + + +/** + * \brief This function performs a GCM authenticated decryption of a + * buffer. + * + * \note For decryption, the output buffer cannot be the same as + * input buffer. If the buffers overlap, the output buffer + * must trail at least 8 Bytes behind the input buffer. + * + * \param ctx The GCM context. + * \param length The length of the input data. This must be a multiple + * of 16 except in the last call before mbedtls_gcm_finish(). + * \param iv The initialization vector. + * \param iv_len The length of the IV. + * \param aad The buffer holding the additional data. + * \param aad_len The length of the additional data. + * \param tag The buffer holding the tag. + * \param tag_len The length of the tag. + * \param input The buffer holding the input data. + * \param output The buffer for holding the output data. + * + * \return 0 if successful and authenticated. + * \return #MBEDTLS_ERR_GCM_AUTH_FAILED if the tag does not match. + */ +int esp_aes_gcm_auth_decrypt( esp_gcm_context *ctx, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *aad, + size_t aad_len, + const unsigned char *tag, + size_t tag_len, + const unsigned char *input, + unsigned char *output ); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_internal.h b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_internal.h new file mode 100644 index 000000000..35a7d3935 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/aes/esp_aes_internal.h @@ -0,0 +1,54 @@ +/** + * \brief AES block cipher, ESP-IDF hardware accelerated version + * Based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Internal API + */ + +#pragma once + + +#include "aes/esp_aes.h" +#include "aes/esp_aes_gcm.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool valid_key_length(const esp_aes_context *ctx); + + +/** + * @brief Run a AES-GCM conversion using DMA + * + * @param ctx Aes context + * @param input Pointer to input data + * @param output Pointer to output data + * @param len Length of the input data + * @param aad_desc GCM additional data DMA descriptor + * @param aad_len GCM additional data length + * @return int -1 on error + */ +int esp_aes_process_dma_gcm(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, lldesc_t *aad_desc, size_t aad_len); + + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/aes_alt.h b/components/mbedtls/mbedtls_v3/port/include/aes_alt.h new file mode 100644 index 000000000..2f8e958b2 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/aes_alt.h @@ -0,0 +1,69 @@ +/** + * \file aes_alt.h + * + * \brief AES block cipher + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + */ +#ifndef AES_ALT_H +#define AES_ALT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MBEDTLS_AES_ALT) +#include "aes/esp_aes.h" + +typedef esp_aes_context mbedtls_aes_context; + +#define mbedtls_aes_init esp_aes_init +#define mbedtls_aes_free esp_aes_free +#define mbedtls_aes_setkey_enc esp_aes_setkey +#define mbedtls_aes_setkey_dec esp_aes_setkey +#define mbedtls_aes_crypt_ecb esp_aes_crypt_ecb +#if defined(MBEDTLS_CIPHER_MODE_CBC) +#define mbedtls_aes_crypt_cbc esp_aes_crypt_cbc +#endif +#if defined(MBEDTLS_CIPHER_MODE_CFB) +#define mbedtls_aes_crypt_cfb128 esp_aes_crypt_cfb128 +#define mbedtls_aes_crypt_cfb8 esp_aes_crypt_cfb8 +#endif +#if defined(MBEDTLS_CIPHER_MODE_CTR) +#define mbedtls_aes_crypt_ctr esp_aes_crypt_ctr +#endif +#if defined(MBEDTLS_CIPHER_MODE_OFB) +#define mbedtls_aes_crypt_ofb esp_aes_crypt_ofb +#endif +#if defined(MBEDTLS_CIPHER_MODE_XTS) +typedef esp_aes_xts_context mbedtls_aes_xts_context; +#define mbedtls_aes_xts_init esp_aes_xts_init +#define mbedtls_aes_xts_free esp_aes_xts_free +#define mbedtls_aes_xts_setkey_enc esp_aes_xts_setkey_enc +#define mbedtls_aes_xts_setkey_dec esp_aes_xts_setkey_dec +#define mbedtls_aes_crypt_xts esp_aes_crypt_xts +#endif +#define mbedtls_internal_aes_encrypt esp_internal_aes_encrypt +#define mbedtls_internal_aes_decrypt esp_internal_aes_decrypt +#endif /* MBEDTLS_AES_ALT */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/bignum_impl.h b/components/mbedtls/mbedtls_v3/port/include/bignum_impl.h new file mode 100644 index 000000000..cb208ecb5 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/bignum_impl.h @@ -0,0 +1,96 @@ +#ifndef _ESP_BIGNUM_H_ +#define _ESP_BIGNUM_H_ + +#include +#include + +/* Use montgomery exponentiation (HAC 14.94) for calculating X ^ Y mod M, + this may be faster for some targets. The hardware acceleration support for modular + exponentiation on the ESP32 is slow for public key operations, so use montgomery + exponentiation instead. +*/ +#if CONFIG_IDF_TARGET_ESP32 +#define ESP_MPI_USE_MONT_EXP +#endif + +/** + * @brief Enable the MPI hardware and acquire the lock + * + */ +void esp_mpi_enable_hardware_hw_op( void ); + +/** + * @brief Disable the MPI hardware and release the lock + * + */ +void esp_mpi_disable_hardware_hw_op( void ); + +/** + * @brief Calculate the number of words needed to represent the input word in hardware + * + * @param words The number of words to be represented + * + * @return size_t Number of words required + */ +size_t esp_mpi_hardware_words(size_t words); + +/** + * @brief Starts a (X * Y) Mod M calculation in hardware. Rinv and M_prime needs to be precalculated in software. + * + */ +void esp_mpi_mul_mpi_mod_hw_op(const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, const mbedtls_mpi *Rinv, mbedtls_mpi_uint Mprime, size_t hw_words); + +/** + * @brief Starts a (X * Y) calculation in hardware. + * + */ +void esp_mpi_mul_mpi_hw_op(const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t num_words); + +/** + * @brief Special-case of (X * Y), where we use hardware montgomery mod + multiplication to calculate result where either A or B are >2048 bits so + can't use the standard multiplication method. + * + */ +void esp_mpi_mult_mpi_failover_mod_mult_hw_op(const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t num_words); + +/** + * @brief Read out the result from the previous calculation. + * + */ +void esp_mpi_read_result_hw_op(mbedtls_mpi *Z, size_t z_words); + +#ifdef ESP_MPI_USE_MONT_EXP +/** + * @brief Starts a montgomery multiplication calculation in hardware + * + */ +int esp_mont_hw_op(mbedtls_mpi* Z, const mbedtls_mpi* X, const mbedtls_mpi* Y, const mbedtls_mpi* M, + mbedtls_mpi_uint Mprime, + size_t hw_words, + bool again); + +#else + +/** + * @brief Starts a (X ^ Y) Mod M calculation in hardware. Rinv and M_prime needs to be precalculated in software. + * + */ +void esp_mpi_exp_mpi_mod_hw_op(const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, const mbedtls_mpi *Rinv, mbedtls_mpi_uint Mprime, size_t hw_words); + +#endif //ESP_MPI_USE_MONT_EXP + +/** + * @brief Enable/disables MPI operation complete interrupt + * + * @param enable true: enable, false: disable + */ +void esp_mpi_interrupt_enable( bool enable ); + +/** + * @brief Clears the MPI operation complete interrupt status + * + */ +void esp_mpi_interrupt_clear( void ); + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/ecc_impl.h b/components/mbedtls/mbedtls_v3/port/include/ecc_impl.h new file mode 100644 index 000000000..c52c98819 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/ecc_impl.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define P256_LEN (256/8) +#define P192_LEN (192/8) + +/* Note: x & y are stored in little endian order (same as CPU byte order, and order used internally by most libraries). + + This is the same order used in hardware + + Note this is opposite to most byte string formats used to represent keys, which are often big endian +*/ +typedef struct { + uint8_t x[P256_LEN]; /* Little endian order */ + uint8_t y[P256_LEN]; /* Little endian order */ + unsigned len; /* P192_LEN or P256_LEN */ +} ecc_point_t; + +/** + * @brief Perform ECC point multiplication (R = K * (Px, Py)) + * + * @param point ECC point (multiplicand) + * @param scalar Integer represented in byte array format (multiplier) + * @param result Result of the multiplication + * @param verify_first Verify that the point is on the curve before performing multiplication + * + * @return - 0 if the multiplication was successful + * - -1 otherwise + * + * @note 'scalar' is expected as a byte array in little endian order. + * Most byte string formats used to represent keys are in big endian order. + */ +int esp_ecc_point_multiply(const ecc_point_t *point, const uint8_t *scalar, ecc_point_t *result, bool verify_first); + +/** + * @brief Perform ECC point verification, + * i.e check whether the point (Px, Py) lies on the curve + * + * @param point ECC point that needs to be verified + * + * @return - 1, if point lies on the curve + * - 0, otherwise + * + */ +int esp_ecc_point_verify(const ecc_point_t *point); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/ecdsa/ecdsa_alt.h b/components/mbedtls/mbedtls_v3/port/include/ecdsa/ecdsa_alt.h new file mode 100644 index 000000000..f4b7af6a0 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/ecdsa/ecdsa_alt.h @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" +#include "mbedtls/pk.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN || __DOXYGEN__ + +/** + * @brief Initialize MPI to notify mbedtls_ecdsa_sign to use the private key in efuse + * We break the MPI struct of the private key in order to + * differentiate between hardware key and software key + * + * @param key The MPI in which this functions stores the hardware context. + * This must be uninitialized + * @param efuse_blk The efuse key block that should be used as the private key. + * The key purpose of this block must be ECDSA_KEY + * + * @return - 0 if successful + * - -1 otherwise + * + */ +int esp_ecdsa_privkey_load_mpi(mbedtls_mpi *key, int efuse_blk); + +/** + * @brief Initialize PK context to notify mbedtls_ecdsa_sign to use the private key in efuse + * We break the MPI struct used to represent the private key `d` in ECP keypair + * in order to differentiate between hardware key and software key + * + * @param key_ctx The context in which this functions stores the hardware context. + * This must be uninitialized + * @param efuse_blk The efuse key block that should be used as the private key. + * The key purpose of this block must be ECDSA_KEY + * + * @return - 0 if successful + * - -1 otherwise + */ +int esp_ecdsa_privkey_load_pk_context(mbedtls_pk_context *key_ctx, int efuse_blk); + +#endif // CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN || __DOXYGEN__ + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/entropy_poll.h b/components/mbedtls/mbedtls_v3/port/include/entropy_poll.h new file mode 100644 index 000000000..4bae4d1db --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/entropy_poll.h @@ -0,0 +1,28 @@ +/** + * Mbedtls entropy_poll.h file + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef MBEDTLS_ENTROPY_POLL_H +#define MBEDTLS_ENTROPY_POLL_H +#include "mbedtls/build_info.h" +#include +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Entropy poll callback for a hardware source + * + * + * \note This must accept NULL as its first argument. + */ +int mbedtls_hardware_poll( void *data, + unsigned char *output, size_t len, size_t *olen ); + +#ifdef __cplusplus +} +#endif + +#endif /* entropy_poll.h */ diff --git a/components/mbedtls/mbedtls_v3/port/include/esp_crypto_shared_gdma.h b/components/mbedtls/mbedtls_v3/port/include/esp_crypto_shared_gdma.h new file mode 100644 index 000000000..4e69a5f8e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/esp_crypto_shared_gdma.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/lldesc.h" +#include "esp_private/gdma.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Start a GDMA transfer on the shared crypto DMA channel + * + * @note Will allocate a GDMA channel for AES & SHA if no such channel is already allocated + * + * @param input Input linked list descriptor + * @param output Output linked list descriptor + * @param peripheral Crypto peripheral to connect the DMA to, either GDMA_TRIG_PERIPH_AES or + * GDMA_TRIG_PERIPH_SHA + * @return esp_err_t ESP_FAIL if no GDMA channel available + */ +esp_err_t esp_crypto_shared_gdma_start(const lldesc_t *input, const lldesc_t *output, gdma_trigger_peripheral_t peripheral); + + +/** + * @brief Frees any shared crypto DMA channel, if esp_crypto_shared_gdma_start is called after + * this, new GDMA channels will be allocated. + * + * @note Function is meant to be called from user code, and thus takes AES/SHA lock. + * This means this function should not be called from code which already takes these locks, + * i.e. inside our AES/SHA code. + * + * If you are continously using AES/SHA (e.g. because of a wifi connection) then it's not recommended + * to use this API. Freeing the channel is mainly for use cases where you are finished with the crypto peripherals + * and need the DMA channel for other peripherals. An example would be doing some processing after disconnecting WiFi + */ +void esp_crypto_shared_gdma_free(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/esp_ds/esp_rsa_sign_alt.h b/components/mbedtls/mbedtls_v3/port/include/esp_ds/esp_rsa_sign_alt.h new file mode 100644 index 000000000..7c00ced54 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/esp_ds/esp_rsa_sign_alt.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifndef _ESP_RSA_SIGN_ALT_H_ +#define _ESP_RSA_SIGN_ALT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_ds.h" +#include "mbedtls/md.h" + +/** + * @brief ESP-DS data context + * + * @note This structure includes encrypted private key parameters such as ciphertext_c, initialization vector, efuse_key_id, RSA key length, which are obtained when DS peripheral is configured. + */ + +/* Context for encrypted private key data required for DS */ +typedef struct esp_ds_data_ctx { + esp_ds_data_t *esp_ds_data; + uint8_t efuse_key_id; /* efuse block id in which DS_KEY is stored e.g. 0,1*/ + uint16_t rsa_length_bits; /* length of RSA private key in bits e.g. 2048 */ +} esp_ds_data_ctx_t; + +/** + * @brief Initializes internal DS data context + * + * This function allocates and initializes internal ds data context which is used for Digital Signature operation. + * + * @in ds_data ds_data context containing encrypted private key parameters + * @return + * - ESP_OK In case of succees + * - ESP_ERR_NO_MEM In case internal context could not be allocated. + * - ESP_ERR_INVALID_ARG in case input parametrers are NULL + * + */ +esp_err_t esp_ds_init_data_ctx(esp_ds_data_ctx_t *ds_data); + +/** + * + * @brief Release the ds lock acquired for the DS operation (then the DS peripheral can be used for other TLS connection) + * + */ +void esp_ds_release_ds_lock(void); + +/** + * + * @brief Alternate implementation for mbedtls_rsa_rsassa_pkcs1_v15_sign, Internally makes use + * of DS module to perform hardware accelerated RSA sign operation + */ +int esp_ds_rsa_sign( void *ctx, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + mbedtls_md_type_t md_alg, unsigned int hashlen, + const unsigned char *hash, unsigned char *sig ); + +/* + * @brief Get RSA key length in bytes from internal DS context + * + * @return RSA key length in bytes + */ +size_t esp_ds_get_keylen(void *ctx); + +/* + * @brief Set timeout (equal to TLS session timeout), so that DS module usage can be synchronized in case of multiple TLS connections using DS module, + */ +void esp_ds_set_session_timeout(int timeout); +#ifdef __cplusplus +} +#endif + +#endif /* _ESP_RSA_SIGN_ALT_H_ */ diff --git a/components/mbedtls/mbedtls_v3/port/include/esp_mem.h b/components/mbedtls/mbedtls_v3/port/include/esp_mem.h new file mode 100644 index 000000000..c88b13287 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/esp_mem.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +void *esp_mbedtls_mem_calloc(size_t n, size_t size); +void esp_mbedtls_mem_free(void *ptr); diff --git a/components/mbedtls/mbedtls_v3/port/include/gcm_alt.h b/components/mbedtls/mbedtls_v3/port/include/gcm_alt.h new file mode 100644 index 000000000..f76970944 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/gcm_alt.h @@ -0,0 +1,43 @@ +/* + * gcm_alt.h: AES block cipher + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +#ifndef GCM_ALT_H +#define GCM_ALT_H + +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MBEDTLS_GCM_ALT) + + +#include "aes/esp_aes_gcm.h" + + +typedef esp_gcm_context mbedtls_gcm_context; + +#define mbedtls_gcm_init esp_aes_gcm_init +#define mbedtls_gcm_free esp_aes_gcm_free +#define mbedtls_gcm_setkey esp_aes_gcm_setkey +#define mbedtls_gcm_starts esp_aes_gcm_starts +#define mbedtls_gcm_update_ad esp_aes_gcm_update_ad +#define mbedtls_gcm_update esp_aes_gcm_update +#define mbedtls_gcm_finish esp_aes_gcm_finish +#define mbedtls_gcm_auth_decrypt esp_aes_gcm_auth_decrypt +#define mbedtls_gcm_crypt_and_tag esp_aes_gcm_crypt_and_tag + +#endif /* MBEDTLS_GCM_ALT */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/bignum.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/bignum.h new file mode 100644 index 000000000..4f84bed74 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/bignum.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include_next "mbedtls/bignum.h" +#include "sdkconfig.h" + +/** + * This is a wrapper for the main mbedtls/bignum.h. This wrapper + * provides a few additional ESP32-only functions. + * + * This is because we don't set MBEDTLS_BIGNUM_ALT in the same way we + * do for AES, SHA, etc. Because we still use most of the bignum.h + * implementation and just replace a few hardware accelerated + * functions (see MBEDTLS_MPI_EXP_MOD_ALT & MBEDTLS_MPI_MUL_MPI_ALT in + * esp_config.h). + * + * @note Unlike the other hardware accelerator support functions in esp32/hwcrypto, there is no + * generic "hwcrypto/bignum.h" header for using these functions without mbedTLS. The reason for this + * is that all of the function implementations depend strongly upon the mbedTLS MPI implementation. + */ + +/** + * @brief Lock access to RSA Accelerator (MPI/bignum operations) + * + * RSA Accelerator hardware unit can only be used by one + * consumer at a time. + * + * @note This function is non-recursive (do not call it twice from the + * same task.) + * + * @note You do not need to call this if you are using the mbedTLS bignum.h + * API or esp_mpi_xxx functions. This function is only needed if you + * want to call ROM RSA functions or access the registers directly. + * + */ +void esp_mpi_acquire_hardware(void); + +/** + * @brief Unlock access to RSA Accelerator (MPI/bignum operations) + * + * Has to be called once for each call to esp_mpi_acquire_hardware(). + * + * @note You do not need to call this if you are using the mbedTLS bignum.h + * API or esp_mpi_xxx functions. This function is only needed if you + * want to call ROM RSA functions or access the registers directly. + */ +void esp_mpi_release_hardware(void); + +#if CONFIG_MBEDTLS_HARDWARE_MPI + +/* @brief MPI modular mupltiplication function + * + * Calculates Z = (X * Y) mod M using MPI hardware acceleration. + * + * This is not part of the standard mbedTLS bignum API. + * + * @note All of X, Y & Z should be less than 4096 bit long or an error is returned. + * + * @param Z Result bignum, should be pre-initialised with mbedtls_mpi_init(). + * @param X First multiplication argument. + * @param Y Second multiplication argument. + * @param M Modulus value for result. + * + * @return 0 on success, mbedTLS MPI error codes on failure. + */ +int esp_mpi_mul_mpi_mod(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M); + +#if CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI + +/** + * @brief Perform a sliding-window exponentiation: X = A^E mod N + * + * @param X The destination MPI. This must point to an initialized MPI. + * @param A The base of the exponentiation. + * This must point to an initialized MPI. + * @param E The exponent MPI. This must point to an initialized MPI. + * @param N The base for the modular reduction. This must point to an + * initialized MPI. + * @param _RR A helper MPI depending solely on \p N which can be used to + * speed-up multiple modular exponentiations for the same value + * of \p N. This may be \c NULL. If it is not \c NULL, it must + * point to an initialized MPI. + * + * @return \c 0 if successful. + * @return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * @return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if \c N is negative or + * even, or if \c E is negative. + * @return Another negative error code on different kinds of failures. + * + */ +int mbedtls_mpi_exp_mod_soft(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *E, const mbedtls_mpi *N, mbedtls_mpi *_RR); + +#endif // CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI + +#endif // CONFIG_MBEDTLS_HARDWARE_MPI diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/certs.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/certs.h new file mode 100644 index 000000000..be1ec9320 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/certs.h @@ -0,0 +1,28 @@ +/** + * \file certs.h + * + * \brief Sample certificates and DHM parameters for testing + */ +/* + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ +#ifndef MBEDTLS_CERTS_H +#define MBEDTLS_CERTS_H +// Only for compilation + +#endif /* certs.h */ diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/ecp.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/ecp.h new file mode 100644 index 000000000..28ccd5c79 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/ecp.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include_next "mbedtls/ecp.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MBEDTLS_ECP_MUL_ALT) || defined(MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK) +int ecp_mul_restartable_internal( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, + const mbedtls_mpi *m, const mbedtls_ecp_point *P, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + mbedtls_ecp_restart_ctx *rs_ctx ); +#endif + +#if defined(MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK) +int ecp_mul_restartable_internal_soft( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, + const mbedtls_mpi *m, const mbedtls_ecp_point *P, + int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, + mbedtls_ecp_restart_ctx *rs_ctx ); +#endif + +#if defined(MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK) + +int mbedtls_ecp_check_pubkey_soft(const mbedtls_ecp_group *grp, + const mbedtls_ecp_point *pt ); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h new file mode 100644 index 000000000..579b6a37f --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h @@ -0,0 +1,3030 @@ +/** + * + * \brief Default mbedTLS configuration options for ESP-IDF + * + * This set of compile-time options may be used to enable + * or disable features selectively, and reduce the global + * memory footprint. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * This set of compile-time options may be used to enable + * or disable features selectively, and reduce the global + * memory footprint. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ESP_CONFIG_H +#define ESP_CONFIG_H + +#include "sdkconfig.h" +#include "mbedtls/mbedtls_config.h" +// #include "soc/soc_caps.h" + +/** + * \name SECTION: System support + * + * This section sets system specific settings. + * \{ + */ + +/** + * \def MBEDTLS_HAVE_TIME + * + * System has time.h and time(). + * The time does not need to be correct, only time differences are used, + * by contrast with MBEDTLS_HAVE_TIME_DATE + * + * Defining MBEDTLS_HAVE_TIME allows you to specify MBEDTLS_PLATFORM_TIME_ALT, + * MBEDTLS_PLATFORM_TIME_MACRO, MBEDTLS_PLATFORM_TIME_TYPE_MACRO and + * MBEDTLS_PLATFORM_STD_TIME. + * + * Comment if your system does not support time functions. + * + * \note If MBEDTLS_TIMING_C is set - to enable the semi-portable timing + * interface - timing.c will include time.h on suitable platforms + * regardless of the setting of MBEDTLS_HAVE_TIME, unless + * MBEDTLS_TIMING_ALT is used. See timing.c for more information. + */ +#ifdef CONFIG_MBEDTLS_HAVE_TIME +#define MBEDTLS_HAVE_TIME +/** + * \def MBEDTLS_PLATFORM_MS_TIME_ALT + * + * Define platform specific function to get time since bootup in milliseconds. + */ +#define MBEDTLS_PLATFORM_MS_TIME_ALT +#else +#undef MBEDTLS_HAVE_TIME +#undef MBEDTLS_PLATFORM_MS_TIME_ALT +#endif + +/** + * \def MBEDTLS_HAVE_TIME_DATE + * + * System has time.h and time(), gmtime() and the clock is correct. + * The time needs to be correct (not necessarily very accurate, but at least + * the date should be correct). This is used to verify the validity period of + * X.509 certificates. + * + * Comment if your system does not have a correct clock. + */ +#ifdef CONFIG_MBEDTLS_HAVE_TIME_DATE +#define MBEDTLS_HAVE_TIME_DATE +#else +#undef MBEDTLS_HAVE_TIME_DATE +#endif + + +/** + * \def MBEDTLS_PLATFORM_TIME_ALT + * + * mbed TLS will provide a function "mbedtls_platform_set_time()" + * that allows you to set an alternative time function pointer. + * + * All these define require MBEDTLS_PLATFORM_C to be defined! + * + * \warning MBEDTLS_PLATFORM_TIME_ALT cannot be defined at the same time as + * MBEDTLS_PLATFORM_TIME_MACRO! + * + * Requires: MBEDTLS_PLATFORM_TIME_ALT requires MBEDTLS_HAVE_TIME + */ +#ifdef CONFIG_MBEDTLS_PLATFORM_TIME_ALT +#define MBEDTLS_PLATFORM_TIME_ALT +#else +#undef MBEDTLS_PLATFORM_TIME_ALT +#endif + +/** + * \def MBEDTLS_PLATFORM_MEMORY + * + * Enable the memory allocation layer. + * + * By default mbed TLS uses the system-provided calloc() and free(). + * This allows different allocators (self-implemented or provided) to be + * provided to the platform abstraction layer. + * + * Enabling MBEDTLS_PLATFORM_MEMORY without the + * MBEDTLS_PLATFORM_{FREE,CALLOC}_MACROs will provide + * "mbedtls_platform_set_calloc_free()" allowing you to set an alternative calloc() and + * free() function pointer at runtime. + * + * Enabling MBEDTLS_PLATFORM_MEMORY and specifying + * MBEDTLS_PLATFORM_{CALLOC,FREE}_MACROs will allow you to specify the + * alternate function at compile time. + * + * Requires: MBEDTLS_PLATFORM_C + * + * Enable this layer to allow use of alternative memory allocators. + */ +#define MBEDTLS_PLATFORM_MEMORY + +/** Override calloc(), free() except for case where memory allocation scheme is not set to custom */ +#ifndef CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC +#include "esp_mem.h" +#define MBEDTLS_PLATFORM_STD_CALLOC esp_mbedtls_mem_calloc +#define MBEDTLS_PLATFORM_STD_FREE esp_mbedtls_mem_free +#endif + +/* \} name SECTION: System support */ + +/** + * \name SECTION: mbed TLS feature support + * + * This section sets support for features that are or are not needed + * within the modules that are enabled. + * \{ + */ + +/* The following units have ESP32 hardware support, + uncommenting each _ALT macro will use the + hardware-accelerated implementation. */ +#ifdef CONFIG_MBEDTLS_HARDWARE_AES +#define MBEDTLS_AES_ALT +#else +#undef MBEDTLS_AES_ALT +#endif + +#ifdef CONFIG_MBEDTLS_HARDWARE_AES +#define MBEDTLS_GCM_ALT +#ifdef CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER + /* Prefer hardware and fallback to software */ + #define MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK +#else + #undef MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK +#endif +#endif + +/* MBEDTLS_SHAxx_ALT to enable hardware SHA support + with software fallback. +*/ +#ifdef CONFIG_MBEDTLS_HARDWARE_SHA +#define MBEDTLS_SHA1_ALT +#define MBEDTLS_SHA256_ALT + +#if SOC_SHA_SUPPORT_SHA512 +#define MBEDTLS_SHA512_ALT +#else +#undef MBEDTLS_SHA512_ALT +#endif + +#else +#undef MBEDTLS_SHA1_ALT +#undef MBEDTLS_SHA256_ALT +#undef MBEDTLS_SHA512_ALT +#endif + +/* MBEDTLS_MDx_ALT to enable ROM MD support + with software fallback. +*/ +#ifdef CONFIG_MBEDTLS_ROM_MD5 +#define MBEDTLS_MD5_ALT +#else +#undef MBEDTLS_MD5_ALT +#endif + +/* The following MPI (bignum) functions have hardware support. + * Uncommenting these macros will use the hardware-accelerated + * implementations. + */ +#ifdef CONFIG_MBEDTLS_HARDWARE_MPI +#ifdef CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI + /* Prefer hardware and fallback to software */ + #define MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK +#else + /* Hardware only mode */ + #define MBEDTLS_MPI_EXP_MOD_ALT +#endif +#define MBEDTLS_MPI_MUL_MPI_ALT +#else +#undef MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK +#undef MBEDTLS_MPI_EXP_MOD_ALT +#undef MBEDTLS_MPI_MUL_MPI_ALT +#endif + +#ifdef CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN +#define MBEDTLS_ECDSA_SIGN_ALT +#endif + +#ifdef CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY +#define MBEDTLS_ECDSA_VERIFY_ALT +#endif + +#ifdef CONFIG_MBEDTLS_HARDWARE_ECC +#ifdef CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK + /* Use hardware accelerator for SECP192R1 and SECP256R1 curves, + * software implementation for rest of the curves + */ + #define MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK + #define MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK +#else + /* Only hardware accelerator support */ + #define MBEDTLS_ECP_MUL_ALT + #define MBEDTLS_ECP_VERIFY_ALT +#endif + +#else +#undef MBEDTLS_ECP_MUL_ALT +#undef MBEDTLS_ECP_MUL_ALT_SOFT_FALLBACK +#undef MBEDTLS_ECP_VERIFY_ALT +#undef MBEDTLS_ECP_VERIFY_ALT_SOFT_FALLBACK +#endif + +/** + * \def MBEDTLS_ENTROPY_HARDWARE_ALT + * + * Uncomment this macro to let mbed TLS use your own implementation of a + * hardware entropy collector. + * + * Your function must be called \c mbedtls_hardware_poll(), have the same + * prototype as declared in entropy_poll.h, and accept NULL as first argument. + * + * Uncomment to use your own hardware entropy collector. + */ +#define MBEDTLS_ENTROPY_HARDWARE_ALT + +/** + * \def MBEDTLS_AES_ROM_TABLES + * + * Store the AES tables in ROM. + * + * Uncomment this macro to store the AES tables in ROM. + */ +#define MBEDTLS_AES_ROM_TABLES + +/** + * \def MBEDTLS_CIPHER_MODE_CBC + * + * Enable Cipher Block Chaining mode (CBC) for symmetric ciphers. + */ +#define MBEDTLS_CIPHER_MODE_CBC + +/** + * \def MBEDTLS_CIPHER_MODE_CFB + * + * Enable Cipher Feedback mode (CFB) for symmetric ciphers. + */ +#define MBEDTLS_CIPHER_MODE_CFB + +/** + * \def MBEDTLS_CIPHER_MODE_CTR + * + * Enable Counter Block Cipher mode (CTR) for symmetric ciphers. + */ +#define MBEDTLS_CIPHER_MODE_CTR + +/** + * \def MBEDTLS_CIPHER_MODE_OFB + * + * Enable Output Feedback mode (OFB) for symmetric ciphers. + */ +#define MBEDTLS_CIPHER_MODE_OFB + +/** + * \def MBEDTLS_CIPHER_MODE_XTS + * + * Enable Xor-encrypt-xor with ciphertext stealing mode (XTS) for AES. + */ +#define MBEDTLS_CIPHER_MODE_XTS + +/** + * \def MBEDTLS_CIPHER_PADDING_PKCS7 + * + * MBEDTLS_CIPHER_PADDING_XXX: Uncomment or comment macros to add support for + * specific padding modes in the cipher layer with cipher modes that support + * padding (e.g. CBC) + * + * If you disable all padding modes, only full blocks can be used with CBC. + * + * Enable padding modes in the cipher layer. + */ +#define MBEDTLS_CIPHER_PADDING_PKCS7 +#define MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS +#define MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN +#define MBEDTLS_CIPHER_PADDING_ZEROS + +/** + * \def MBEDTLS_ECP_RESTARTABLE + * + * Enable "non-blocking" ECC operations that can return early and be resumed. + * + * This allows various functions to pause by returning + * #MBEDTLS_ERR_ECP_IN_PROGRESS (or, for functions in the SSL module, + * #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) and then be called later again in + * order to further progress and eventually complete their operation. This is + * controlled through mbedtls_ecp_set_max_ops() which limits the maximum + * number of ECC operations a function may perform before pausing; see + * mbedtls_ecp_set_max_ops() for more information. + * + * This is useful in non-threaded environments if you want to avoid blocking + * for too long on ECC (and, hence, X.509 or SSL/TLS) operations. + * + * This option: + * - Adds xxx_restartable() variants of existing operations in the + * following modules, with corresponding restart context types: + * - ECP (for Short Weierstrass curves only): scalar multiplication (mul), + * linear combination (muladd); + * - ECDSA: signature generation & verification; + * - PK: signature generation & verification; + * - X509: certificate chain verification. + * - Adds mbedtls_ecdh_enable_restart() in the ECDH module. + * - Changes the behaviour of TLS 1.2 clients (not servers) when using the + * ECDHE-ECDSA key exchange (not other key exchanges) to make all ECC + * computations restartable: + * - ECDH operations from the key exchange, only for Short Weierstrass + * curves, only when MBEDTLS_USE_PSA_CRYPTO is not enabled. + * - verification of the server's key exchange signature; + * - verification of the server's certificate chain; + * - generation of the client's signature if client authentication is used, + * with an ECC key/certificate. + * + * \note In the cases above, the usual SSL/TLS functions, such as + * mbedtls_ssl_handshake(), can now return + * MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS. + * + * \note This option only works with the default software implementation of + * elliptic curve functionality. It is incompatible with + * MBEDTLS_ECP_ALT, MBEDTLS_ECDH_XXX_ALT, MBEDTLS_ECDSA_XXX_ALT. + * + * Requires: MBEDTLS_ECP_C + * + * Uncomment this macro to enable restartable ECC computations. + */ +#ifdef CONFIG_MBEDTLS_ECP_RESTARTABLE +#define MBEDTLS_ECP_RESTARTABLE +#endif + +/** + * \def MBEDTLS_ECDH_LEGACY_CONTEXT + * + * Use a backward compatible ECDH context. + * + * Mbed TLS supports two formats for ECDH contexts (#mbedtls_ecdh_context + * defined in `ecdh.h`). For most applications, the choice of format makes + * no difference, since all library functions can work with either format, + * except that the new format is incompatible with MBEDTLS_ECP_RESTARTABLE. + + * The new format used when this option is disabled is smaller + * (56 bytes on a 32-bit platform). In future versions of the library, it + * will support alternative implementations of ECDH operations. + * The new format is incompatible with applications that access + * context fields directly and with restartable ECP operations. + * + * Define this macro if you enable MBEDTLS_ECP_RESTARTABLE or if you + * want to access ECDH context fields directly. Otherwise you should + * comment out this macro definition. + * + * This option has no effect if #MBEDTLS_ECDH_C is not enabled. + * + * \note This configuration option is experimental. Future versions of the + * library may modify the way the ECDH context layout is configured + * and may modify the layout of the new context type. + */ +#ifdef CONFIG_MBEDTLS_ECDH_LEGACY_CONTEXT +#define MBEDTLS_ECDH_LEGACY_CONTEXT +#endif + +/** + * \def MBEDTLS_CMAC_C + * + * Enable the CMAC (Cipher-based Message Authentication Code) mode for block + * ciphers. + * + * \note When #MBEDTLS_CMAC_ALT is active, meaning that the underlying + * implementation of the CMAC algorithm is provided by an alternate + * implementation, that alternate implementation may opt to not support + * AES-192 or 3DES as underlying block ciphers for the CMAC operation. + * + * Module: library/cmac.c + * + * Requires: MBEDTLS_CIPHER_C, MBEDTLS_AES_C or MBEDTLS_DES_C + * + */ +#ifdef CONFIG_MBEDTLS_CMAC_C +#define MBEDTLS_CMAC_C +#endif + +/** + * \def MBEDTLS_ECP_DP_SECP192R1_ENABLED + * + * MBEDTLS_ECP_XXXX_ENABLED: Enables specific curves within the Elliptic Curve + * module. By default all supported curves are enabled. + * + * Comment macros to disable the curve and functions for it + */ +/* Short Weierstrass curves (supporting ECP, ECDH, ECDSA) */ +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP192R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP224R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP256R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP384R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP521R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP192K1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP224K1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#else +#undef MBEDTLS_ECP_DP_SECP256K1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_BP256R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_BP384R1_ENABLED +#endif +#ifdef CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +#else +#undef MBEDTLS_ECP_DP_BP512R1_ENABLED +#endif +/* Montgomery curves (supporting ECP) */ +#ifdef CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#else +#undef MBEDTLS_ECP_DP_CURVE25519_ENABLED +#endif +#ifdef MBEDTLS_ECP_DP_CURVE448_ENABLED +#undef MBEDTLS_ECP_DP_CURVE448_ENABLED +#endif + +/** + * \def MBEDTLS_ECP_NIST_OPTIM + * + * Enable specific 'modulo p' routines for each NIST prime. + * Depending on the prime and architecture, makes operations 4 to 8 times + * faster on the corresponding curve. + * + * Comment this macro to disable NIST curves optimisation. + */ +#ifdef CONFIG_MBEDTLS_ECP_NIST_OPTIM +#define MBEDTLS_ECP_NIST_OPTIM +#else +#undef MBEDTLS_ECP_NIST_OPTIM +#endif + +/** + * \def MBEDTLS_ECP_FIXED_POINT_OPTIM + * + * Enable speed up fixed-point multiplication. + * + * Comment this macro to disable FIXED POINT curves optimisation. + */ +#ifdef CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM +#define MBEDTLS_ECP_FIXED_POINT_OPTIM 1 +#else +#define MBEDTLS_ECP_FIXED_POINT_OPTIM 0 +#endif + +/** + * \def MBEDTLS_ECDSA_DETERMINISTIC + * + * Enable deterministic ECDSA (RFC 6979). + * Standard ECDSA is "fragile" in the sense that lack of entropy when signing + * may result in a compromise of the long-term signing key. This is avoided by + * the deterministic variant. + * + * Requires: MBEDTLS_HMAC_DRBG_C, MBEDTLS_ECDSA_C + * + * Comment this macro to disable deterministic ECDSA. + */ +#ifdef CONFIG_MBEDTLS_ECDSA_DETERMINISTIC +#define MBEDTLS_ECDSA_DETERMINISTIC +#else +#undef MBEDTLS_ECDSA_DETERMINISTIC +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + * + * Enable the PSK based ciphersuite modes in SSL / TLS. + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_PSK_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_PSK +#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED + * + * Enable the DHE-PSK based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_DHM_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_PSK +#define MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED + * + * Enable the ECDHE-PSK based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_ECDH_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_PSK +#define MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED + * + * Enable the RSA-PSK based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_RSA_C, MBEDTLS_PKCS1_V15, + * MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_PSK +#define MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_RSA_ENABLED + * + * Enable the RSA-only based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_RSA_C, MBEDTLS_PKCS1_V15, + * MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_RSA +#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED + * + * Enable the DHE-RSA based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_DHM_C, MBEDTLS_RSA_C, MBEDTLS_PKCS1_V15, + * MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA +#define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED + * + * Enable the ECDHE-RSA based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_ECDH_C, MBEDTLS_RSA_C, MBEDTLS_PKCS1_V15, + * MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA +#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + * + * Enable the ECDHE-ECDSA based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_ECDH_C, MBEDTLS_ECDSA_C, MBEDTLS_X509_CRT_PARSE_C, + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED + * + * Enable the ECDH-ECDSA based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_ECDH_C, MBEDTLS_ECDSA_C, MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA +#define MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED + * + * Enable the ECDH-RSA based ciphersuite modes in SSL / TLS. + * + * Requires: MBEDTLS_ECDH_C, MBEDTLS_RSA_C, MBEDTLS_X509_CRT_PARSE_C + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA +#define MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED +#endif + +/** + * \def MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED + * + * Enable the ECJPAKE based ciphersuite modes in SSL / TLS. + * + * \warning This is currently experimental. EC J-PAKE support is based on the + * Thread v1.0.0 specification; incompatible changes to the specification + * might still happen. For this reason, this is disabled by default. + * + * Requires: MBEDTLS_ECJPAKE_C + * MBEDTLS_SHA256_C + * MBEDTLS_ECP_DP_SECP256R1_ENABLED + * + * This enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 + */ +#ifdef CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE +#define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED +#else +#undef MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED +#endif + +/** + * \def MBEDTLS_PK_PARSE_EC_EXTENDED + * + * Enhance support for reading EC keys using variants of SEC1 not allowed by + * RFC 5915 and RFC 5480. + * + * Currently this means parsing the SpecifiedECDomain choice of EC + * parameters (only known groups are supported, not arbitrary domains, to + * avoid validation issues). + * + * Disable if you only need to support RFC 5915 + 5480 key formats. + */ +#define MBEDTLS_PK_PARSE_EC_EXTENDED + +/** + * \def MBEDTLS_ERROR_STRERROR_DUMMY + * + * Enable a dummy error function to make use of mbedtls_strerror() in + * third party libraries easier when MBEDTLS_ERROR_C is disabled + * (no effect when MBEDTLS_ERROR_C is enabled). + * + * You can safely disable this if MBEDTLS_ERROR_C is enabled, or if you're + * not using mbedtls_strerror() or error_strerror() in your application. + * + * Disable if you run into name conflicts and want to really remove the + * mbedtls_strerror() + */ +#define MBEDTLS_ERROR_STRERROR_DUMMY + +/** + * \def MBEDTLS_GENPRIME + * + * Enable the prime-number generation code. + * + * Requires: MBEDTLS_BIGNUM_C + */ +#define MBEDTLS_GENPRIME + +/** + * \def MBEDTLS_FS_IO + * + * Enable functions that use the filesystem. + */ +#define MBEDTLS_FS_IO + +/** + * \def MBEDTLS_NO_PLATFORM_ENTROPY + * + * Do not use built-in platform entropy functions. + * This is useful if your platform does not support + * standards like the /dev/urandom or Windows CryptoAPI. + * + * Uncomment this macro to disable the built-in platform entropy functions. + */ +#define MBEDTLS_NO_PLATFORM_ENTROPY + +/** + * \def MBEDTLS_PK_RSA_ALT_SUPPORT + * + * Support external private RSA keys (eg from a HSM) in the PK layer. + * + * Comment this macro to disable support for external private RSA keys. + */ +#define MBEDTLS_PK_RSA_ALT_SUPPORT + +/** + * \def MBEDTLS_PKCS1_V15 + * + * Enable support for PKCS#1 v1.5 encoding. + * + * Requires: MBEDTLS_RSA_C + * + * This enables support for PKCS#1 v1.5 operations. + */ +#define MBEDTLS_PKCS1_V15 + +/** + * \def MBEDTLS_PKCS1_V21 + * + * Enable support for PKCS#1 v2.1 encoding. + * + * Requires: MBEDTLS_MD_C, MBEDTLS_RSA_C + * + * This enables support for RSAES-OAEP and RSASSA-PSS operations. + */ +#define MBEDTLS_PKCS1_V21 + +/** + * \def MBEDTLS_SELF_TEST + * + * Enable the checkup functions (*_self_test). + */ +#define MBEDTLS_SELF_TEST + +/** + * \def MBEDTLS_SSL_ALL_ALERT_MESSAGES + * + * Enable sending of alert messages in case of encountered errors as per RFC. + * If you choose not to send the alert messages, mbed TLS can still communicate + * with other servers, only debugging of failures is harder. + * + * The advantage of not sending alert messages, is that no information is given + * about reasons for failures thus preventing adversaries of gaining intel. + * + * Enable sending of all alert messages + */ +#define MBEDTLS_SSL_ALL_ALERT_MESSAGES + +/** + * \def MBEDTLS_SSL_DTLS_CONNECTION_ID + * + * Enable support for the DTLS Connection ID (CID) extension, + * which allows to identify DTLS connections across changes + * in the underlying transport. The CID functionality is described + * in RFC 9146. + * + * Setting this option enables the SSL APIs `mbedtls_ssl_set_cid()`, + * mbedtls_ssl_get_own_cid()`, `mbedtls_ssl_get_peer_cid()` and + * `mbedtls_ssl_conf_cid()`. See the corresponding documentation for + * more information. + * + * The maximum lengths of outgoing and incoming CIDs can be configured + * through the options + * - MBEDTLS_SSL_CID_OUT_LEN_MAX + * - MBEDTLS_SSL_CID_IN_LEN_MAX. + * + * Requires: MBEDTLS_SSL_PROTO_DTLS + * + * Uncomment to enable the Connection ID extension. + */ +#ifdef CONFIG_MBEDTLS_SSL_DTLS_CONNECTION_ID +#define MBEDTLS_SSL_DTLS_CONNECTION_ID +#else +#undef MBEDTLS_SSL_DTLS_CONNECTION_ID +#endif + +/** + * \def MBEDTLS_SSL_DTLS_CONNECTION_ID_COMPAT + * + * Defines whether RFC 9146 (default) or the legacy version + * (version draft-ietf-tls-dtls-connection-id-05, + * https://tools.ietf.org/html/draft-ietf-tls-dtls-connection-id-05) + * is used. + * + * Set the value to 0 for the standard version, and + * 1 for the legacy draft version. + * + * \deprecated Support for the legacy version of the DTLS + * Connection ID feature is deprecated. Please + * switch to the standardized version defined + * in RFC 9146 enabled by utilizing + * MBEDTLS_SSL_DTLS_CONNECTION_ID without use + * of MBEDTLS_SSL_DTLS_CONNECTION_ID_COMPAT. + * + * Requires: MBEDTLS_SSL_DTLS_CONNECTION_ID + */ +#undef MBEDTLS_SSL_DTLS_CONNECTION_ID_COMPAT + +/** + * \def MBEDTLS_SSL_CONTEXT_SERIALIZATION + * + * Enable serialization of the TLS context structures, through use of the + * functions mbedtls_ssl_context_save() and mbedtls_ssl_context_load(). + * + * This pair of functions allows one side of a connection to serialize the + * context associated with the connection, then free or reuse that context + * while the serialized state is persisted elsewhere, and finally deserialize + * that state to a live context for resuming read/write operations on the + * connection. From a protocol perspective, the state of the connection is + * unaffected, in particular this is entirely transparent to the peer. + * + * Note: this is distinct from TLS session resumption, which is part of the + * protocol and fully visible by the peer. TLS session resumption enables + * establishing new connections associated to a saved session with shorter, + * lighter handshakes, while context serialization is a local optimization in + * handling a single, potentially long-lived connection. + * + * Enabling these APIs makes some SSL structures larger, as 64 extra bytes are + * saved after the handshake to allow for more efficient serialization, so if + * you don't need this feature you'll save RAM by disabling it. + * + * Requires: MBEDTLS_GCM_C or MBEDTLS_CCM_C or MBEDTLS_CHACHAPOLY_C + * + * Comment to disable the context serialization APIs. + */ +#ifdef CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION +#define MBEDTLS_SSL_CONTEXT_SERIALIZATION +#else +#undef MBEDTLS_SSL_CONTEXT_SERIALIZATION +#endif + +/** \def MBEDTLS_SSL_ENCRYPT_THEN_MAC + * + * Enable support for Encrypt-then-MAC, RFC 7366. + * + * This allows peers that both support it to use a more robust protection for + * ciphersuites using CBC, providing deep resistance against timing attacks + * on the padding or underlying cipher. + * + * This only affects CBC ciphersuites, and is useless if none is defined. + * + * Requires: MBEDTLS_SSL_PROTO_TLS1_2 + * + * Comment this macro to disable support for Encrypt-then-MAC + */ +#ifdef CONFIG_MBEDTLS_TLS_ENABLED +#define MBEDTLS_SSL_ENCRYPT_THEN_MAC +#else +#undef MBEDTLS_SSL_ENCRYPT_THEN_MAC +#endif + +/** \def MBEDTLS_SSL_EXTENDED_MASTER_SECRET + * + * Enable support for RFC 7627: Session Hash and Extended Master Secret + * Extension. + * + * This was introduced as "the proper fix" to the Triple Handshake family of + * attacks, but it is recommended to always use it (even if you disable + * renegotiation), since it actually fixes a more fundamental issue in the + * original SSL/TLS design, and has implications beyond Triple Handshake. + * + * Requires: MBEDTLS_SSL_PROTO_TLS1_2 + * + * Comment this macro to disable support for Extended Master Secret. + */ +#ifdef CONFIG_MBEDTLS_TLS_ENABLED +#define MBEDTLS_SSL_EXTENDED_MASTER_SECRET +#else +#undef MBEDTLS_SSL_EXTENDED_MASTER_SECRET +#endif + +/** + * \def MBEDTLS_SSL_FALLBACK_SCSV + * + * Enable support for RFC 7507: Fallback Signaling Cipher Suite Value (SCSV) + * for Preventing Protocol Downgrade Attacks. + * + * For servers, it is recommended to always enable this, unless you support + * only one version of TLS, or know for sure that none of your clients + * implements a fallback strategy. + * + * For clients, you only need this if you're using a fallback strategy, which + * is not recommended in the first place, unless you absolutely need it to + * interoperate with buggy (version-intolerant) servers. + * + * Comment this macro to disable support for FALLBACK_SCSV + */ +#define MBEDTLS_SSL_FALLBACK_SCSV + +/** + * \def MBEDTLS_SSL_KEEP_PEER_CERTIFICATE + * + * This option controls the availability of the API mbedtls_ssl_get_peer_cert() + * giving access to the peer's certificate after completion of the handshake. + * + * Unless you need mbedtls_ssl_peer_cert() in your application, it is + * recommended to disable this option for reduced RAM usage. + * + * \note If this option is disabled, mbedtls_ssl_get_peer_cert() is still + * defined, but always returns \c NULL. + * + * \note This option has no influence on the protection against the + * triple handshake attack. Even if it is disabled, Mbed TLS will + * still ensure that certificates do not change during renegotiation, + * for example by keeping a hash of the peer's certificate. + * + * \note This option is required if MBEDTLS_SSL_PROTO_TLS1_3 is set. + * + * Comment this macro to disable storing the peer's certificate + * after the handshake. + */ +#ifdef CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE +#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE +#else +#undef MBEDTLS_SSL_KEEP_PEER_CERTIFICATE +#endif + +/** + * \def MBEDTLS_SSL_CBC_RECORD_SPLITTING + * + * Enable 1/n-1 record splitting for CBC mode in SSLv3 and TLS 1.0. + * + * This is a countermeasure to the BEAST attack, which also minimizes the risk + * of interoperability issues compared to sending 0-length records. + * + * Comment this macro to disable 1/n-1 record splitting. + */ +#if defined(MBEDTLS_SSL_PROTO_SSL3) || defined(MBEDTLS_SSL_PROTO_TLS1) +#define MBEDTLS_SSL_CBC_RECORD_SPLITTING +#else +#undef MBEDTLS_SSL_CBC_RECORD_SPLITTING +#endif + +/** + * \def MBEDTLS_SSL_RENEGOTIATION + * + * Enable support for TLS renegotiation. + * + * The two main uses of renegotiation are (1) refresh keys on long-lived + * connections and (2) client authentication after the initial handshake. + * If you don't need renegotiation, it's probably better to disable it, since + * it has been associated with security issues in the past and is easy to + * misuse/misunderstand. + * + * Comment this to disable support for renegotiation. + * + * \note Even if this option is disabled, both client and server are aware + * of the Renegotiation Indication Extension (RFC 5746) used to + * prevent the SSL renegotiation attack (see RFC 5746 Sect. 1). + * (See \c mbedtls_ssl_conf_legacy_renegotiation for the + * configuration of this extension). + * + */ +#ifdef CONFIG_MBEDTLS_SSL_RENEGOTIATION +#define MBEDTLS_SSL_RENEGOTIATION +#else +#undef MBEDTLS_SSL_RENEGOTIATION +#endif + +/** + * \def MBEDTLS_SSL_MAX_FRAGMENT_LENGTH + * + * Enable support for RFC 6066 max_fragment_length extension in SSL. + * + * Comment this macro to disable support for the max_fragment_length extension + */ +#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH + +/** + * \def MBEDTLS_SSL_RECORD_SIZE_LIMIT + * + * Enable support for RFC 8449 record_size_limit extension in SSL (TLS 1.3 only). + * + * \warning This extension is currently in development and must NOT be used except + * for testing purposes. + * + * Requires: MBEDTLS_SSL_PROTO_TLS1_3 + * + * Uncomment this macro to enable support for the record_size_limit extension + */ +//#define MBEDTLS_SSL_RECORD_SIZE_LIMIT + +/** + * \def MBEDTLS_SSL_PROTO_TLS1_2 + * + * Enable support for TLS 1.2 (and DTLS 1.2 if DTLS is enabled). + * + * Requires: MBEDTLS_SHA1_C or MBEDTLS_SHA256_C or MBEDTLS_SHA512_C + * (Depends on ciphersuites) + * + * Comment this macro to disable support for TLS 1.2 / DTLS 1.2 + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_PROTO_TLS1_2 +#else +#undef MBEDTLS_SSL_PROTO_TLS1_2 +#endif + +/** + * \def MBEDTLS_SSL_PROTO_TLS1_3 + * + * Enable support for TLS 1.3. + * + * \note The support for TLS 1.3 is not comprehensive yet, in particular + * pre-shared keys are not supported. + * See docs/architecture/tls13-support.md for a description of the TLS + * 1.3 support that this option enables. + * + * Requires: MBEDTLS_SSL_KEEP_PEER_CERTIFICATE + * + * Uncomment this macro to enable the support for TLS 1.3. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 +#define MBEDTLS_SSL_PROTO_TLS1_3 +#else +#undef MBEDTLS_SSL_PROTO_TLS1_3 +#endif + +/** + * \def MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE + * + * Enable TLS 1.3 middlebox compatibility mode. + * + * As specified in Section D.4 of RFC 8446, TLS 1.3 offers a compatibility + * mode to make a TLS 1.3 connection more likely to pass through middle boxes + * expecting TLS 1.2 traffic. + * + * Turning on the compatibility mode comes at the cost of a few added bytes + * on the wire, but it doesn't affect compatibility with TLS 1.3 implementations + * that don't use it. Therefore, unless transmission bandwidth is critical and + * you know that middlebox compatibility issues won't occur, it is therefore + * recommended to set this option. + * + * Comment to disable compatibility mode for TLS 1.3. If + * MBEDTLS_SSL_PROTO_TLS1_3 is not enabled, this option does not have any + * effect on the build. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE +#define MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE +#else +#undef MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE +#endif + +/** + * \def MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED + * + * Enable TLS 1.3 PSK key exchange mode. + * + * Comment to disable support for the PSK key exchange mode in TLS 1.3. If + * MBEDTLS_SSL_PROTO_TLS1_3 is not enabled, this option does not have any + * effect on the build. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_PSK +#define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED +#else +#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED +#endif + +/** + * \def MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED + * + * Enable TLS 1.3 ephemeral key exchange mode. + * + * Requires: MBEDTLS_ECDH_C, MBEDTLS_X509_CRT_PARSE_C, MBEDTLS_ECDSA_C or + * MBEDTLS_PKCS1_V21 + * + * Comment to disable support for the ephemeral key exchange mode in TLS 1.3. + * If MBEDTLS_SSL_PROTO_TLS1_3 is not enabled, this option does not have any + * effect on the build. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_EPHEMERAL +#define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +#else +#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +#endif + +/** + * \def MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED + * + * Enable TLS 1.3 PSK ephemeral key exchange mode. + * + * Requires: MBEDTLS_ECDH_C + * + * Comment to disable support for the PSK ephemeral key exchange mode in + * TLS 1.3. If MBEDTLS_SSL_PROTO_TLS1_3 is not enabled, this option does not + * have any effect on the build. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_PSK_EPHEMERAL +#define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED +#else +#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED +#endif + +/** + * \def MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE + * + * Maximum time difference in milliseconds tolerated between the age of a + * ticket from the server and client point of view. + * From the client point of view, the age of a ticket is the time difference + * between the time when the client proposes to the server to use the ticket + * (time of writing of the Pre-Shared Key Extension including the ticket) and + * the time the client received the ticket from the server. + * From the server point of view, the age of a ticket is the time difference + * between the time when the server receives a proposition from the client + * to use the ticket and the time when the ticket was created by the server. + * The server age is expected to be always greater than the client one and + * MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE defines the + * maximum difference tolerated for the server to accept the ticket. + * This is not used in TLS 1.2. + * + */ +#define MBEDTLS_SSL_TLS1_3_TICKET_AGE_TOLERANCE 6000 + +/** + * \def MBEDTLS_SSL_TLS1_3_TICKET_NONCE_LENGTH + * + * Size in bytes of a ticket nonce. This is not used in TLS 1.2. + * + * This must be less than 256. + */ +#define MBEDTLS_SSL_TLS1_3_TICKET_NONCE_LENGTH 32 + +/** + * \def MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS + * + * Default number of NewSessionTicket messages to be sent by a TLS 1.3 server + * after handshake completion. This is not used in TLS 1.2 and relevant only if + * the MBEDTLS_SSL_SESSION_TICKETS option is enabled. + * + */ +#define MBEDTLS_SSL_TLS1_3_DEFAULT_NEW_SESSION_TICKETS 1 + +/** + * \def MBEDTLS_SSL_EARLY_DATA + * + * Enable support for RFC 8446 TLS 1.3 early data. + * + * Requires: MBEDTLS_SSL_SESSION_TICKETS and either + * MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED or + * MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED + * + * Comment this to disable support for early data. If MBEDTLS_SSL_PROTO_TLS1_3 + * is not enabled, this option does not have any effect on the build. + * + * This feature is experimental, not completed and thus not ready for + * production. + * + */ +//#define MBEDTLS_SSL_EARLY_DATA + +/** + * \def MBEDTLS_SSL_MAX_EARLY_DATA_SIZE + * + * The default maximum amount of 0-RTT data. See the documentation of + * \c mbedtls_ssl_tls13_conf_max_early_data_size() for more information. + * + * It must be positive and smaller than UINT32_MAX. + * + * If MBEDTLS_SSL_EARLY_DATA is not defined, this default value does not + * have any impact on the build. + * + * This feature is experimental, not completed and thus not ready for + * production. + * + */ +#define MBEDTLS_SSL_MAX_EARLY_DATA_SIZE 1024 + + +/** + * \def MBEDTLS_SSL_PROTO_DTLS + * + * Enable support for DTLS (all available versions). + * + * Enable this and MBEDTLS_SSL_PROTO_TLS1_2 to enable DTLS 1.2. + * + * Requires: MBEDTLS_SSL_PROTO_TLS1_2 + * + * Comment this macro to disable support for DTLS + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_PROTO_DTLS +#else +#undef MBEDTLS_SSL_PROTO_DTLS +#endif + +/** + * \def MBEDTLS_SSL_ALPN + * + * Enable support for RFC 7301 Application Layer Protocol Negotiation. + * + * Comment this macro to disable support for ALPN. + */ +#ifdef CONFIG_MBEDTLS_SSL_ALPN +#define MBEDTLS_SSL_ALPN +#else +#undef MBEDTLS_SSL_ALPN +#endif + +/** + * \def MBEDTLS_SSL_DTLS_ANTI_REPLAY + * + * Enable support for the anti-replay mechanism in DTLS. + * + * Requires: MBEDTLS_SSL_TLS_C + * MBEDTLS_SSL_PROTO_DTLS + * + * \warning Disabling this is often a security risk! + * See mbedtls_ssl_conf_dtls_anti_replay() for details. + * + * Comment this to disable anti-replay in DTLS. + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_DTLS_ANTI_REPLAY +#else +#undef MBEDTLS_SSL_DTLS_ANTI_REPLAY +#endif + +/** + * \def MBEDTLS_SSL_DTLS_HELLO_VERIFY + * + * Enable support for HelloVerifyRequest on DTLS servers. + * + * This feature is highly recommended to prevent DTLS servers being used as + * amplifiers in DoS attacks against other hosts. It should always be enabled + * unless you know for sure amplification cannot be a problem in the + * environment in which your server operates. + * + * \warning Disabling this can ba a security risk! (see above) + * + * Requires: MBEDTLS_SSL_PROTO_DTLS + * + * Comment this to disable support for HelloVerifyRequest. + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_DTLS_HELLO_VERIFY +#else +#undef MBEDTLS_SSL_DTLS_HELLO_VERIFY +#endif + +/** + * \def MBEDTLS_SSL_DTLS_SRTP + * + * Enable support for negotiation of DTLS-SRTP (RFC 5764) + * through the use_srtp extension. + * + * \note This feature provides the minimum functionality required + * to negotiate the use of DTLS-SRTP and to allow the derivation of + * the associated SRTP packet protection key material. + * In particular, the SRTP packet protection itself, as well as the + * demultiplexing of RTP and DTLS packets at the datagram layer + * (see Section 5 of RFC 5764), are not handled by this feature. + * Instead, after successful completion of a handshake negotiating + * the use of DTLS-SRTP, the extended key exporter API + * mbedtls_ssl_conf_export_keys_ext_cb() should be used to implement + * the key exporter described in Section 4.2 of RFC 5764 and RFC 5705 + * (this is implemented in the SSL example programs). + * The resulting key should then be passed to an SRTP stack. + * + * Setting this option enables the runtime API + * mbedtls_ssl_conf_dtls_srtp_protection_profiles() + * through which the supported DTLS-SRTP protection + * profiles can be configured. You must call this API at + * runtime if you wish to negotiate the use of DTLS-SRTP. + * + * Requires: MBEDTLS_SSL_PROTO_DTLS + * + * Uncomment this to enable support for use_srtp extension. + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_DTLS_SRTP +#else +#undef MBEDTLS_SSL_DTLS_SRTP +#endif + +/** + * \def MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE + * + * Enable server-side support for clients that reconnect from the same port. + * + * Some clients unexpectedly close the connection and try to reconnect using the + * same source port. This needs special support from the server to handle the + * new connection securely, as described in section 4.2.8 of RFC 6347. This + * flag enables that support. + * + * Requires: MBEDTLS_SSL_DTLS_HELLO_VERIFY + * + * Comment this to disable support for clients reusing the source port. + */ +#ifdef CONFIG_MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE +#else +#undef MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE +#endif + +/** + * \def MBEDTLS_SSL_SESSION_TICKETS + * + * Enable support for RFC 5077 session tickets in SSL. + * Client-side, provides full support for session tickets (maintenance of a + * session store remains the responsibility of the application, though). + * Server-side, you also need to provide callbacks for writing and parsing + * tickets, including authenticated encryption and key management. Example + * callbacks are provided by MBEDTLS_SSL_TICKET_C. + * + * Comment this macro to disable support for SSL session tickets + */ +#ifdef CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS +#define MBEDTLS_SSL_SESSION_TICKETS +#else +#undef MBEDTLS_SSL_SESSION_TICKETS +#endif + +/** + * \def MBEDTLS_SSL_EXPORT_KEYS + * + * Enable support for exporting key block and master secret. + * This is required for certain users of TLS, e.g. EAP-TLS. + * + * Comment this macro to disable support for key export + */ +#define MBEDTLS_SSL_EXPORT_KEYS + +/** + * \def MBEDTLS_SSL_SERVER_NAME_INDICATION + * + * Enable support for RFC 6066 server name indication (SNI) in SSL. + * + * Requires: MBEDTLS_X509_CRT_PARSE_C + * + * Comment this macro to disable support for server name indication in SSL + */ +#define MBEDTLS_SSL_SERVER_NAME_INDICATION + + +/** + * \def MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH + * + * When this option is enabled, the SSL buffer will be resized automatically + * based on the negotiated maximum fragment length in each direction. + * + * Requires: MBEDTLS_SSL_MAX_FRAGMENT_LENGTH + */ +#ifdef CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH +#define MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH +#else +#undef MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH +#endif + +/** + * + * \def MBEDTLS_VERSION_FEATURES + * + * Allow run-time checking of compile-time enabled features. Thus allowing users + * to check at run-time if the library is for instance compiled with threading + * support via mbedtls_version_check_feature(). + * + * Requires: MBEDTLS_VERSION_C + * + * Comment this to disable run-time checking and save ROM space + */ +#define MBEDTLS_VERSION_FEATURES + + +/** + * \def MBEDTLS_X509_RSASSA_PSS_SUPPORT + * + * Enable parsing and verification of X.509 certificates, CRLs and CSRS + * signed with RSASSA-PSS (aka PKCS#1 v2.1). + * + * Comment this macro to disallow using RSASSA-PSS in certificates. + */ +#define MBEDTLS_X509_RSASSA_PSS_SUPPORT + + +/* \} name SECTION: mbed TLS feature support */ + +/** + * \name SECTION: mbed TLS modules + * + * This section enables or disables entire modules in mbed TLS + * \{ + */ + +/** + * \def MBEDTLS_AESNI_C + * + * Enable AES-NI support on x86-64. + * + * Module: library/aesni.c + * Caller: library/aes.c + * + * Requires: MBEDTLS_HAVE_ASM + * + * This modules adds support for the AES-NI instructions on x86-64 + */ +#define MBEDTLS_AESNI_C + +/** + * \def MBEDTLS_AES_C + * + * Enable the AES block cipher. + * + * Module: library/aes.c + * Caller: library/ssl_tls.c + * library/pem.c + * library/ctr_drbg.c + * + * This module enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA + * MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384 + * MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA384 + * MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA + * MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256 + * MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA256 + * MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA + * + * PEM_PARSE uses AES for decrypting encrypted keys. + */ +#ifdef CONFIG_MBEDTLS_AES_C +#define MBEDTLS_AES_C +#else +#undef MBEDTLS_AES_C +#endif + +/** + * \def MBEDTLS_ASN1_PARSE_C + * + * Enable the generic ASN1 parser. + * + * Module: library/asn1.c + * Caller: library/x509.c + * library/dhm.c + * library/pkcs12.c + * library/pkcs5.c + * library/pkparse.c + */ +#define MBEDTLS_ASN1_PARSE_C + +/** + * \def MBEDTLS_ASN1_WRITE_C + * + * Enable the generic ASN1 writer. + * + * Module: library/asn1write.c + * Caller: library/ecdsa.c + * library/pkwrite.c + * library/x509_create.c + * library/x509write_crt.c + * library/mbedtls_x509write_csr.c + */ +#define MBEDTLS_ASN1_WRITE_C + +/** + * \def MBEDTLS_BASE64_C + * + * Enable the Base64 module. + * + * Module: library/base64.c + * Caller: library/pem.c + * + * This module is required for PEM support (required by X.509). + */ +#define MBEDTLS_BASE64_C + +/** + * \def MBEDTLS_BIGNUM_C + * + * Enable the multi-precision integer library. + * + * Module: library/bignum.c + * library/bignum_core.c + * library/bignum_mod.c + * library/bignum_mod_raw.c + * Caller: library/dhm.c + * library/ecp.c + * library/ecdsa.c + * library/rsa.c + * library/rsa_alt_helpers.c + * library/ssl_tls.c + * + * This module is required for RSA, DHM and ECC (ECDH, ECDSA) support. + */ +#define MBEDTLS_BIGNUM_C + +/** + * \def MBEDTLS_BLOWFISH_C + * + * Enable the Blowfish block cipher. + * + * Module: library/blowfish.c + */ +#ifdef CONFIG_MBEDTLS_BLOWFISH_C +#define MBEDTLS_BLOWFISH_C +#else +#undef MBEDTLS_BLOWFISH_C +#endif + +/** + * \def MBEDTLS_CAMELLIA_C + * + * Enable the Camellia block cipher. + * + * Module: library/camellia.c + * Caller: library/ssl_tls.c + * + * This module enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 + * MBEDTLS_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 + */ +#ifdef CONFIG_MBEDTLS_CAMELLIA_C +#define MBEDTLS_CAMELLIA_C +#else +#undef MBEDTLS_CAMELLIA_C +#endif + +/** + * \def MBEDTLS_CCM_C + * + * Enable the Counter with CBC-MAC (CCM) mode for 128-bit block cipher. + * + * Module: library/ccm.c + * + * Requires: MBEDTLS_CIPHER_C, MBEDTLS_AES_C or MBEDTLS_CAMELLIA_C or + * MBEDTLS_ARIA_C + * + * This module enables the AES-CCM ciphersuites, if other requisites are + * enabled as well. + */ +#ifdef CONFIG_MBEDTLS_CCM_C +#define MBEDTLS_CCM_C +#else +#undef MBEDTLS_CCM_C +#endif + +/** + * \def MBEDTLS_CERTS_C + * + * Enable the test certificates. + * + * Module: library/certs.c + * Caller: + * + * This module is used for testing (ssl_client/server). + */ +#define MBEDTLS_CERTS_C + +/** + * \def MBEDTLS_CHACHA20_C + * + * Enable the ChaCha20 stream cipher. + * + * Module: library/chacha20.c + */ +#ifdef CONFIG_MBEDTLS_CHACHA20_C +#define MBEDTLS_CHACHA20_C +#else +#undef MBEDTLS_CHACHA20_C +#endif + +/** + * \def MBEDTLS_CHACHAPOLY_C + * + * Enable the ChaCha20-Poly1305 AEAD algorithm. + * + * Module: library/chachapoly.c + * + * This module requires: MBEDTLS_CHACHA20_C, MBEDTLS_POLY1305_C + */ +#ifdef CONFIG_MBEDTLS_CHACHAPOLY_C +#define MBEDTLS_CHACHAPOLY_C +#else +#undef MBEDTLS_CHACHAPOLY_C +#endif + +/** + * \def MBEDTLS_CIPHER_C + * + * Enable the generic cipher layer. + * + * Module: library/cipher.c + * Caller: library/ccm.c + * library/cmac.c + * library/gcm.c + * library/nist_kw.c + * library/pkcs12.c + * library/pkcs5.c + * library/psa_crypto_aead.c + * library/psa_crypto_mac.c + * library/ssl_ciphersuites.c + * library/ssl_msg.c + * library/ssl_ticket.c (unless MBEDTLS_USE_PSA_CRYPTO is enabled) + * + * Uncomment to enable generic cipher wrappers. + */ +#define MBEDTLS_CIPHER_C + +/** + * \def MBEDTLS_CTR_DRBG_C + * + * Enable the CTR_DRBG AES-256-based random generator. + * + * Module: library/ctr_drbg.c + * Caller: + * + * Requires: MBEDTLS_AES_C + * + * This module provides the CTR_DRBG AES-256 random number generator. + */ +#define MBEDTLS_CTR_DRBG_C + +/** + * \def MBEDTLS_DEBUG_C + * + * Enable the debug functions. + * + * Module: library/debug.c + * Caller: library/ssl_msg.c + * library/ssl_tls.c + * library/ssl_tls12_*.c + * library/ssl_tls13_*.c + * + * This module provides debugging functions. + */ +#if CONFIG_MBEDTLS_DEBUG +#define MBEDTLS_DEBUG_C +#else +#undef MBEDTLS_DEBUG_C +#endif + +/** + * \def MBEDTLS_DES_C + * + * Enable the DES block cipher. + * + * Module: library/des.c + * Caller: library/pem.c + * library/ssl_tls.c + * + * This module enables the following ciphersuites (if other requisites are + * enabled as well): + * MBEDTLS_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_RSA_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA + * MBEDTLS_TLS_PSK_WITH_3DES_EDE_CBC_SHA + * + * PEM_PARSE uses DES/3DES for decrypting encrypted keys. + */ +#ifdef CONFIG_MBEDTLS_DES_C +#define MBEDTLS_DES_C +#else +#undef MBEDTLS_DES_C +#endif + +/** + * \def MBEDTLS_DHM_C + * + * Enable the Diffie-Hellman-Merkle module. + * + * Module: library/dhm.c + * Caller: library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c + * + * This module is used by the following key exchanges: + * DHE-RSA, DHE-PSK + */ +#ifdef CONFIG_MBEDTLS_DHM_C +#define MBEDTLS_DHM_C +#else +#undef MBEDTLS_DHM_C +#endif + +/** + * \def MBEDTLS_ECDH_C + * + * Enable the elliptic curve Diffie-Hellman library. + * + * Module: library/ecdh.c + * Caller: library/psa_crypto.c + * library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c + * + * This module is used by the following key exchanges: + * ECDHE-ECDSA, ECDHE-RSA, DHE-PSK + * + * Requires: MBEDTLS_ECP_C + */ +#ifdef CONFIG_MBEDTLS_ECDH_C +#define MBEDTLS_ECDH_C +#else +#undef MBEDTLS_ECDH_C +#endif + +/** + * \def MBEDTLS_ECDSA_C + * + * Enable the elliptic curve DSA library. + * + * Module: library/ecdsa.c + * Caller: + * + * This module is used by the following key exchanges: + * ECDHE-ECDSA + * + * Requires: MBEDTLS_ECP_C, MBEDTLS_ASN1_WRITE_C, MBEDTLS_ASN1_PARSE_C, + * and at least one MBEDTLS_ECP_DP_XXX_ENABLED for a + * short Weierstrass curve. + */ +#ifdef CONFIG_MBEDTLS_ECDSA_C +#define MBEDTLS_ECDSA_C +#else +#undef MBEDTLS_ECDSA_C +#endif + +/** + * \def MBEDTLS_ECJPAKE_C + * + * Enable the elliptic curve J-PAKE library. + * + * \warning This is currently experimental. EC J-PAKE support is based on the + * Thread v1.0.0 specification; incompatible changes to the specification + * might still happen. For this reason, this is disabled by default. + * + * Module: library/ecjpake.c + * Caller: + * + * This module is used by the following key exchanges: + * ECJPAKE + * + * Requires: MBEDTLS_ECP_C and MBEDTLS_MD_C + * + */ +#ifdef CONFIG_MBEDTLS_ECJPAKE_C +#define MBEDTLS_ECJPAKE_C +#else +#undef MBEDTLS_ECJPAKE_C +#endif + +/** + * \def MBEDTLS_ECP_C + * + * Enable the elliptic curve over GF(p) library. + * + * Module: library/ecp.c + * Caller: library/ecdh.c + * library/ecdsa.c + * library/ecjpake.c + * + * Requires: MBEDTLS_BIGNUM_C and at least one MBEDTLS_ECP_DP_XXX_ENABLED + */ +#ifdef CONFIG_MBEDTLS_ECP_C +#define MBEDTLS_ECP_C +#else +#undef MBEDTLS_ECP_C +#endif + +/** + * \def MBEDTLS_ENTROPY_C + * + * Enable the platform-specific entropy code. + * + * Module: library/entropy.c + * Caller: + * + * Requires: MBEDTLS_SHA512_C or MBEDTLS_SHA256_C + * + * This module provides a generic entropy pool + */ +#define MBEDTLS_ENTROPY_C + +/** + * \def MBEDTLS_ERROR_C + * + * Enable error code to error string conversion. + * + * Module: library/error.c + * Caller: + * + * This module enables mbedtls_strerror(). + */ +#if CONFIG_MBEDTLS_ERROR_STRINGS +#define MBEDTLS_ERROR_C +#else +#undef MBEDTLS_ERROR_C +#endif + +/** + * \def MBEDTLS_GCM_C + * + * Enable the Galois/Counter Mode (GCM). + * + * Module: library/gcm.c + * + * Requires: MBEDTLS_CIPHER_C, MBEDTLS_AES_C or MBEDTLS_CAMELLIA_C or + * MBEDTLS_ARIA_C + * + * This module enables the AES-GCM and CAMELLIA-GCM ciphersuites, if other + * requisites are enabled as well. + */ +#ifdef CONFIG_MBEDTLS_GCM_C +#define MBEDTLS_GCM_C +#else +#undef MBEDTLS_GCM_C +#endif + +/** + * \def MBEDTLS_HKDF_C + * + * Enable the HKDF algorithm (RFC 5869). + * + * Module: library/hkdf.c + * Caller: + * + * Requires: MBEDTLS_MD_C + * + * This module enables support for the Hashed Message Authentication Code + * (HMAC)-based key derivation function (HKDF). + */ +#ifdef CONFIG_MBEDTLS_HKDF_C +#define MBEDTLS_HKDF_C +#else +#undef MBEDTLS_HKDF_C +#endif + +/** + * \def MBEDTLS_HMAC_DRBG_C + * + * Enable the HMAC_DRBG random generator. + * + * Module: library/hmac_drbg.c + * Caller: + * + * Requires: MBEDTLS_MD_C + * + * Uncomment to enable the HMAC_DRBG random number generator. + */ +#define MBEDTLS_HMAC_DRBG_C + +/** + * \def MBEDTLS_MD_C + * + * Enable the generic message digest layer. + * + * Requires: one of: MBEDTLS_MD5_C, MBEDTLS_RIPEMD160_C, MBEDTLS_SHA1_C, + * MBEDTLS_SHA224_C, MBEDTLS_SHA256_C, MBEDTLS_SHA384_C, + * MBEDTLS_SHA512_C. + * + * Module: library/md.c + * Caller: library/constant_time.c + * library/ecdsa.c + * library/ecjpake.c + * library/hkdf.c + * library/hmac_drbg.c + * library/pk.c + * library/pkcs5.c + * library/pkcs12.c + * library/psa_crypto_ecp.c + * library/psa_crypto_rsa.c + * library/rsa.c + * library/ssl_cookie.c + * library/ssl_msg.c + * library/ssl_tls.c + * library/x509.c + * library/x509_crt.c + * library/x509write_crt.c + * library/x509write_csr.c + * + * Uncomment to enable generic message digest wrappers. + */ +#define MBEDTLS_MD_C + +/** + * \def MBEDTLS_MD5_C + * + * Enable the MD5 hash algorithm. + * + * Module: library/mbedtls_md5.c + * Caller: library/mbedtls_md.c + * library/pem.c + * library/ssl_tls.c + * + * This module is required for SSL/TLS and X.509. + * PEM_PARSE uses MD5 for decrypting encrypted keys. + */ +#define MBEDTLS_MD5_C + +/** + * \def MBEDTLS_NET_C + * + * Enable the TCP and UDP over IPv6/IPv4 networking routines. + * + * \note This module only works on POSIX/Unix (including Linux, BSD and OS X) + * and Windows. For other platforms, you'll want to disable it, and write your + * own networking callbacks to be passed to \c mbedtls_ssl_set_bio(). + * + * \note See also our Knowledge Base article about porting to a new + * environment: + * https://mbed-tls.readthedocs.io/en/latest/kb/how-to/how-do-i-port-mbed-tls-to-a-new-environment-OS + * + * Module: library/net_sockets.c + * + * This module provides networking routines. + */ +#ifdef MBEDTLS_NET_C +#undef MBEDTLS_NET_C +#endif + +/** + * \def MBEDTLS_OID_C + * + * Enable the OID database. + * + * Module: library/oid.c + * Caller: library/asn1write.c + * library/pkcs5.c + * library/pkparse.c + * library/pkwrite.c + * library/rsa.c + * library/x509.c + * library/x509_create.c + * library/mbedtls_x509_crl.c + * library/mbedtls_x509_crt.c + * library/mbedtls_x509_csr.c + * library/x509write_crt.c + * library/mbedtls_x509write_csr.c + * + * This modules translates between OIDs and internal values. + */ +#define MBEDTLS_OID_C + +/** + * \def MBEDTLS_PADLOCK_C + * + * Enable VIA Padlock support on x86. + * + * Module: library/padlock.c + * Caller: library/aes.c + * + * Requires: MBEDTLS_HAVE_ASM + * + * This modules adds support for the VIA PadLock on x86. + */ +#define MBEDTLS_PADLOCK_C + +/** + * \def MBEDTLS_PEM_PARSE_C + * + * Enable PEM decoding / parsing. + * + * Module: library/pem.c + * Caller: library/dhm.c + * library/pkparse.c + * library/mbedtls_x509_crl.c + * library/mbedtls_x509_crt.c + * library/mbedtls_x509_csr.c + * + * Requires: MBEDTLS_BASE64_C + * + * This modules adds support for decoding / parsing PEM files. + */ +#ifdef CONFIG_MBEDTLS_PEM_PARSE_C +#define MBEDTLS_PEM_PARSE_C +#else +#undef MBEDTLS_PEM_PARSE_C +#endif + +/** + * \def MBEDTLS_PEM_WRITE_C + * + * Enable PEM encoding / writing. + * + * Module: library/pem.c + * Caller: library/pkwrite.c + * library/x509write_crt.c + * library/mbedtls_x509write_csr.c + * + * Requires: MBEDTLS_BASE64_C + * + * This modules adds support for encoding / writing PEM files. + */ +#ifdef CONFIG_MBEDTLS_PEM_WRITE_C +#define MBEDTLS_PEM_WRITE_C +#else +#undef MBEDTLS_PEM_WRITE_C +#endif + +/** + * \def MBEDTLS_PK_C + * + * Enable the generic public (asymmetric) key layer. + * + * Module: library/pk.c + * Caller: library/psa_crypto_rsa.c + * library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c + * library/x509.c + * + * Requires: MBEDTLS_MD_C, MBEDTLS_RSA_C or MBEDTLS_ECP_C + * + * Requires: MBEDTLS_RSA_C or MBEDTLS_ECP_C + * + * Uncomment to enable generic public key wrappers. + */ +#define MBEDTLS_PK_C + +/** + * \def MBEDTLS_PK_PARSE_C + * + * Enable the generic public (asymmetric) key parser. + * + * Module: library/pkparse.c + * Caller: library/mbedtls_x509_crt.c + * library/mbedtls_x509_csr.c + * + * Requires: MBEDTLS_PK_C + * + * Uncomment to enable generic public key parse functions. + */ +#define MBEDTLS_PK_PARSE_C + +/** + * \def MBEDTLS_PK_WRITE_C + * + * Enable the generic public (asymmetric) key writer. + * + * Module: library/pkwrite.c + * Caller: library/x509write.c + * + * Requires: MBEDTLS_PK_C + * + * Uncomment to enable generic public key write functions. + */ +#define MBEDTLS_PK_WRITE_C + +/** + * \def MBEDTLS_PKCS5_C + * + * Enable PKCS#5 functions. + * + * Module: library/pkcs5.c + * + * Requires: MBEDTLS_CIPHER_C and MBEDTLS_MD_C + * + * This module adds support for the PKCS#5 functions. + */ +#define MBEDTLS_PKCS5_C + +/** + * \def MBEDTLS_PKCS7_C + * + * This feature is a work in progress and not ready for production. Testing and + * validation is incomplete, and handling of malformed inputs may not be robust. + * The API may change. + * + * Enable PKCS7 core for using PKCS7 formatted signatures. + * RFC Link - https://tools.ietf.org/html/rfc2315 + * + * Module: library/pkcs7.c + * + * Requires: MBEDTLS_ASN1_PARSE_C, MBEDTLS_OID_C, MBEDTLS_PK_PARSE_C, + * MBEDTLS_X509_CRT_PARSE_C MBEDTLS_X509_CRL_PARSE_C, + * MBEDTLS_BIGNUM_C, MBEDTLS_MD_C + * + * This module is required for the PKCS #7 parsing modules. + */ +#ifdef CONFIG_MBEDTLS_PKCS7_C +#define MBEDTLS_PKCS7_C +#else +#undef MBEDTLS_PKCS7_C +#endif + +/** + * \def MBEDTLS_PKCS12_C + * + * Enable PKCS#12 PBE functions. + * Adds algorithms for parsing PKCS#8 encrypted private keys + * + * Module: library/pkcs12.c + * Caller: library/pkparse.c + * + * Requires: MBEDTLS_ASN1_PARSE_C, MBEDTLS_CIPHER_C, MBEDTLS_MD_C + * + * This module enables PKCS#12 functions. + */ +#define MBEDTLS_PKCS12_C + +/** + * \def MBEDTLS_PLATFORM_C + * + * Enable the platform abstraction layer that allows you to re-assign + * functions like calloc(), free(), snprintf(), printf(), fprintf(), exit(). + * + * Enabling MBEDTLS_PLATFORM_C enables to use of MBEDTLS_PLATFORM_XXX_ALT + * or MBEDTLS_PLATFORM_XXX_MACRO directives, allowing the functions mentioned + * above to be specified at runtime or compile time respectively. + * + * \note This abstraction layer must be enabled on Windows (including MSYS2) + * as other modules rely on it for a fixed snprintf implementation. + * + * Module: library/platform.c + * Caller: Most other .c files + * + * This module enables abstraction of common (libc) functions. + */ +#define MBEDTLS_PLATFORM_C + +/** + * \def MBEDTLS_POLY1305_C + * + * Enable the Poly1305 MAC algorithm. + * + * Module: library/poly1305.c + * Caller: library/chachapoly.c + */ +#ifdef CONFIG_MBEDTLS_POLY1305_C +#define MBEDTLS_POLY1305_C +#else +#undef MBEDTLS_POLY1305_C +#endif + +/** + * \def MBEDTLS_RIPEMD160_C + * + * Enable the RIPEMD-160 hash algorithm. + * + * Module: library/mbedtls_ripemd160.c + * Caller: library/mbedtls_md.c + * + */ +#ifdef CONFIG_MBEDTLS_RIPEMD160_C +#define MBEDTLS_RIPEMD160_C +#else +#undef MBEDTLS_RIPEMD160_C +#endif + +/** + * \def MBEDTLS_RSA_C + * + * Enable the RSA public-key cryptosystem. + * + * Module: library/rsa.c + * library/rsa_alt_helpers.c + * Caller: library/pk.c + * library/psa_crypto.c + * library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c + * + * This module is used by the following key exchanges: + * RSA, DHE-RSA, ECDHE-RSA, RSA-PSK + * + * Requires: MBEDTLS_BIGNUM_C, MBEDTLS_OID_C + */ +#define MBEDTLS_RSA_C + +/** + * \def MBEDTLS_SHA1_C + * + * Enable the SHA1 cryptographic hash algorithm. + * + * Module: library/sha1.c + * Caller: library/md.c + * library/psa_crypto_hash.c + * + * This module is required for TLS 1.2 depending on the handshake parameters, + * and for SHA1-signed certificates. + * + * \warning SHA-1 is considered a weak message digest and its use constitutes + * a security risk. If possible, we recommend avoiding dependencies + * on it, and considering stronger message digests instead. + * + */ +#define MBEDTLS_SHA1_C + +/** + * \def MBEDTLS_SHA224_C + * + * Enable the SHA-224 cryptographic hash algorithm. + * + * Requires: MBEDTLS_SHA256_C. The library does not currently support enabling + * SHA-224 without SHA-256. + * + * Module: library/sha256.c + * Caller: library/md.c + * library/ssl_cookie.c + * + * This module adds support for SHA-224. + */ +#define MBEDTLS_SHA224_C + +/** + * \def MBEDTLS_SHA256_C + * + * Enable the SHA-224 and SHA-256 cryptographic hash algorithms. + * + * Module: library/mbedtls_sha256.c + * Caller: library/entropy.c + * library/mbedtls_md.c + * library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c= + * + * This module adds support for SHA-224 and SHA-256. + * This module is required for the SSL/TLS 1.2 PRF function. + */ +#define MBEDTLS_SHA256_C + +/** + * \def MBEDTLS_SHA512_C + * + * Enable the SHA-384 and SHA-512 cryptographic hash algorithms. + * + * Module: library/sha512.c + * Caller: library/entropy.c + * library/md.c + * library/ssl_tls.c + * library/ssl_cookie.c + * + * This module adds support for SHA-384 and SHA-512. + */ +#ifdef CONFIG_MBEDTLS_SHA512_C +#define MBEDTLS_SHA384_C +#define MBEDTLS_SHA512_C +#else +#undef MBEDTLS_SHA384_C +#undef MBEDTLS_SHA512_C +#endif + +/** + * \def MBEDTLS_SHA3_C + * + * Enable the SHA3 cryptographic hash algorithm. + * + * Module: library/sha3.c + * + * This module adds support for SHA3. + */ +#ifdef CONFIG_MBEDTLS_SHA3_C +#define MBEDTLS_SHA3_C +#else +#undef MBEDTLS_SHA3_C +#endif + +/** + * \def MBEDTLS_SSL_CACHE_C + * + * Enable simple SSL cache implementation. + * + * Module: library/ssl_cache.c + * Caller: + * + * Requires: MBEDTLS_SSL_CACHE_C + */ +#define MBEDTLS_SSL_CACHE_C + +/** + * \def MBEDTLS_SSL_COOKIE_C + * + * Enable basic implementation of DTLS cookies for hello verification. + * + * Module: library/ssl_cookie.c + * Caller: + */ +#define MBEDTLS_SSL_COOKIE_C + +/** + * \def MBEDTLS_SSL_TICKET_C + * + * Enable an implementation of TLS server-side callbacks for session tickets. + * + * Module: library/ssl_ticket.c + * Caller: + * + * Requires: (MBEDTLS_CIPHER_C) && + * (MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C) + */ +#ifdef CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS +#define MBEDTLS_SSL_TICKET_C +#else +#undef MBEDTLS_SSL_TICKET_C +#endif + +/** + * \def MBEDTLS_SSL_CLI_C + * + * Enable the SSL/TLS client code. + * + * Module: library/ssl*_client.c + * Caller: + * + * Requires: MBEDTLS_SSL_TLS_C + * + * This module is required for SSL/TLS client support. + */ +#ifdef CONFIG_MBEDTLS_TLS_CLIENT +#define MBEDTLS_SSL_CLI_C +#else +#undef MBEDTLS_SSL_CLI_C +#endif + +/** + * \def MBEDTLS_SSL_SRV_C + * + * Enable the SSL/TLS server code. + * + * Module: library/ssl_srv.c + * Caller: + * + * Requires: MBEDTLS_SSL_TLS_C + * + * This module is required for SSL/TLS server support. + */ +#ifdef CONFIG_MBEDTLS_TLS_SERVER +#define MBEDTLS_SSL_SRV_C +#else +#undef MBEDTLS_SSL_SRV_C +#endif + +/** + * \def MBEDTLS_SSL_TLS_C + * + * Enable the generic SSL/TLS code. + * + * Module: library/ssl_tls.c + * Caller: library/ssl*_client.c + * library/ssl*_server.c + * + * Requires: MBEDTLS_CIPHER_C, MBEDTLS_MD_C + * and at least one of the MBEDTLS_SSL_PROTO_XXX defines + * + * This module is required for SSL/TLS. + */ +#ifdef CONFIG_MBEDTLS_TLS_ENABLED +#define MBEDTLS_SSL_TLS_C +#else +#undef MBEDTLS_SSL_TLS_C +#endif + +/** + * \def MBEDTLS_TIMING_C + * + * Enable the semi-portable timing interface. + * + * \note The provided implementation only works on POSIX/Unix (including Linux, + * BSD and OS X) and Windows. On other platforms, you can either disable that + * module and provide your own implementations of the callbacks needed by + * \c mbedtls_ssl_set_timer_cb() for DTLS, or leave it enabled and provide + * your own implementation of the whole module by setting + * \c MBEDTLS_TIMING_ALT in the current file. + * + * \note See also our Knowledge Base article about porting to a new + * environment: + * https://mbed-tls.readthedocs.io/en/latest/kb/how-to/how-do-i-port-mbed-tls-to-a-new-environment-OS + * + * Module: library/timing.c + * Caller: library/havege.c + * + * This module is used by the HAVEGE random number generator. + */ +#ifdef MBEDTLS_TIMING_C +#undef MBEDTLS_TIMING_C +#endif + +/** + * \def MBEDTLS_VERSION_C + * + * Enable run-time version information. + * + * Module: library/version.c + * + * This module provides run-time version information. + */ +#define MBEDTLS_VERSION_C + +/** + * \def MBEDTLS_X509_USE_C + * + * Enable X.509 core for using certificates. + * + * Module: library/x509.c + * Caller: library/mbedtls_x509_crl.c + * library/mbedtls_x509_crt.c + * library/mbedtls_x509_csr.c + * + * Requires: MBEDTLS_ASN1_PARSE_C, MBEDTLS_BIGNUM_C, MBEDTLS_OID_C, + * MBEDTLS_PK_PARSE_C, MBEDTLS_MD_C + * + * This module is required for the X.509 parsing modules. + */ +#define MBEDTLS_X509_USE_C + +/** + * \def MBEDTLS_X509_CRT_PARSE_C + * + * Enable X.509 certificate parsing. + * + * Module: library/mbedtls_x509_crt.c + * Caller: library/ssl_tls.c + * library/ssl*_client.c + * library/ssl*_server.c + * + * Requires: MBEDTLS_X509_USE_C + * + * This module is required for X.509 certificate parsing. + */ +#define MBEDTLS_X509_CRT_PARSE_C + +/** + * \def MBEDTLS_X509_CRL_PARSE_C + * + * Enable X.509 CRL parsing. + * + * Module: library/mbedtls_x509_crl.c + * Caller: library/mbedtls_x509_crt.c + * + * Requires: MBEDTLS_X509_USE_C + * + * This module is required for X.509 CRL parsing. + */ +#ifdef CONFIG_MBEDTLS_X509_CRL_PARSE_C +#define MBEDTLS_X509_CRL_PARSE_C +#else +#undef MBEDTLS_X509_CRL_PARSE_C +#endif + +/** + * \def MBEDTLS_X509_CSR_PARSE_C + * + * Enable X.509 Certificate Signing Request (CSR) parsing. + * + * Module: library/mbedtls_x509_csr.c + * Caller: library/x509_crt_write.c + * + * Requires: MBEDTLS_X509_USE_C + * + * This module is used for reading X.509 certificate request. + */ +#ifdef CONFIG_MBEDTLS_X509_CSR_PARSE_C +#define MBEDTLS_X509_CSR_PARSE_C +#else +#undef MBEDTLS_X509_CSR_PARSE_C +#endif + +/** + * \def MBEDTLS_X509_CREATE_C + * + * Enable X.509 core for creating certificates. + * + * Module: library/x509_create.c + * + * Requires: MBEDTLS_BIGNUM_C, MBEDTLS_OID_C, MBEDTLS_PK_WRITE_C, + * MBEDTLS_MD_C + * + * This module is the basis for creating X.509 certificates and CSRs. + */ +#define MBEDTLS_X509_CREATE_C + +/** + * \def MBEDTLS_X509_CRT_WRITE_C + * + * Enable creating X.509 certificates. + * + * Module: library/x509_crt_write.c + * + * Requires: MBEDTLS_X509_CREATE_C + * + * This module is required for X.509 certificate creation. + */ +#define MBEDTLS_X509_CRT_WRITE_C + +/** + * \def MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK + * + * If set, this enables the X.509 API `mbedtls_x509_crt_verify_with_ca_cb()` + * and the SSL API `mbedtls_ssl_conf_ca_cb()` which allow users to configure + * the set of trusted certificates through a callback instead of a linked + * list. + * + * This is useful for example in environments where a large number of trusted + * certificates is present and storing them in a linked list isn't efficient + * enough, or when the set of trusted certificates changes frequently. + * + * See the documentation of `mbedtls_x509_crt_verify_with_ca_cb()` and + * `mbedtls_ssl_conf_ca_cb()` for more information. + * + * Uncomment to enable trusted certificate callbacks. + */ +#ifdef CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK +#define MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK +#else +#undef MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK +#endif + +/** + * \def MBEDTLS_X509_CSR_WRITE_C + * + * Enable creating X.509 Certificate Signing Requests (CSR). + * + * Module: library/x509_csr_write.c + * + * Requires: MBEDTLS_X509_CREATE_C + * + * This module is required for X.509 certificate request writing. + */ +#define MBEDTLS_X509_CSR_WRITE_C + +/** + * \def MBEDTLS_XTEA_C + * + * Enable the XTEA block cipher. + * + * Module: library/xtea.c + * Caller: + */ +#ifdef CONFIG_MBEDTLS_XTEA_C +#define MBEDTLS_XTEA_C +#else +#undef MBEDTLS_XTEA_C +#endif + +/* \} name SECTION: mbed TLS modules */ + +/** + * \name SECTION: Module configuration options + * + * This section allows for the setting of module specific sizes and + * configuration options. The default values are already present in the + * relevant header files and should suffice for the regular use cases. + * + * Our advice is to enable options and change their values here + * only if you have a good reason and know the consequences. + * + * Please check the respective header file for documentation on these + * parameters (to prevent duplicate documentation). + * \{ + */ + +/* SSL options */ +#ifndef CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN + +#define MBEDTLS_SSL_MAX_CONTENT_LEN CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN /**< Maximum fragment length in bytes, determines the size of each of the two internal I/O buffers */ + +#else + +/** \def MBEDTLS_SSL_IN_CONTENT_LEN + * + * Maximum incoming fragment length in bytes. + * + * Uncomment to set the size of the inward TLS buffer independently of the + * outward buffer. + */ +#define MBEDTLS_SSL_IN_CONTENT_LEN CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN + +/** \def MBEDTLS_SSL_CID_IN_LEN_MAX + * + * The maximum length of CIDs used for incoming DTLS messages. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_DTLS_CONNECTION_ID +#define MBEDTLS_SSL_CID_IN_LEN_MAX CONFIG_MBEDTLS_SSL_CID_IN_LEN_MAX +#else +#undef MBEDTLS_SSL_CID_IN_LEN_MAX +#endif + + +/** \def MBEDTLS_SSL_CID_OUT_LEN_MAX + * + * The maximum length of CIDs used for outgoing DTLS messages. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_DTLS_CONNECTION_ID +#define MBEDTLS_SSL_CID_OUT_LEN_MAX CONFIG_MBEDTLS_SSL_CID_OUT_LEN_MAX +#else +#undef MBEDTLS_SSL_CID_OUT_LEN_MAX +#endif + +/** \def MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY + * + * This option controls the use of record plaintext padding + * in TLS 1.3 and when using the Connection ID extension in DTLS 1.2. + * + * The padding will always be chosen so that the length of the + * padded plaintext is a multiple of the value of this option. + * + * Note: A value of \c 1 means that no padding will be used + * for outgoing records. + * + * Note: On systems lacking division instructions, + * a power of two should be preferred. + * + */ +#ifdef CONFIG_MBEDTLS_SSL_CID_PADDING_GRANULARITY +#define MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY CONFIG_MBEDTLS_SSL_CID_PADDING_GRANULARITY +#else +#undef MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY +#endif + + +/** \def MBEDTLS_SSL_OUT_CONTENT_LEN + * + * Maximum outgoing fragment length in bytes. + * + * Uncomment to set the size of the outward TLS buffer independently of the + * inward buffer. + * + * It is possible to save RAM by setting a smaller outward buffer, while keeping + * the default inward 16384 byte buffer to conform to the TLS specification. + * + * The minimum required outward buffer size is determined by the handshake + * protocol's usage. Handshaking will fail if the outward buffer is too small. + * The specific size requirement depends on the configured ciphers and any + * certificate data which is sent during the handshake. + * + * For absolute minimum RAM usage, it's best to enable + * MBEDTLS_SSL_MAX_FRAGMENT_LENGTH and reduce MBEDTLS_SSL_MAX_CONTENT_LEN. This + * reduces both incoming and outgoing buffer sizes. However this is only + * guaranteed if the other end of the connection also supports the TLS + * max_fragment_len extension. Otherwise the connection may fail. + */ +#define MBEDTLS_SSL_OUT_CONTENT_LEN CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN + +#endif /* !CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN */ + +/** + * Allow SHA-1 in the default TLS configuration for TLS 1.2 handshake + * signature and ciphersuite selection. Without this build-time option, SHA-1 + * support must be activated explicitly through mbedtls_ssl_conf_sig_hashes. + * The use of SHA-1 in TLS <= 1.1 and in HMAC-SHA-1 is always allowed by + * default. At the time of writing, there is no practical attack on the use + * of SHA-1 in handshake signatures, hence this option is turned on by default + * for compatibility with existing peers. + * + * \warning SHA-1 is considered a weak message digest and its use constitutes + * a security risk. If possible, we recommend avoiding dependencies + * on it, and considering stronger message digests instead. + */ +#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE + +/** + * \def MBEDTLS_THREADING_C + * + * Enable the threading abstraction layer. + * By default mbed TLS assumes it is used in a non-threaded environment or that + * contexts are not shared between threads. If you do intend to use contexts + * between threads, you will need to enable this layer to prevent race + * conditions. See also our Knowledge Base article about threading: + * https://mbed-tls.readthedocs.io/en/latest/kb/development/thread-safety-and-multi-threading + * + * Module: library/threading.c + * + * This allows different threading implementations (self-implemented or + * provided). + * + * You will have to enable either MBEDTLS_THREADING_ALT or + * MBEDTLS_THREADING_PTHREAD. + * + * Enable this layer to allow use of mutexes within mbed TLS + */ +#ifdef CONFIG_MBEDTLS_THREADING_C +#define MBEDTLS_THREADING_C +#else +#undef MBEDTLS_THREADING_C +#endif + +/** + * \def MBEDTLS_THREADING_ALT + * + * Provide your own alternate threading implementation. + * + * Requires: MBEDTLS_THREADING_C + * + * Uncomment this to allow your own alternate threading implementation. + */ +#ifdef CONFIG_MBEDTLS_THREADING_ALT +#define MBEDTLS_THREADING_ALT +#else +#undef MBEDTLS_THREADING_ALT +#endif + +/** + * \def MBEDTLS_THREADING_PTHREAD + * + * Enable the pthread wrapper layer for the threading layer. + * + * Requires: MBEDTLS_THREADING_C + * + * Uncomment this to enable pthread mutexes. + */ +#ifdef CONFIG_MBEDTLS_THREADING_PTHREAD +#define MBEDTLS_THREADING_PTHREAD +#else +#undef MBEDTLS_THREADING_PTHREAD +#endif + +/** + * \def MBEDTLS_NIST_KW_C + * + * Enable AES key wrapping as per NIST + * + * Requires: MBEDTLS_AES_C + * + * Uncomment this to enable aes key wrap. + */ +#ifdef CONFIG_MBEDTLS_NIST_KW_C +#define MBEDTLS_NIST_KW_C +#else +#undef MBEDTLS_NIST_KW_C +#endif + +/* \} name SECTION: Module configuration options */ + +#if defined(TARGET_LIKE_MBED) +#include "mbedtls/target_config.h" +#endif + +/* + * Allow user to override any previous default. + * + * Use two macro names for that, as: + * - with yotta the prefix YOTTA_CFG_ is forced + * - without yotta is looks weird to have a YOTTA prefix. + */ +#if defined(YOTTA_CFG_MBEDTLS_USER_CONFIG_FILE) +#include YOTTA_CFG_MBEDTLS_USER_CONFIG_FILE +#elif defined(MBEDTLS_USER_CONFIG_FILE) +#include MBEDTLS_USER_CONFIG_FILE +#endif + +/* This flag makes sure that we are not using + * any functino that is deprecated by mbedtls */ +// #define MBEDTLS_DEPRECATED_REMOVED + +#include "mbedtls/compat-2.x.h" + +#endif /* ESP_CONFIG_H */ diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_debug.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_debug.h new file mode 100644 index 000000000..ecd4688f9 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_debug.h @@ -0,0 +1,59 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _ESP_DEBUG_H_ +#define _ESP_DEBUG_H_ + +#include "mbedtls/ssl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sdkconfig.h" +#ifdef CONFIG_MBEDTLS_DEBUG + +/** @brief Enable mbedTLS debug logging via the esp_log mechanism. + * + * mbedTLS internal debugging is filtered from a specified mbedTLS + * threshold level to esp_log level at runtime: + * + * - 1 - Warning + * - 2 - Info + * - 3 - Debug + * - 4 - Verbose + * + * (Note that mbedTLS debug thresholds are not always consistently used.) + * + * This function will set the esp log level for "mbedtls" to the specified mbedTLS + * threshold level that matches. However, the overall max ESP log level must be set high + * enough in menuconfig, or some messages may be filtered at compile time. + * + * @param conf mbedtls_ssl_config structure + * @param mbedTLS debug threshold, 0-4. Messages are filtered at runtime. + */ +void mbedtls_esp_enable_debug_log(mbedtls_ssl_config *conf, int threshold); + +/** @brief Disable mbedTLS debug logging via the esp_log mechanism. + * + */ +void mbedtls_esp_disable_debug_log(mbedtls_ssl_config *conf); + + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __ESP_DEBUG_H__ */ diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/gcm.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/gcm.h new file mode 100644 index 000000000..d50527d4d --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/gcm.h @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include_next "mbedtls/gcm.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MBEDTLS_GCM_ALT) && defined(MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK) + +/** + * When the MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK is defined, for non-AES GCM + * operations we need to fallback to the software function definitions of the + * mbedtls GCM layer. + * Thus in this case we need declarations for the software funtions. + * Please refer mbedtls/include/mbedtls/gcm.h for function documentations + */ + +void mbedtls_gcm_init_soft(mbedtls_gcm_context_soft *ctx); + + +int mbedtls_gcm_setkey_soft(mbedtls_gcm_context_soft *ctx, + mbedtls_cipher_id_t cipher, + const unsigned char *key, + unsigned int keybits); + +int mbedtls_gcm_starts_soft(mbedtls_gcm_context_soft *ctx, + int mode, + const unsigned char *iv, size_t iv_len); + +int mbedtls_gcm_update_ad_soft(mbedtls_gcm_context_soft *ctx, + const unsigned char *add, size_t add_len); + +int mbedtls_gcm_update_soft(mbedtls_gcm_context_soft *ctx, + const unsigned char *input, size_t input_length, + unsigned char *output, size_t output_size, + size_t *output_length); + +int mbedtls_gcm_finish_soft(mbedtls_gcm_context_soft *ctx, + unsigned char *output, size_t output_size, + size_t *output_length, + unsigned char *tag, size_t tag_len); + + +int mbedtls_gcm_crypt_and_tag_soft(mbedtls_gcm_context_soft *ctx, + int mode, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *add, + size_t add_len, + const unsigned char *input, + unsigned char *output, + size_t tag_len, + unsigned char *tag); + + +int mbedtls_gcm_auth_decrypt_soft(mbedtls_gcm_context_soft *ctx, + size_t length, + const unsigned char *iv, + size_t iv_len, + const unsigned char *add, + size_t add_len, + const unsigned char *tag, + size_t tag_len, + const unsigned char *input, + unsigned char *output); + +void mbedtls_gcm_free_soft(mbedtls_gcm_context_soft *ctx); + +#endif /* MBEDTLS_GCM_ALT && MBEDTLS_GCM_NON_AES_CIPHER_SOFT_FALLBACK*/ + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/ssl_internal.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/ssl_internal.h new file mode 100644 index 000000000..52b1a515a --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/ssl_internal.h @@ -0,0 +1,28 @@ +/** + * \file ssl_internal.h + * + * \brief Internal functions shared by the SSL modules + */ +/* + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ +#ifndef MBEDTLS_SSL_INTERNAL_H +#define MBEDTLS_SSL_INTERNAL_H +// Only for compilation + +#endif /* ssl_internal.h */ diff --git a/components/mbedtls/mbedtls_v3/port/include/md/esp_md.h b/components/mbedtls/mbedtls_v3/port/include/md/esp_md.h new file mode 100644 index 000000000..5f4d175c1 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/md/esp_md.h @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +// #include "esp_rom_md5.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_IDF_TARGET_ESP32C2 +typedef struct mbedtls_md5_context mbedtls_md5_context; +#else +typedef struct MD5Context mbedtls_md5_context; +#endif + +/** + * \brief Initialize MD5 context + * + * \param ctx MD5 context to be initialized + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +void esp_md5_init( mbedtls_md5_context *ctx ); + +/** + * \brief Clear MD5 context + * + * \param ctx MD5 context to be cleared + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +void esp_md5_free( mbedtls_md5_context *ctx ); + +/** + * \brief Clone (the state of) an MD5 context + * + * \param dst The destination context + * \param src The context to be cloned + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +void esp_md5_clone( mbedtls_md5_context *dst, const mbedtls_md5_context *src ); + +/** + * \brief MD5 context setup + * + * \param ctx context to be initialized + * + * \return 0 if successful + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +int mbedtls_md5_starts( mbedtls_md5_context *ctx ); + +/** + * \brief MD5 process buffer + * + * \param ctx MD5 context + * \param input buffer holding the data + * \param ilen length of the input data + * + * \return 0 if successful + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +int esp_md5_update( mbedtls_md5_context *ctx, const unsigned char *input, size_t ilen ); + +/** + * \brief MD5 final digest + * + * \param ctx MD5 context + * \param output MD5 checksum result + * + * \return 0 if successful + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +int esp_md5_finish( mbedtls_md5_context *ctx, unsigned char output[16] ); + +/** + * \brief MD5 process data block (internal use only) + * + * \param ctx MD5 context + * \param data buffer holding one block of data + * + * \return 0 if successful + * + * \warning MD5 is considered a weak message digest and its use + * constitutes a security risk. We recommend considering + * stronger message digests instead. + * + */ +int esp_md5_process( mbedtls_md5_context *ctx, const unsigned char data[64] ); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/md5_alt.h b/components/mbedtls/mbedtls_v3/port/include/md5_alt.h new file mode 100644 index 000000000..788c6554e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/md5_alt.h @@ -0,0 +1,35 @@ +/* + * md5_alt.h: MD5 block cipher + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +#ifndef MD5_ALT_H +#define MD5_ALT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MBEDTLS_MD5_ALT) +#include "md/esp_md.h" + +#define mbedtls_md5_init esp_md5_init +#define mbedtls_md5_update esp_md5_update +#define mbedtls_md5_finish esp_md5_finish +#define mbedtls_md5_starts esp_md5_starts + +#define mbedtls_md5_free esp_md5_free +#define mbedtls_md5_clone esp_md5_clone +#define mbedtls_internal_md5_process esp_md5_process + +#endif /* MBEDTLS_MD5_ALT */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/rsa_sign_alt.h b/components/mbedtls/mbedtls_v3/port/include/rsa_sign_alt.h new file mode 100644 index 000000000..bd299cfc3 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/rsa_sign_alt.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifndef _RSA_SIGN_ALT_H_ +#define _RSA_SIGN_ALT_H_ + +#ifdef __cpluscplus +extern "C" { +#endif + +#ifdef CONFIG_ESP_TLS_USE_DS_PERIPHERAL +#include "esp_ds/esp_rsa_sign_alt.h" +#else + +#error "DS configuration flags not activated, please enable required menuconfig flags" + +#endif + +#ifdef __cpluscplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha/sha_block.h b/components/mbedtls/mbedtls_v3/port/include/sha/sha_block.h new file mode 100644 index 000000000..d1752e772 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha/sha_block.h @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "hal/sha_types.h" + +/** @brief Low-level support functions for the hardware SHA engine + * + * @note If you're looking for a SHA API to use, try mbedtls component + * mbedtls/shaXX.h. That API supports hardware acceleration. + * + * The API in this header provides some building blocks for implementing a + * full SHA API such as the one in mbedtls, and also a basic SHA function esp_sha(). + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Calculate SHA1 or SHA2 sum of some data, using hardware SHA engine + * + * @note For more versatile SHA calculations, where data doesn't need + * to be passed all at once, try the mbedTLS mbedtls/shaX.h APIs. + * + * @note It is not necessary to lock any SHA hardware before calling + * this function, thread safety is managed internally. + * + * @param sha_type SHA algorithm to use. + * + * @param input Input data buffer. + * + * @param ilen Length of input data in bytes. + * + * @param output Buffer for output SHA digest. Output is 20 bytes for + * sha_type SHA1, 32 bytes for sha_type SHA2_256, 48 bytes for + * sha_type SHA2_384, 64 bytes for sha_type SHA2_512. + */ +void esp_sha(esp_sha_type sha_type, const unsigned char *input, size_t ilen, unsigned char *output); + +/** @brief Execute SHA block operation + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA + * algorithm. + * + * @note Call esp_sha_acquire_hardware() before calling this + * function. + * + * @param sha_type SHA algorithm to use. + * + * @param data_block Pointer to the input data. Block size is + * determined by algorithm (SHA1/SHA2_256 = 64 bytes, + * SHA2_384/SHA2_512 = 128 bytes) + * + * @param is_first_block If this parameter is true, the SHA state will + * be initialised (with the initial state of the given SHA algorithm) + * before the block is calculated. If false, the existing state of the + * SHA engine will be used. + * + */ +void esp_sha_block(esp_sha_type sha_type, const void *data_block, bool is_first_block); + +/** + * @brief Read out the current state of the SHA digest + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA algorithm. + * + * @note Call esp_sha_aquire_hardware() before calling this + * function. + * + * If the SHA suffix padding block has been executed already, the + * value that is read is the SHA digest. + * Otherwise, the value that is read is an interim SHA state. + * + * @param sha_type SHA algorithm in use. + * @param digest_state Pointer to a memory buffer to hold the SHA state. Size + * is 20 bytes (SHA1), 32 bytes (SHA2_256), or 64 bytes (SHA2_384, SHA2_512). + */ +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state); + +/** + * @brief Set the current state of the SHA digest + * + * @note Call esp_sha_aquire_hardware() before calling this + * function. + * + * @param sha_type SHA algorithm in use. + * @param digest_state Digest state to write to hardware + */ +void esp_sha_write_digest_state(esp_sha_type sha_type, void *digest_state); + + +/** + * @brief Enables the SHA peripheral and takes the lock. + */ +void esp_sha_acquire_hardware(void); + +/** + * @brief Disables the SHA peripheral and releases the lock. + */ +void esp_sha_release_hardware(void); + + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha/sha_dma.h b/components/mbedtls/mbedtls_v3/port/include/sha/sha_dma.h new file mode 100644 index 000000000..af12f23a5 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha/sha_dma.h @@ -0,0 +1,159 @@ +// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "hal/sha_types.h" + +/** @brief Low-level support functions for the hardware SHA engine using DMA + * + * @note If you're looking for a SHA API to use, try mbedtls component + * mbedtls/shaXX.h. That API supports hardware acceleration. + * + * The API in this header provides some building blocks for implementing a + * full SHA API such as the one in mbedtls, and also a basic SHA function esp_sha(). + * + * Some technical details about the hardware SHA engine: + * + * - The crypto DMA is shared between the SHA and AES engine, it is not + * possible for them to run calcalutions in parallel. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Calculate SHA1 or SHA2 sum of some data, using hardware SHA engine + * + * @note For more versatile SHA calculations, where data doesn't need + * to be passed all at once, try the mbedTLS mbedtls/shaX.h APIs. + * + * @note It is not necessary to lock any SHA hardware before calling + * this function, thread safety is managed internally. + * + * @param sha_type SHA algorithm to use. + * + * @param input Input data buffer. + * + * @param ilen Length of input data in bytes. + * + * @param output Buffer for output SHA digest. Output is 20 bytes for + * sha_type SHA1, 32 bytes for sha_type SHA2_256, 48 bytes for + * sha_type SHA2_384, 64 bytes for sha_type SHA2_512. + */ +void esp_sha(esp_sha_type sha_type, const unsigned char *input, size_t ilen, unsigned char *output); + +/** @brief Execute SHA block operation using DMA + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA + * algorithm. + * + * @note Call esp_sha_aquire_hardware() before calling this + * function. + * + * @param sha_type SHA algorithm to use. + * + * @param input Pointer to the input data. Block size is + * determined by algorithm (SHA1/SHA2_256 = 64 bytes, + * SHA2_384/SHA2_512 = 128 bytes) + * + * @param ilen length of input data should be multiple of block length. + * + * @param buf Pointer to blocks of data that will be prepended + * to data_block before hashing. Useful when there is two sources of + * data that need to be efficiently calculated in a single SHA DMA + * operation. + * + * @param buf_len length of buf data should be multiple of block length. + * Should not be longer than the maximum amount of bytes in a single block + * (128 bytes) + * + * @param is_first_block If this parameter is true, the SHA state will + * be initialised (with the initial state of the given SHA algorithm) + * before the block is calculated. If false, the existing state of the + * SHA engine will be used. + * + * @param t The number of bits for the SHA512/t hash function, with + * output truncated to t bits. Used for calculating the inital hash. + * t is any positive integer between 1 and 512, except 384. + * + * @return 0 if successful + */ +int esp_sha_dma(esp_sha_type sha_type, const void *input, uint32_t ilen, + const void *buf, uint32_t buf_len, bool is_first_block); + +/** + * @brief Read out the current state of the SHA digest + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA algorithm. + * + * @note Call esp_sha_aquire_hardware() before calling this + * function. + * + * If the SHA suffix padding block has been executed already, the + * value that is read is the SHA digest. + * Otherwise, the value that is read is an interim SHA state. + * + * @param sha_type SHA algorithm in use. + * @param digest_state Pointer to a memory buffer to hold the SHA state. Size + * is 20 bytes (SHA1), 32 bytes (SHA2_256), or 64 bytes (SHA2_384, SHA2_512). + */ +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state); + +/** + * @brief Set the current state of the SHA digest + * + * @note Call esp_sha_aquire_hardware() before calling this + * function. + * + * When resuming a + * + * @param sha_type SHA algorithm in use. + * @param digest_state + */ +void esp_sha_write_digest_state(esp_sha_type sha_type, void *digest_state); + + +/** + * @brief Enables the SHA and crypto DMA peripheral and takes the + * locks for both of them. + */ +void esp_sha_acquire_hardware(void); + +/** + * @brief Disables the SHA and crypto DMA peripheral and releases the + * locks. + */ +void esp_sha_release_hardware(void); + +/** + * @brief Sets the initial hash value for SHA512/t. + * + * @note Is generated according to the algorithm described in the TRM, + * chapter SHA-Accelerator + * + * @note The engine must be locked until the value is used for an operation + * or read out. Else you risk another operation overwriting it. + * + * @param t + * + * @return 0 if successful + */ +int esp_sha_512_t_init_hash(uint16_t t); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha/sha_parallel_engine.h b/components/mbedtls/mbedtls_v3/port/include/sha/sha_parallel_engine.h new file mode 100644 index 000000000..cf6f0607d --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha/sha_parallel_engine.h @@ -0,0 +1,205 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "hal/sha_types.h" +#include "esp_types.h" + +/** @brief Low-level support functions for the hardware SHA engine + * + * @note If you're looking for a SHA API to use, try mbedtls component + * mbedtls/shaXX.h. That API supports hardware acceleration. + * + * The API in this header provides some building blocks for implementing a + * full SHA API such as the one in mbedtls, and also a basic SHA function esp_sha(). + * + * Some technical details about the hardware SHA engine: + * + * - SHA accelerator engine calculates one digest at a time, per SHA + * algorithm type. It initialises and maintains the digest state + * internally. It is possible to read out an in-progress SHA digest + * state, but it is not possible to restore a SHA digest state + * into the engine. + * + * - The memory block SHA_TEXT_BASE is shared between all SHA digest + * engines, so all engines must be idle before this memory block is + * modified. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Calculate SHA1 or SHA2 sum of some data, using hardware SHA engine + * + * @note For more versatile SHA calculations, where data doesn't need + * to be passed all at once, try the mbedTLS mbedtls/shaX.h APIs. The + * hardware-accelerated mbedTLS implementation is also faster when + * hashing large amounts of data. + * + * @note It is not necessary to lock any SHA hardware before calling + * this function, thread safety is managed internally. + * + * @note If a TLS connection is open then this function may block + * indefinitely waiting for a SHA engine to become available. Use the + * mbedTLS SHA API to avoid this problem. + * + * @param sha_type SHA algorithm to use. + * + * @param input Input data buffer. + * + * @param ilen Length of input data in bytes. + * + * @param output Buffer for output SHA digest. Output is 20 bytes for + * sha_type SHA1, 32 bytes for sha_type SHA2_256, 48 bytes for + * sha_type SHA2_384, 64 bytes for sha_type SHA2_512. + */ +void esp_sha(esp_sha_type sha_type, const unsigned char *input, size_t ilen, unsigned char *output); + +/* @brief Begin to execute a single SHA block operation + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA + * algorithm. + * + * @note Call esp_sha_try_lock_engine() before calling this + * function. Do not call esp_sha_lock_memory_block() beforehand, this + * is done inside the function. + * + * @param sha_type SHA algorithm to use. + * + * @param data_block Pointer to block of data. Block size is + * determined by algorithm (SHA1/SHA2_256 = 64 bytes, + * SHA2_384/SHA2_512 = 128 bytes) + * + * @param is_first_block If this parameter is true, the SHA state will + * be initialised (with the initial state of the given SHA algorithm) + * before the block is calculated. If false, the existing state of the + * SHA engine will be used. + * + * @return As a performance optimisation, this function returns before + * the SHA block operation is complete. Both this function and + * esp_sha_read_state() will automatically wait for any previous + * operation to complete before they begin. If using the SHA registers + * directly in another way, call esp_sha_wait_idle() after calling this + * function but before accessing the SHA registers. + */ +void esp_sha_block(esp_sha_type sha_type, const void *data_block, bool is_first_block); + +/** @brief Read out the current state of the SHA digest loaded in the engine. + * + * @note This is a piece of a SHA algorithm, rather than an entire SHA algorithm. + * + * @note Call esp_sha_try_lock_engine() before calling this + * function. Do not call esp_sha_lock_memory_block() beforehand, this + * is done inside the function. + * + * If the SHA suffix padding block has been executed already, the + * value that is read is the SHA digest (in big endian + * format). Otherwise, the value that is read is an interim SHA state. + * + * @note If sha_type is SHA2_384, only 48 bytes of state will be read. + * This is enough for the final SHA2_384 digest, but if you want the + * interim SHA-384 state (to continue digesting) then pass SHA2_512 instead. + * + * @param sha_type SHA algorithm in use. + * + * @param state Pointer to a memory buffer to hold the SHA state. Size + * is 20 bytes (SHA1), 32 bytes (SHA2_256), 48 bytes (SHA2_384) or 64 bytes (SHA2_512). + * + */ +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state); + +/** + * @brief Obtain exclusive access to a particular SHA engine + * + * @param sha_type Type of SHA engine to use. + * + * Blocks until engine is available. Note: Can block indefinitely + * while a TLS connection is open, suggest using + * esp_sha_try_lock_engine() and failing over to software SHA. + */ +void esp_sha_lock_engine(esp_sha_type sha_type); + +/** + * @brief Try and obtain exclusive access to a particular SHA engine + * + * @param sha_type Type of SHA engine to use. + * + * @return Returns true if the SHA engine is locked for exclusive + * use. Call esp_sha_unlock_sha_engine() when done. Returns false if + * the SHA engine is already in use, caller should use software SHA + * algorithm for this digest. + */ +bool esp_sha_try_lock_engine(esp_sha_type sha_type); + +/** + * @brief Unlock an engine previously locked with esp_sha_lock_engine() or esp_sha_try_lock_engine() + * + * @param sha_type Type of engine to release. + */ +void esp_sha_unlock_engine(esp_sha_type sha_type); + +/** + * @brief Acquire exclusive access to the SHA shared memory block at SHA_TEXT_BASE + * + * This memory block is shared across all the SHA algorithm types. + * + * Caller should have already locked a SHA engine before calling this function. + * + * Note that it is possible to obtain exclusive access to the memory block even + * while it is in use by the SHA engine. Caller should use esp_sha_wait_idle() + * to ensure the SHA engine is not reading from the memory block in hardware. + * + * @note This function enters a critical section. Do not block while holding this lock. + * + * @note You do not need to lock the memory block before calling esp_sha_block() or esp_sha_read_digest_state(), these functions handle memory block locking internally. + * + * Call esp_sha_unlock_memory_block() when done. + */ +void esp_sha_lock_memory_block(void); + +/** + * @brief Release exclusive access to the SHA register memory block at SHA_TEXT_BASE + * + * Caller should have already locked a SHA engine before calling this function. + * + * This function releases the critical section entered by esp_sha_lock_memory_block(). + * + * Call following esp_sha_lock_memory_block(). + */ +void esp_sha_unlock_memory_block(void); + +/** @brief Wait for the SHA engine to finish any current operation + * + * @note This function does not ensure exclusive access to any SHA + * engine. Caller should use esp_sha_try_lock_engine() and + * esp_sha_lock_memory_block() as required. + * + * @note Functions declared in this header file wait for SHA engine + * completion automatically, so you don't need to use this API for + * these. However if accessing SHA registers directly, you will need + * to call this before accessing SHA registers if using the + * esp_sha_block() function. + * + * @note This function busy-waits, so wastes CPU resources. + * Best to delay calling until you are about to need it. + * + */ +void esp_sha_wait_idle(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha1_alt.h b/components/mbedtls/mbedtls_v3/port/include/sha1_alt.h new file mode 100644 index 000000000..26039378b --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha1_alt.h @@ -0,0 +1,98 @@ +/* + * SHA-1 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _SHA1_ALT_H_ +#define _SHA1_ALT_H_ + +#if defined(MBEDTLS_SHA1_ALT) + +#include "hal/sha_types.h" +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_SHA_SUPPORT_PARALLEL_ENG + +typedef enum { + ESP_MBEDTLS_SHA1_UNUSED, /* first block hasn't been processed yet */ + ESP_MBEDTLS_SHA1_HARDWARE, /* using hardware SHA engine */ + ESP_MBEDTLS_SHA1_SOFTWARE, /* using software SHA */ +} esp_mbedtls_sha1_mode; + +/** + * \brief SHA-1 context structure + */ +typedef struct { + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[5]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + esp_mbedtls_sha1_mode mode; +} mbedtls_sha1_context; + +/** + * \brief Set the SHA-1 mode for a mbedtls_sha1_context. + * + * \param ctx The SHA-1 context structure. + * \param mode The SHA-1 mode to be set. It can be one of the following: + * - ESP_MBEDTLS_SHA1_UNUSED: Indicates that the first block hasn't been processed yet. + * - ESP_MBEDTLS_SHA1_HARDWARE: Specifies the use of hardware SHA engine for SHA-1 calculations. + * - ESP_MBEDTLS_SHA1_SOFTWARE: Specifies the use of software-based SHA-1 calculations. + * + * \return None. + */ +static inline void esp_mbedtls_set_sha1_mode(mbedtls_sha1_context *ctx, esp_mbedtls_sha1_mode mode) +{ + if (ctx) { + ctx->mode = mode; + } +} + +#elif SOC_SHA_SUPPORT_DMA || SOC_SHA_SUPPORT_RESUME + +typedef enum { + ESP_SHA1_STATE_INIT, + ESP_SHA1_STATE_IN_PROCESS +} esp_sha1_state; + +/** + * \brief SHA-1 context structure + */ +typedef struct { + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[5]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + int first_block; /*!< if first then true else false */ + esp_sha_type mode; + esp_sha1_state sha_state; +} mbedtls_sha1_context; + +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha256_alt.h b/components/mbedtls/mbedtls_v3/port/include/sha256_alt.h new file mode 100644 index 000000000..641f5e893 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha256_alt.h @@ -0,0 +1,79 @@ +/* + * SHA-256 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _SHA256_ALT_H_ +#define _SHA256_ALT_H_ + +#if defined(MBEDTLS_SHA256_ALT) + +#include "hal/sha_types.h" +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if SOC_SHA_SUPPORT_PARALLEL_ENG +typedef enum { + ESP_MBEDTLS_SHA256_UNUSED, /* first block hasn't been processed yet */ + ESP_MBEDTLS_SHA256_HARDWARE, /* using hardware SHA engine */ + ESP_MBEDTLS_SHA256_SOFTWARE, /* using software SHA */ +} esp_mbedtls_sha256_mode; + +/** + * \brief SHA-256 context structure + */ +typedef struct { + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[8]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + int is224; /*!< 0 => SHA-256, else SHA-224 */ + esp_mbedtls_sha256_mode mode; +} mbedtls_sha256_context; + +#elif SOC_SHA_SUPPORT_DMA || SOC_SHA_SUPPORT_RESUME +typedef enum { + ESP_SHA256_STATE_INIT, + ESP_SHA256_STATE_IN_PROCESS +} esp_sha256_state; + +/** + * \brief SHA-256 context structure + */ +typedef struct { + uint32_t total[2]; /*!< number of bytes processed */ + uint32_t state[8]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + int first_block; /*!< if first then true, else false */ + esp_sha_type mode; + esp_sha256_state sha_state; +} mbedtls_sha256_context; + +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/include/sha512_alt.h b/components/mbedtls/mbedtls_v3/port/include/sha512_alt.h new file mode 100644 index 000000000..d0de4623e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/include/sha512_alt.h @@ -0,0 +1,97 @@ +/* + * SHA-512 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE LTD + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _SHA512_ALT_H_ +#define _SHA512_ALT_H_ + +#if defined(MBEDTLS_SHA512_ALT) + +#include "hal/sha_types.h" +#include "soc/soc_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#if SOC_SHA_SUPPORT_PARALLEL_ENG + +typedef enum { + ESP_MBEDTLS_SHA512_UNUSED, /* first block hasn't been processed yet */ + ESP_MBEDTLS_SHA512_HARDWARE, /* using hardware SHA engine */ + ESP_MBEDTLS_SHA512_SOFTWARE, /* using software SHA */ +} esp_mbedtls_sha512_mode; + +/** + * \brief SHA-512 context structure + */ +typedef struct { + uint64_t total[2]; /*!< number of bytes processed */ + uint64_t state[8]; /*!< intermediate digest state */ + unsigned char buffer[128]; /*!< data block being processed */ + int is384; /*!< 0 => SHA-512, else SHA-384 */ + esp_mbedtls_sha512_mode mode; +} mbedtls_sha512_context; + +#elif SOC_SHA_SUPPORT_DMA || SOC_SHA_SUPPORT_RESUME + +typedef enum { + ESP_SHA512_STATE_INIT, + ESP_SHA512_STATE_IN_PROCESS +} esp_sha512_state; + +/** + * \brief SHA-512 context structure + */ +typedef struct { + uint64_t total[2]; /*!< number of bytes processed */ + uint64_t state[8]; /*!< intermediate digest state */ + unsigned char buffer[128]; /*!< data block being processed */ + int first_block; + esp_sha_type mode; + uint32_t t_val; /*!< t_val for 512/t mode */ + esp_sha512_state sha_state; +} mbedtls_sha512_context; + +/** + * @brief Sets the specfic algorithm for SHA512 + * + * @param ctx The mbedtls sha512 context + * + * @param type The mode, used for setting SHA2_512224 and SHA2_512256: + * + */ +void esp_sha512_set_mode(mbedtls_sha512_context *ctx, esp_sha_type type); + +/* For SHA512/t mode the intial hash value will depend on t */ +void esp_sha512_set_t( mbedtls_sha512_context *ctx, uint16_t t_val); + + +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mbedtls/mbedtls_v3/port/mbedtls_debug.c b/components/mbedtls/mbedtls_v3/port/mbedtls_debug.c new file mode 100644 index 000000000..23f7eab10 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/mbedtls_debug.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "esp_log.h" +#include "mbedtls/platform.h" +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/esp_debug.h" + +#ifdef CONFIG_MBEDTLS_DEBUG +static const char *TAG = "mbedtls"; + +static void mbedtls_esp_debug(void *ctx, int level, + const char *file, int line, + const char *str); + +void mbedtls_esp_enable_debug_log(mbedtls_ssl_config *conf, int threshold) +{ + esp_log_level_t level = ESP_LOG_NONE; + mbedtls_debug_set_threshold(threshold); + mbedtls_ssl_conf_dbg(conf, mbedtls_esp_debug, NULL); + switch(threshold) { + case 1: + level = ESP_LOG_WARN; + break; + case 2: + level = ESP_LOG_INFO; + break; + case 3: + level = ESP_LOG_DEBUG; + break; + case 4: + level = ESP_LOG_VERBOSE; + break; + } + esp_log_level_set(TAG, level); +} + +void mbedtls_esp_disable_debug_log(mbedtls_ssl_config *conf) +{ + mbedtls_ssl_conf_dbg(conf, NULL, NULL); +} + + +/* Default mbedtls debug function that translates mbedTLS debug output + to ESP_LOGx debug output. +*/ +static void mbedtls_esp_debug(void *ctx, int level, + const char *file, int line, + const char *str) +{ + char *file_sep; + + /* Shorten 'file' from the whole file path to just the filename + + This is a bit wasteful because the macros are compiled in with + the full _FILE_ path in each case. + */ + file_sep = rindex(file, '/'); + if(file_sep) + file = file_sep+1; + + switch(level) { + case 1: + ESP_LOGW(TAG, "%s:%d %s", file, line, str); + break; + case 2: + ESP_LOGI(TAG, "%s:%d %s", file, line, str); + break; + case 3: + ESP_LOGD(TAG, "%s:%d %s", file, line, str); + break; + case 4: + ESP_LOGV(TAG, "%s:%d %s", file, line, str); + break; + default: + ESP_LOGE(TAG, "Unexpected log level %d: %s", level, str); + break; + } +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.c b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.c new file mode 100644 index 000000000..d29d03e96 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.c @@ -0,0 +1,468 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if !defined(MBEDTLS_CONFIG_FILE) +#include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif + +#include "soc/chip_revision.h" +#include "hal/efuse_hal.h" +#include "mbedtls/platform.h" +#include "mbedtls_rom_osi.h" + +void mbedtls_rom_osi_functions_init(void); + +static void mbedtls_rom_mutex_init( mbedtls_threading_mutex_t *mutex ) +{ + if (mutex == NULL) { + return; + } + +#if defined(MBEDTLS_THREADING_ALT) + mutex->mutex = xSemaphoreCreateMutex(); + assert(mutex->mutex != NULL); +#else + mbedtls_mutex_init(mutex); +#endif +} + +static void mbedtls_rom_mutex_free( mbedtls_threading_mutex_t *mutex ) +{ + if (mutex == NULL) { + return; + } + +#if defined(MBEDTLS_THREADING_ALT) + vSemaphoreDelete(mutex->mutex); +#else + mbedtls_mutex_free(mutex); +#endif +} + +static int mbedtls_rom_mutex_lock( mbedtls_threading_mutex_t *mutex ) +{ + if (mutex == NULL) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + +#if defined(MBEDTLS_THREADING_ALT) + if (xSemaphoreTake(mutex->mutex, portMAX_DELAY) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + return 0; +#else + return mbedtls_mutex_lock(mutex); +#endif +} + +static int mbedtls_rom_mutex_unlock( mbedtls_threading_mutex_t *mutex ) +{ + if (mutex == NULL) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + +#if defined(MBEDTLS_THREADING_ALT) + if (xSemaphoreGive(mutex->mutex) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + return 0; +#else + return mbedtls_mutex_unlock(mutex); +#endif +} + +/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ +static const mbedtls_rom_funcs_t mbedtls_rom_funcs_table = { + /* Fill the ROM functions into mbedtls rom function table. */ + /* aes module */ + ._rom_mbedtls_aes_init = mbedtls_aes_init, + ._rom_mbedtls_aes_free = mbedtls_aes_free, + ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, + ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, + ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, + ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, + ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, + ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, + /* asn1 module */ + ._rom_mbedtls_asn1_get_len = mbedtls_asn1_get_len, + ._rom_mbedtls_asn1_get_tag = mbedtls_asn1_get_tag, + ._rom_mbedtls_asn1_get_bool = mbedtls_asn1_get_bool, + ._rom_mbedtls_asn1_get_int = mbedtls_asn1_get_int, + ._rom_mbedtls_asn1_get_bitstring = mbedtls_asn1_get_bitstring, + ._rom_mbedtls_asn1_get_bitstring_null = mbedtls_asn1_get_bitstring_null, + ._rom_mbedtls_asn1_get_sequence_of = mbedtls_asn1_get_sequence_of, + ._rom_mbedtls_asn1_get_mpi = mbedtls_asn1_get_mpi, + ._rom_mbedtls_asn1_get_alg = mbedtls_asn1_get_alg, + ._rom_mbedtls_asn1_get_alg_null = mbedtls_asn1_get_alg_null, + ._rom_mbedtls_asn1_write_len = mbedtls_asn1_write_len, + ._rom_mbedtls_asn1_write_tag = mbedtls_asn1_write_tag, + ._rom_mbedtls_asn1_write_mpi = mbedtls_asn1_write_mpi, + /* base64 module */ + ._rom_mbedtls_base64_decode = mbedtls_base64_decode, + /* bignum module */ + ._rom_mbedtls_mpi_init = mbedtls_mpi_init, + ._rom_mbedtls_mpi_free = mbedtls_mpi_free, + ._rom_mbedtls_mpi_grow = mbedtls_mpi_grow, + ._rom_mbedtls_mpi_shrink = mbedtls_mpi_shrink, + ._rom_mbedtls_mpi_copy = mbedtls_mpi_copy, + ._rom_mbedtls_mpi_safe_cond_assign = mbedtls_mpi_safe_cond_assign, + ._rom_mbedtls_mpi_safe_cond_swap = mbedtls_mpi_safe_cond_swap, + ._rom_mbedtls_mpi_lset = mbedtls_mpi_lset, + ._rom_mbedtls_mpi_get_bit = mbedtls_mpi_get_bit, + ._rom_mbedtls_mpi_set_bit = mbedtls_mpi_set_bit, + ._rom_mbedtls_mpi_lsb = mbedtls_mpi_lsb, + ._rom_mbedtls_mpi_bitlen = mbedtls_mpi_bitlen, + ._rom_mbedtls_mpi_size = mbedtls_mpi_size, + ._rom_mbedtls_mpi_read_binary = mbedtls_mpi_read_binary, + ._rom_mbedtls_mpi_write_binary = mbedtls_mpi_write_binary, + ._rom_mbedtls_mpi_shift_l = mbedtls_mpi_shift_l, + ._rom_mbedtls_mpi_shift_r = mbedtls_mpi_shift_r, + ._rom_mbedtls_mpi_cmp_abs = mbedtls_mpi_cmp_abs, + ._rom_mbedtls_mpi_cmp_mpi = mbedtls_mpi_cmp_mpi, + ._rom_mbedtls_mpi_lt_mpi_ct = mbedtls_mpi_lt_mpi_ct, + ._rom_mbedtls_mpi_cmp_int = mbedtls_mpi_cmp_int, + ._rom_mbedtls_mpi_add_abs = mbedtls_mpi_add_abs, + ._rom_mbedtls_mpi_sub_abs = mbedtls_mpi_sub_abs, + ._rom_mbedtls_mpi_add_mpi = mbedtls_mpi_add_mpi, + ._rom_mbedtls_mpi_sub_mpi = mbedtls_mpi_sub_mpi, + ._rom_mbedtls_mpi_add_int = mbedtls_mpi_add_int, + ._rom_mbedtls_mpi_sub_int = mbedtls_mpi_sub_int, + ._rom_mbedtls_mpi_mul_mpi = mbedtls_mpi_mul_mpi, + ._rom_mbedtls_mpi_mul_int = mbedtls_mpi_mul_int, + ._rom_mbedtls_mpi_div_mpi = mbedtls_mpi_div_mpi, + ._rom_mbedtls_mpi_div_int = mbedtls_mpi_div_int, + ._rom_mbedtls_mpi_mod_mpi = mbedtls_mpi_mod_mpi, + ._rom_mbedtls_mpi_mod_int = mbedtls_mpi_mod_int, + ._rom_mbedtls_mpi_exp_mod = mbedtls_mpi_exp_mod, + ._rom_mbedtls_mpi_fill_random = mbedtls_mpi_fill_random, + ._rom_mbedtls_mpi_gcd = mbedtls_mpi_gcd, + ._rom_mbedtls_mpi_inv_mod = mbedtls_mpi_inv_mod, + ._rom_mbedtls_mpi_is_prime_ext = mbedtls_mpi_is_prime_ext, + /* ccm module */ + ._rom_mbedtls_ccm_star_encrypt_and_tag = mbedtls_ccm_star_encrypt_and_tag, + ._rom_mbedtls_ccm_star_auth_decrypt = mbedtls_ccm_star_auth_decrypt, + /* cipher module */ + ._rom_mbedtls_cipher_init = mbedtls_cipher_init, + ._rom_mbedtls_cipher_set_padding_mode = mbedtls_cipher_set_padding_mode, + ._rom_mbedtls_cipher_reset = mbedtls_cipher_reset, + ._rom_mbedtls_cipher_finish = mbedtls_cipher_finish, + ._rom_mbedtls_cipher_crypt = mbedtls_cipher_crypt, + ._rom_mbedtls_cipher_cmac_starts = mbedtls_cipher_cmac_starts, + ._rom_mbedtls_cipher_cmac_update = mbedtls_cipher_cmac_update, + ._rom_mbedtls_cipher_cmac_finish = mbedtls_cipher_cmac_finish, + /* ctr drbg module */ + ._rom_mbedtls_ctr_drbg_init = mbedtls_ctr_drbg_init, + ._rom_mbedtls_ctr_drbg_seed = mbedtls_ctr_drbg_seed, + ._rom_mbedtls_ctr_drbg_free = mbedtls_ctr_drbg_free, + ._rom_mbedtls_ctr_drbg_reseed = mbedtls_ctr_drbg_reseed, + ._rom_mbedtls_ctr_drbg_random_with_add = mbedtls_ctr_drbg_random_with_add, + ._rom_mbedtls_ctr_drbg_random = mbedtls_ctr_drbg_random, + /* sha1 module */ + ._rom_mbedtls_sha1_init = mbedtls_sha1_init, + ._rom_mbedtls_sha1_free = mbedtls_sha1_free, + ._rom_mbedtls_sha1_clone = mbedtls_sha1_clone, + ._rom_mbedtls_sha1_starts = mbedtls_sha1_starts, + ._rom_mbedtls_sha1_finish = mbedtls_sha1_finish, + /* sha256 module */ + ._rom_mbedtls_sha256_init = mbedtls_sha256_init, + ._rom_mbedtls_sha256_free = mbedtls_sha256_free, + ._rom_mbedtls_sha256_clone = mbedtls_sha256_clone, + ._rom_mbedtls_sha256_starts = mbedtls_sha256_starts, + ._rom_mbedtls_sha256_finish = mbedtls_sha256_finish, + ._rom_mbedtls_sha256 = mbedtls_sha256, + /* sha512 module */ + ._rom_mbedtls_sha512_init = mbedtls_sha512_init, + ._rom_mbedtls_sha512_free = mbedtls_sha512_free, + ._rom_mbedtls_sha512_clone = mbedtls_sha512_clone, + ._rom_mbedtls_sha512_starts = mbedtls_sha512_starts, + ._rom_mbedtls_sha512_update = mbedtls_sha512_update, + ._rom_mbedtls_sha512_finish = mbedtls_sha512_finish, + ._rom_mbedtls_internal_sha512_process = mbedtls_internal_sha512_process, + ._rom_mbedtls_sha512 = mbedtls_sha512, + + /* Fill the platform functions into mbedtls rom function table. */ + ._mbedtls_mutex_init = mbedtls_rom_mutex_init, + ._mbedtls_mutex_free = mbedtls_rom_mutex_free, + ._mbedtls_mutex_lock = mbedtls_rom_mutex_lock, + ._mbedtls_mutex_unlock = mbedtls_rom_mutex_unlock, + ._mbedtls_calloc = MBEDTLS_PLATFORM_STD_CALLOC, + ._mbedtls_free = MBEDTLS_PLATFORM_STD_FREE, + + /* Fill the SHA functions into mbedtls rom function table, since these functions are not exported in the ROM interface. */ + ._mbedtls_sha1_update = mbedtls_sha1_update, + ._mbedtls_internal_sha1_process = mbedtls_internal_sha1_process, + ._mbedtls_sha256_update = mbedtls_sha256_update, + ._mbedtls_internal_sha256_process = mbedtls_internal_sha256_process, +}; + +/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ +static const mbedtls_rom_eco4_funcs_t mbedtls_rom_eco4_funcs_table = { + /* Fill the ROM functions into mbedtls rom function table. */ + /* aes module */ + ._rom_mbedtls_aes_init = mbedtls_aes_init, + ._rom_mbedtls_aes_free = mbedtls_aes_free, + ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, + ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, + ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, + ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, + ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, + ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, + /* asn1 module */ + ._rom_mbedtls_asn1_get_len = mbedtls_asn1_get_len, + ._rom_mbedtls_asn1_get_tag = mbedtls_asn1_get_tag, + ._rom_mbedtls_asn1_get_bool = mbedtls_asn1_get_bool, + ._rom_mbedtls_asn1_get_int = mbedtls_asn1_get_int, + ._rom_mbedtls_asn1_get_bitstring = mbedtls_asn1_get_bitstring, + ._rom_mbedtls_asn1_get_bitstring_null = mbedtls_asn1_get_bitstring_null, + ._rom_mbedtls_asn1_get_sequence_of = mbedtls_asn1_get_sequence_of, + ._rom_mbedtls_asn1_get_mpi = mbedtls_asn1_get_mpi, + ._rom_mbedtls_asn1_get_alg = mbedtls_asn1_get_alg, + ._rom_mbedtls_asn1_get_alg_null = mbedtls_asn1_get_alg_null, + ._rom_mbedtls_asn1_write_len = mbedtls_asn1_write_len, + ._rom_mbedtls_asn1_write_tag = mbedtls_asn1_write_tag, + ._rom_mbedtls_asn1_write_mpi = mbedtls_asn1_write_mpi, + /* base64 module */ + ._rom_mbedtls_base64_decode = mbedtls_base64_decode, + /* bignum module */ + ._rom_mbedtls_mpi_init = mbedtls_mpi_init, + ._rom_mbedtls_mpi_free = mbedtls_mpi_free, + ._rom_mbedtls_mpi_grow = mbedtls_mpi_grow, + ._rom_mbedtls_mpi_shrink = mbedtls_mpi_shrink, + ._rom_mbedtls_mpi_copy = mbedtls_mpi_copy, + ._rom_mbedtls_mpi_safe_cond_assign = mbedtls_mpi_safe_cond_assign, + ._rom_mbedtls_mpi_safe_cond_swap = mbedtls_mpi_safe_cond_swap, + ._rom_mbedtls_mpi_lset = mbedtls_mpi_lset, + ._rom_mbedtls_mpi_get_bit = mbedtls_mpi_get_bit, + ._rom_mbedtls_mpi_set_bit = mbedtls_mpi_set_bit, + ._rom_mbedtls_mpi_lsb = mbedtls_mpi_lsb, + ._rom_mbedtls_mpi_bitlen = mbedtls_mpi_bitlen, + ._rom_mbedtls_mpi_size = mbedtls_mpi_size, + ._rom_mbedtls_mpi_read_binary = mbedtls_mpi_read_binary, + ._rom_mbedtls_mpi_write_binary = mbedtls_mpi_write_binary, + ._rom_mbedtls_mpi_shift_l = mbedtls_mpi_shift_l, + ._rom_mbedtls_mpi_shift_r = mbedtls_mpi_shift_r, + ._rom_mbedtls_mpi_cmp_abs = mbedtls_mpi_cmp_abs, + ._rom_mbedtls_mpi_cmp_mpi = mbedtls_mpi_cmp_mpi, + ._rom_mbedtls_mpi_lt_mpi_ct = mbedtls_mpi_lt_mpi_ct, + ._rom_mbedtls_mpi_cmp_int = mbedtls_mpi_cmp_int, + ._rom_mbedtls_mpi_add_abs = mbedtls_mpi_add_abs, + ._rom_mbedtls_mpi_sub_abs = mbedtls_mpi_sub_abs, + ._rom_mbedtls_mpi_add_mpi = mbedtls_mpi_add_mpi, + ._rom_mbedtls_mpi_sub_mpi = mbedtls_mpi_sub_mpi, + ._rom_mbedtls_mpi_add_int = mbedtls_mpi_add_int, + ._rom_mbedtls_mpi_sub_int = mbedtls_mpi_sub_int, + ._rom_mbedtls_mpi_mul_mpi = mbedtls_mpi_mul_mpi, + ._rom_mbedtls_mpi_mul_int = mbedtls_mpi_mul_int, + ._rom_mbedtls_mpi_div_mpi = mbedtls_mpi_div_mpi, + ._rom_mbedtls_mpi_div_int = mbedtls_mpi_div_int, + ._rom_mbedtls_mpi_mod_mpi = mbedtls_mpi_mod_mpi, + ._rom_mbedtls_mpi_mod_int = mbedtls_mpi_mod_int, + ._rom_mbedtls_mpi_exp_mod = mbedtls_mpi_exp_mod, + ._rom_mbedtls_mpi_fill_random = mbedtls_mpi_fill_random, + ._rom_mbedtls_mpi_gcd = mbedtls_mpi_gcd, + ._rom_mbedtls_mpi_inv_mod = mbedtls_mpi_inv_mod, + ._rom_mbedtls_mpi_is_prime_ext = mbedtls_mpi_is_prime_ext, + /* ccm module */ + ._rom_mbedtls_ccm_star_encrypt_and_tag = mbedtls_ccm_star_encrypt_and_tag, + ._rom_mbedtls_ccm_star_auth_decrypt = mbedtls_ccm_star_auth_decrypt, + /* cipher module */ + ._rom_mbedtls_cipher_init = mbedtls_cipher_init, + ._rom_mbedtls_cipher_set_padding_mode = mbedtls_cipher_set_padding_mode, + ._rom_mbedtls_cipher_reset = mbedtls_cipher_reset, + ._rom_mbedtls_cipher_finish = mbedtls_cipher_finish, + ._rom_mbedtls_cipher_crypt = mbedtls_cipher_crypt, + ._rom_mbedtls_cipher_cmac_starts = mbedtls_cipher_cmac_starts, + ._rom_mbedtls_cipher_cmac_update = mbedtls_cipher_cmac_update, + ._rom_mbedtls_cipher_cmac_finish = mbedtls_cipher_cmac_finish, + /* ctr drbg module */ + ._rom_mbedtls_ctr_drbg_init = mbedtls_ctr_drbg_init, + ._rom_mbedtls_ctr_drbg_seed = mbedtls_ctr_drbg_seed, + ._rom_mbedtls_ctr_drbg_free = mbedtls_ctr_drbg_free, + ._rom_mbedtls_ctr_drbg_reseed = mbedtls_ctr_drbg_reseed, + ._rom_mbedtls_ctr_drbg_random_with_add = mbedtls_ctr_drbg_random_with_add, + ._rom_mbedtls_ctr_drbg_random = mbedtls_ctr_drbg_random, + /* sha1 module */ + ._rom_mbedtls_sha1_init = mbedtls_sha1_init, + ._rom_mbedtls_sha1_free = mbedtls_sha1_free, + ._rom_mbedtls_sha1_clone = mbedtls_sha1_clone, + ._rom_mbedtls_sha1_starts = mbedtls_sha1_starts, + ._rom_mbedtls_sha1_finish = mbedtls_sha1_finish, + /* sha256 module */ + ._rom_mbedtls_sha256_init = mbedtls_sha256_init, + ._rom_mbedtls_sha256_free = mbedtls_sha256_free, + ._rom_mbedtls_sha256_clone = mbedtls_sha256_clone, + ._rom_mbedtls_sha256_starts = mbedtls_sha256_starts, + ._rom_mbedtls_sha256_finish = mbedtls_sha256_finish, + ._rom_mbedtls_sha256 = mbedtls_sha256, + /* sha512 module */ + ._rom_mbedtls_sha512_init = mbedtls_sha512_init, + ._rom_mbedtls_sha512_free = mbedtls_sha512_free, + ._rom_mbedtls_sha512_clone = mbedtls_sha512_clone, + ._rom_mbedtls_sha512_starts = mbedtls_sha512_starts, + ._rom_mbedtls_sha512_update = mbedtls_sha512_update, + ._rom_mbedtls_sha512_finish = mbedtls_sha512_finish, + //._rom_mbedtls_internal_sha512_process = mbedtls_internal_sha512_process, + ._rom_mbedtls_sha512 = mbedtls_sha512, + + ._rom_mbedtls_aes_xts_init = mbedtls_aes_xts_init, + ._rom_mbedtls_aes_xts_free = mbedtls_aes_xts_free, + ._rom_mbedtls_aes_xts_setkey_enc = mbedtls_aes_xts_setkey_enc, + ._rom_mbedtls_aes_xts_setkey_dec = mbedtls_aes_xts_setkey_dec, + ._rom_mbedtls_aes_crypt_xts = mbedtls_aes_crypt_xts, + ._rom_mbedtls_aes_crypt_cfb128 = mbedtls_aes_crypt_cfb128, + ._rom_mbedtls_aes_crypt_ofb = mbedtls_aes_crypt_ofb, + ._rom_mbedtls_aes_crypt_ctr = mbedtls_aes_crypt_ctr, + ._rom_mbedtls_ccm_init = mbedtls_ccm_init, + ._rom_mbedtls_ccm_setkey = mbedtls_ccm_setkey, + ._rom_mbedtls_ccm_free = mbedtls_ccm_free, + ._rom_mbedtls_ccm_encrypt_and_tag = mbedtls_ccm_encrypt_and_tag, + ._rom_mbedtls_ccm_auth_decrypt = mbedtls_ccm_auth_decrypt, + ._rom_mbedtls_md5_init = mbedtls_md5_init, + ._rom_mbedtls_md5_free = mbedtls_md5_free, + ._rom_mbedtls_md5_clone = mbedtls_md5_clone, + ._rom_mbedtls_md5_starts = mbedtls_md5_starts, + ._rom_mbedtls_md5_update = mbedtls_md5_update, + ._rom_mbedtls_md5_finish = mbedtls_md5_finish, + ._rom_mbedtls_md5 = mbedtls_md5, + ._rom_mbedtls_sha1 = mbedtls_sha1, + + // eco4 rom mbedtls functions + ._rom_mbedtls_aes_crypt_cfb8 = mbedtls_aes_crypt_cfb8, + ._rom_mbedtls_mpi_swap = mbedtls_mpi_swap, + ._rom_mbedtls_mpi_read_string = mbedtls_mpi_read_string, + ._rom_mbedtls_mpi_write_string = mbedtls_mpi_write_string, + ._rom_mbedtls_mpi_read_binary_le = mbedtls_mpi_read_binary_le, + ._rom_mbedtls_mpi_write_binary_le = mbedtls_mpi_write_binary_le, + ._rom_mbedtls_mpi_random = mbedtls_mpi_random, + ._rom_mbedtls_mpi_gen_prime = mbedtls_mpi_gen_prime, + ._rom_mbedtls_ecp_check_budget = mbedtls_ecp_check_budget, + ._rom_mbedtls_ecp_set_max_ops = mbedtls_ecp_set_max_ops, + ._rom_mbedtls_ecp_restart_is_enabled = mbedtls_ecp_restart_is_enabled, + ._rom_mbedtls_ecp_get_type = mbedtls_ecp_get_type, + ._rom_mbedtls_ecp_curve_list = mbedtls_ecp_curve_list, + ._rom_mbedtls_ecp_grp_id_list = mbedtls_ecp_grp_id_list, + ._rom_mbedtls_ecp_curve_info_from_grp_id = mbedtls_ecp_curve_info_from_grp_id, + ._rom_mbedtls_ecp_curve_info_from_tls_id = mbedtls_ecp_curve_info_from_tls_id, + ._rom_mbedtls_ecp_curve_info_from_name = mbedtls_ecp_curve_info_from_name, + ._rom_mbedtls_ecp_point_init = mbedtls_ecp_point_init, + ._rom_mbedtls_ecp_group_init = mbedtls_ecp_group_init, + ._rom_mbedtls_ecp_keypair_init = mbedtls_ecp_keypair_init, + ._rom_mbedtls_ecp_point_free = mbedtls_ecp_point_free, + ._rom_mbedtls_ecp_group_free = mbedtls_ecp_group_free, + ._rom_mbedtls_ecp_keypair_free = mbedtls_ecp_keypair_free, + ._rom_mbedtls_ecp_restart_init = mbedtls_ecp_restart_init, + ._rom_mbedtls_ecp_restart_free = mbedtls_ecp_restart_free, + ._rom_mbedtls_ecp_copy = mbedtls_ecp_copy, + ._rom_mbedtls_ecp_group_copy = mbedtls_ecp_group_copy, + ._rom_mbedtls_ecp_set_zero = mbedtls_ecp_set_zero, + ._rom_mbedtls_ecp_is_zero = mbedtls_ecp_is_zero, + ._rom_mbedtls_ecp_point_cmp = mbedtls_ecp_point_cmp, + ._rom_mbedtls_ecp_point_read_string = mbedtls_ecp_point_read_string, + ._rom_mbedtls_ecp_point_write_binary = mbedtls_ecp_point_write_binary, + ._rom_mbedtls_ecp_point_read_binary = mbedtls_ecp_point_read_binary, + ._rom_mbedtls_ecp_tls_read_point = mbedtls_ecp_tls_read_point, + ._rom_mbedtls_ecp_tls_write_point = mbedtls_ecp_tls_write_point, + ._rom_mbedtls_ecp_group_load = mbedtls_ecp_group_load, + ._rom_mbedtls_ecp_tls_read_group = mbedtls_ecp_tls_read_group, + ._rom_mbedtls_ecp_tls_read_group_id = mbedtls_ecp_tls_read_group_id, + ._rom_mbedtls_ecp_tls_write_group = mbedtls_ecp_tls_write_group, + ._rom_mbedtls_ecp_mul = mbedtls_ecp_mul, + ._rom_mbedtls_ecp_mul_restartable = mbedtls_ecp_mul_restartable, + ._rom_mbedtls_ecp_muladd = mbedtls_ecp_muladd, + ._rom_mbedtls_ecp_muladd_restartable = mbedtls_ecp_muladd_restartable, + ._rom_mbedtls_ecp_check_pubkey = mbedtls_ecp_check_pubkey, + ._rom_mbedtls_ecp_check_privkey = mbedtls_ecp_check_privkey, + ._rom_mbedtls_ecp_gen_privkey = mbedtls_ecp_gen_privkey, + ._rom_mbedtls_ecp_gen_keypair_base = mbedtls_ecp_gen_keypair_base, + ._rom_mbedtls_ecp_gen_keypair = mbedtls_ecp_gen_keypair, + ._rom_mbedtls_ecp_gen_key = mbedtls_ecp_gen_key, + ._rom_mbedtls_ecp_read_key = mbedtls_ecp_read_key, + ._rom_mbedtls_ecp_write_key_ext = mbedtls_ecp_write_key_ext, + ._rom_mbedtls_ecp_check_pub_priv = mbedtls_ecp_check_pub_priv, + ._rom_mbedtls_ecp_export = mbedtls_ecp_export, + ._rom_mbedtls_asn1_get_enum = mbedtls_asn1_get_enum, + ._rom_mbedtls_asn1_sequence_free = mbedtls_asn1_sequence_free, + ._rom_mbedtls_asn1_traverse_sequence_of = mbedtls_asn1_traverse_sequence_of, + ._rom_mbedtls_asn1_find_named_data = mbedtls_asn1_find_named_data, + ._rom_mbedtls_asn1_free_named_data_list = mbedtls_asn1_free_named_data_list, + ._rom_mbedtls_asn1_free_named_data_list_shallow = mbedtls_asn1_free_named_data_list_shallow, + ._rom_mbedtls_asn1_write_raw_buffer = mbedtls_asn1_write_raw_buffer, + ._rom_mbedtls_asn1_write_null = mbedtls_asn1_write_null, + ._rom_mbedtls_asn1_write_oid = mbedtls_asn1_write_oid, + ._rom_mbedtls_asn1_write_algorithm_identifier = mbedtls_asn1_write_algorithm_identifier, + ._rom_mbedtls_asn1_write_bool = mbedtls_asn1_write_bool, + ._rom_mbedtls_asn1_write_int = mbedtls_asn1_write_int, + ._rom_mbedtls_asn1_write_enum = mbedtls_asn1_write_enum, + ._rom_mbedtls_asn1_write_tagged_string = mbedtls_asn1_write_tagged_string, + ._rom_mbedtls_asn1_write_printable_string = mbedtls_asn1_write_printable_string, + ._rom_mbedtls_asn1_write_utf8_string = mbedtls_asn1_write_utf8_string, + ._rom_mbedtls_asn1_write_ia5_string = mbedtls_asn1_write_ia5_string, + ._rom_mbedtls_asn1_write_bitstring = mbedtls_asn1_write_bitstring, + ._rom_mbedtls_asn1_write_named_bitstring = mbedtls_asn1_write_named_bitstring, + ._rom_mbedtls_asn1_write_octet_string = mbedtls_asn1_write_octet_string, + ._rom_mbedtls_asn1_store_named_data = mbedtls_asn1_store_named_data, + ._rom_mbedtls_ccm_starts = mbedtls_ccm_starts, + ._rom_mbedtls_ccm_set_lengths = mbedtls_ccm_set_lengths, + ._rom_mbedtls_ccm_update_ad = mbedtls_ccm_update_ad, + ._rom_mbedtls_ccm_update = mbedtls_ccm_update, + ._rom_mbedtls_ccm_finish = mbedtls_ccm_finish, + ._rom_mbedtls_cipher_list = mbedtls_cipher_list, + ._rom_mbedtls_cipher_info_from_string = mbedtls_cipher_info_from_string, + ._rom_mbedtls_cipher_info_from_type = mbedtls_cipher_info_from_type, + ._rom_mbedtls_cipher_info_from_values = mbedtls_cipher_info_from_values, + ._rom_mbedtls_cipher_free = mbedtls_cipher_free, + ._rom_mbedtls_cipher_setup = mbedtls_cipher_setup, + ._rom_mbedtls_cipher_setkey = mbedtls_cipher_setkey, + ._rom_mbedtls_cipher_set_iv = mbedtls_cipher_set_iv, + ._rom_mbedtls_cipher_update_ad = mbedtls_cipher_update_ad, + ._rom_mbedtls_cipher_update = mbedtls_cipher_update, + ._rom_mbedtls_cipher_write_tag = mbedtls_cipher_write_tag, + ._rom_mbedtls_cipher_check_tag = mbedtls_cipher_check_tag, + ._rom_mbedtls_cipher_auth_encrypt_ext = mbedtls_cipher_auth_encrypt_ext, + ._rom_mbedtls_cipher_auth_decrypt_ext = mbedtls_cipher_auth_decrypt_ext, + ._rom_mbedtls_cipher_cmac_reset = mbedtls_cipher_cmac_reset, + ._rom_mbedtls_cipher_cmac = mbedtls_cipher_cmac, + ._rom_mbedtls_aes_cmac_prf_128 = mbedtls_aes_cmac_prf_128, + ._rom_mbedtls_ctr_drbg_set_prediction_resistance = mbedtls_ctr_drbg_set_prediction_resistance, + ._rom_mbedtls_ctr_drbg_set_entropy_len = mbedtls_ctr_drbg_set_entropy_len, + ._rom_mbedtls_ctr_drbg_set_nonce_len = mbedtls_ctr_drbg_set_nonce_len, + ._rom_mbedtls_ctr_drbg_set_reseed_interval = mbedtls_ctr_drbg_set_reseed_interval, + ._rom_mbedtls_ctr_drbg_update = mbedtls_ctr_drbg_update, + ._rom_mbedtls_base64_encode = mbedtls_base64_encode, + + /* Fill the SHA hardware functions into mbedtls rom function table */ + ._rom_mbedtls_sha1_update = mbedtls_sha1_update, + ._rom_mbedtls_sha256_update = mbedtls_sha256_update, + + //memory calloc free + ._rom_mbedtls_mem_calloc = MBEDTLS_PLATFORM_STD_CALLOC, + ._rom_mbedtls_mem_free = MBEDTLS_PLATFORM_STD_FREE, +}; + +__attribute__((constructor)) void mbedtls_rom_osi_functions_init(void) +{ + /* Export the rom mbedtls functions table pointer */ + extern void *mbedtls_rom_osi_funcs_ptr; + +#if defined(MBEDTLS_THREADING_ALT) + mbedtls_threading_set_alt(mbedtls_rom_mutex_init, mbedtls_rom_mutex_free, mbedtls_rom_mutex_lock, mbedtls_rom_mutex_unlock); +#endif + + unsigned chip_version = efuse_hal_chip_revision(); + if ( ESP_CHIP_REV_ABOVE(chip_version, 200) ) { + /* Initialize the rom function mbedtls_threading_set_alt on chip rev2.0 with rom eco4 */ + _rom_mbedtls_threading_set_alt_t rom_mbedtls_threading_set_alt = (_rom_mbedtls_threading_set_alt_t)0x40002c0c; + rom_mbedtls_threading_set_alt(mbedtls_rom_mutex_init, mbedtls_rom_mutex_free, mbedtls_rom_mutex_lock, mbedtls_rom_mutex_unlock); + + /* Initialize the pointer of rom eco4 mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; + } else { + /* Initialize the pointer of rom mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_funcs_t *)&mbedtls_rom_funcs_table; + } +} diff --git a/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.h b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.h new file mode 100644 index 000000000..b612adfa5 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/mbedtls_rom_osi.h @@ -0,0 +1,794 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "mbedtls/aes.h" +#include "mbedtls/asn1.h" +#include "mbedtls/asn1write.h" +#include "mbedtls/base64.h" +#include "mbedtls/bignum.h" +#include "mbedtls/ccm.h" +#include "mbedtls/cipher.h" +#include "mbedtls/cmac.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/dhm.h" +#include "mbedtls/ecdh.h" +#include "mbedtls/ecdsa.h" +#include "mbedtls/ecjpake.h" +#include "mbedtls/ecp.h" +#include "mbedtls/entropy.h" +#include "mbedtls/hmac_drbg.h" +#include "mbedtls/md.h" +#include "mbedtls/md5.h" +#include "mbedtls/oid.h" +#include "mbedtls/pem.h" +#include "mbedtls/pkcs12.h" +#include "mbedtls/pkcs5.h" +#include "mbedtls/pk.h" +#include "mbedtls/platform.h" +#include "mbedtls/rsa.h" +#include "mbedtls/sha1.h" +#include "mbedtls/sha256.h" +#include "mbedtls/sha512.h" +#include "mbedtls/ssl_ciphersuites.h" +#include "mbedtls/ssl.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/x509.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if (!defined(CONFIG_MBEDTLS_THREADING_C)) +#error CONFIG_MBEDTLS_THREADING_C +#endif + +typedef void (*_rom_mbedtls_threading_set_alt_t)(void (*mutex_init)(mbedtls_threading_mutex_t *), + void (*mutex_free)(mbedtls_threading_mutex_t *), + int (*mutex_lock)(mbedtls_threading_mutex_t *), + int (*mutex_unlock)(mbedtls_threading_mutex_t *)); + +typedef struct mbedtls_rom_funcs { + void (*_rom_mbedtls_aes_init)( mbedtls_aes_context *ctx ); + int (*_rom_ssl_write_client_hello)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_parse_server_hello)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_parse_server_key_exchange)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_parse_certificate_request)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_parse_server_hello_done)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_write_client_key_exchange)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_write_certificate_verify)( mbedtls_ssl_context *ssl ); + int (*_rom_ssl_parse_new_session_ticket)( mbedtls_ssl_context *ssl ); + void (*_rom_mbedtls_aes_free)( mbedtls_aes_context *ctx ); + int (*_rom_mbedtls_aes_setkey_enc)( mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits ); + int (*_rom_mbedtls_aes_setkey_dec)( mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits ); + int (*_rom_mbedtls_aes_crypt_ecb)( mbedtls_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16] ); + int (*_rom_mbedtls_aes_crypt_cbc)( mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_internal_aes_encrypt)( mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); + int (*_rom_mbedtls_internal_aes_decrypt)( mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); + int (*_rom_mbedtls_asn1_get_len)( unsigned char **p, const unsigned char *end, size_t *len ); + int (*_rom_mbedtls_asn1_get_tag)( unsigned char **p, const unsigned char *end, size_t *len, int tag ); + int (*_rom_mbedtls_asn1_get_bool)( unsigned char **p, const unsigned char *end, int *val ); + int (*_rom_mbedtls_asn1_get_int)( unsigned char **p, const unsigned char *end, int *val ); + int (*_rom_mbedtls_asn1_get_bitstring)( unsigned char **p, const unsigned char *end, mbedtls_asn1_bitstring *bs); + int (*_rom_mbedtls_asn1_get_bitstring_null)( unsigned char **p, const unsigned char *end, size_t *len ); + int (*_rom_mbedtls_asn1_get_sequence_of)( unsigned char **p, const unsigned char *end, mbedtls_asn1_sequence *cur, int tag); + int (*_rom_mbedtls_asn1_get_mpi)( unsigned char **p, const unsigned char *end, mbedtls_mpi *X ); + int (*_rom_mbedtls_asn1_get_alg)( unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg, mbedtls_asn1_buf *params ); + int (*_rom_mbedtls_asn1_get_alg_null)( unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg ); + int (*_rom_mbedtls_asn1_write_len)( unsigned char **p, const unsigned char *start, size_t len ); + int (*_rom_mbedtls_asn1_write_tag)( unsigned char **p, const unsigned char *start, unsigned char tag ); + int (*_rom_mbedtls_asn1_write_mpi)( unsigned char **p, const unsigned char *start, const mbedtls_mpi *X ); + int (*_rom_mbedtls_base64_decode)( unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen ); + void (*_rom_mbedtls_mpi_init)( mbedtls_mpi *X ); + void (*_rom_mbedtls_mpi_free)( mbedtls_mpi *X ); + int (*_rom_mbedtls_mpi_grow)( mbedtls_mpi *X, size_t nblimbs ); + int (*_rom_mbedtls_mpi_shrink)( mbedtls_mpi *X, size_t nblimbs ); + int (*_rom_mbedtls_mpi_copy)( mbedtls_mpi *X, const mbedtls_mpi *Y ); + int (*_rom_mbedtls_mpi_safe_cond_assign)( mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned char assign ); + int (*_rom_mbedtls_mpi_safe_cond_swap)( mbedtls_mpi *X, mbedtls_mpi *Y, unsigned char assign ); + int (*_rom_mbedtls_mpi_lset)( mbedtls_mpi *X, mbedtls_mpi_sint z ); + int (*_rom_mbedtls_mpi_get_bit)( const mbedtls_mpi *X, size_t pos ); + int (*_rom_mbedtls_mpi_set_bit)( mbedtls_mpi *X, size_t pos, unsigned char val ); + size_t (*_rom_mbedtls_mpi_lsb)( const mbedtls_mpi *X ); + size_t (*_rom_mbedtls_mpi_bitlen)( const mbedtls_mpi *X ); + size_t (*_rom_mbedtls_mpi_size)( const mbedtls_mpi *X ); + int (*_rom_mbedtls_mpi_read_binary)( mbedtls_mpi *X, const unsigned char *buf, size_t buflen ); + int (*_rom_mbedtls_mpi_write_binary)( const mbedtls_mpi *X, unsigned char *buf, size_t buflen ); + int (*_rom_mbedtls_mpi_shift_l)( mbedtls_mpi *X, size_t count ); + int (*_rom_mbedtls_mpi_shift_r)( mbedtls_mpi *X, size_t count ); + int (*_rom_mbedtls_mpi_cmp_abs)( const mbedtls_mpi *X, const mbedtls_mpi *Y ); + int (*_rom_mbedtls_mpi_cmp_mpi)( const mbedtls_mpi *X, const mbedtls_mpi *Y ); + int (*_rom_mbedtls_mpi_lt_mpi_ct)( const mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned *ret ); + int (*_rom_mbedtls_mpi_cmp_int)( const mbedtls_mpi *X, mbedtls_mpi_sint z ); + int (*_rom_mbedtls_mpi_add_abs)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_sub_abs)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_add_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_sub_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_add_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b ); + int (*_rom_mbedtls_mpi_sub_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b ); + int (*_rom_mbedtls_mpi_mul_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_mul_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_uint b ); + int (*_rom_mbedtls_mpi_div_mpi)( mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_div_int)( mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, mbedtls_mpi_sint b ); + int (*_rom_mbedtls_mpi_mod_mpi)( mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_mod_int)( mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_sint b ); + int (*_rom_mbedtls_mpi_exp_mod)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *E, const mbedtls_mpi *N, mbedtls_mpi *_RR ); + int (*_rom_mbedtls_mpi_fill_random)( mbedtls_mpi *X, size_t size, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_mpi_gcd)( mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B ); + int (*_rom_mbedtls_mpi_inv_mod)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *N ); + int (*_rom_mbedtls_mpi_is_prime_ext)( const mbedtls_mpi *X, int rounds, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ccm_star_encrypt_and_tag)( mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, unsigned char *tag, size_t tag_len ); + int (*_rom_mbedtls_ccm_star_auth_decrypt)( mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, const unsigned char *tag, size_t tag_len ); + void (*_rom_mbedtls_cipher_init)( mbedtls_cipher_context_t *ctx ); + int (*_rom_mbedtls_cipher_set_padding_mode)( mbedtls_cipher_context_t *ctx, mbedtls_cipher_padding_t mode ); + int (*_rom_mbedtls_cipher_reset)( mbedtls_cipher_context_t *ctx ); + int (*_rom_mbedtls_cipher_finish)( mbedtls_cipher_context_t *ctx, unsigned char *output, size_t *olen ); + int (*_rom_mbedtls_cipher_crypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen ); + int (*_rom_mbedtls_cipher_cmac_starts)( mbedtls_cipher_context_t *ctx, const unsigned char *key, size_t keybits ); + int (*_rom_mbedtls_cipher_cmac_update)( mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen ); + int (*_rom_mbedtls_cipher_cmac_finish)( mbedtls_cipher_context_t *ctx, unsigned char *output ); + void (*_rom_mbedtls_ctr_drbg_init)( mbedtls_ctr_drbg_context *ctx ); + int (*_rom_mbedtls_ctr_drbg_seed)( mbedtls_ctr_drbg_context *ctx, int (*f_entropy)(void *, unsigned char *, size_t), void *p_entropy, const unsigned char *custom, size_t len ); + void (*_rom_mbedtls_ctr_drbg_free)( mbedtls_ctr_drbg_context *ctx ); + int (*_rom_mbedtls_ctr_drbg_reseed)( mbedtls_ctr_drbg_context *ctx, const unsigned char *additional, size_t len ); + int (*_rom_mbedtls_ctr_drbg_random_with_add)( void *p_rng, unsigned char *output, size_t output_len, const unsigned char *additional, size_t add_len ); + int (*_rom_mbedtls_ctr_drbg_random)( void *p_rng, unsigned char *output, size_t output_len ); + void (*_rom_mbedtls_dhm_init)( mbedtls_dhm_context *ctx ); + int (*_rom_mbedtls_dhm_read_params)( mbedtls_dhm_context *ctx, unsigned char **p, const unsigned char *end ); + int (*_rom_mbedtls_dhm_make_public)( mbedtls_dhm_context *ctx, int x_size, unsigned char *output, size_t olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_dhm_calc_secret)( mbedtls_dhm_context *ctx, unsigned char *output, size_t output_size, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + void (*_rom_mbedtls_dhm_free)( mbedtls_dhm_context *ctx ); + void (*_rom_mbedtls_ecdh_init)( mbedtls_ecdh_context *ctx ); + int (*_rom_mbedtls_ecdh_setup)( mbedtls_ecdh_context *ctx, mbedtls_ecp_group_id grp_id ); + void (*_rom_mbedtls_ecdh_free)( mbedtls_ecdh_context *ctx ); + int (*_rom_mbedtls_ecdh_read_params)( mbedtls_ecdh_context *ctx, const unsigned char **buf, const unsigned char *end ); + int (*_rom_mbedtls_ecdh_get_params)( mbedtls_ecdh_context *ctx, const mbedtls_ecp_keypair *key, mbedtls_ecdh_side side ); + int (*_rom_mbedtls_ecdh_make_public)( mbedtls_ecdh_context *ctx, size_t *olen, unsigned char *buf, size_t blen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecdh_calc_secret)( mbedtls_ecdh_context *ctx, size_t *olen, unsigned char *buf, size_t blen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + void (*_rom_mbedtls_ecdh_enable_restart)( mbedtls_ecdh_context *ctx ); + int (*_rom_mbedtls_ecdsa_write_signature)( mbedtls_ecdsa_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hlen, unsigned char *sig, size_t *slen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecdsa_write_signature_restartable)( mbedtls_ecdsa_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hlen, unsigned char *sig, size_t *slen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_ecdsa_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_ecdsa_read_signature)( mbedtls_ecdsa_context *ctx, const unsigned char *hash, size_t hlen, const unsigned char *sig, size_t slen ); + int (*_rom_mbedtls_ecdsa_read_signature_restartable)( mbedtls_ecdsa_context *ctx, const unsigned char *hash, size_t hlen, const unsigned char *sig, size_t slen, mbedtls_ecdsa_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_ecdsa_from_keypair)( mbedtls_ecdsa_context *ctx, const mbedtls_ecp_keypair *key ); + void (*_rom_mbedtls_ecdsa_init)( mbedtls_ecdsa_context *ctx ); + void (*_rom_mbedtls_ecdsa_free)( mbedtls_ecdsa_context *ctx ); + void (*_rom_mbedtls_ecdsa_restart_init)( mbedtls_ecdsa_restart_ctx *ctx ); + void (*_rom_mbedtls_ecdsa_restart_free)( mbedtls_ecdsa_restart_ctx *ctx ); + void (*_rom_mbedtls_ecjpake_init)( mbedtls_ecjpake_context *ctx ); + int (*_rom_mbedtls_ecjpake_check)( const mbedtls_ecjpake_context *ctx ); + int (*_rom_mbedtls_ecjpake_write_round_one)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecjpake_read_round_one)( mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len ); + int (*_rom_mbedtls_ecjpake_write_round_two)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecjpake_read_round_two)( mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len ); + int (*_rom_mbedtls_ecjpake_derive_secret)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + void (*_rom_mbedtls_ecjpake_free)( mbedtls_ecjpake_context *ctx ); + int (*_rom_mbedtls_ecp_check_budget)( const mbedtls_ecp_group *grp, mbedtls_ecp_restart_ctx *rs_ctx, unsigned ops ); + int (*_rom_mbedtls_ecp_restart_is_enabled)( void ); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_list)( void ); + const mbedtls_ecp_group_id *(*_rom_mbedtls_ecp_grp_id_list)( void ); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_grp_id)( mbedtls_ecp_group_id grp_id ); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_tls_id)( uint16_t tls_id ); + void (*_rom_mbedtls_ecp_point_init)( mbedtls_ecp_point *pt ); + void (*_rom_mbedtls_ecp_group_init)( mbedtls_ecp_group *grp ); + void (*_rom_mbedtls_ecp_keypair_init)( mbedtls_ecp_keypair *key ); + void (*_rom_mbedtls_ecp_point_free)( mbedtls_ecp_point *pt ); + void (*_rom_mbedtls_ecp_group_free)( mbedtls_ecp_group *grp ); + void (*_rom_mbedtls_ecp_keypair_free)( mbedtls_ecp_keypair *key ); + void (*_rom_mbedtls_ecp_restart_init)( mbedtls_ecp_restart_ctx *ctx ); + void (*_rom_mbedtls_ecp_restart_free)( mbedtls_ecp_restart_ctx *ctx ); + int (*_rom_mbedtls_ecp_copy)( mbedtls_ecp_point *P, const mbedtls_ecp_point *Q ); + int (*_rom_mbedtls_ecp_group_copy)( mbedtls_ecp_group *dst, const mbedtls_ecp_group *src ); + int (*_rom_mbedtls_ecp_set_zero)( mbedtls_ecp_point *pt ); + int (*_rom_mbedtls_ecp_is_zero)( mbedtls_ecp_point *pt ); + int (*_rom_mbedtls_ecp_point_cmp)( const mbedtls_ecp_point *P, const mbedtls_ecp_point *Q ); + int (*_rom_mbedtls_ecp_point_write_binary)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *P, int format, size_t *olen, unsigned char *buf, size_t buflen ); + int (*_rom_mbedtls_ecp_point_read_binary)( const mbedtls_ecp_group *grp, mbedtls_ecp_point *P, const unsigned char *buf, size_t ilen ); + int (*_rom_mbedtls_ecp_tls_read_point)( const mbedtls_ecp_group *grp, mbedtls_ecp_point *pt, const unsigned char **buf, size_t len ); + int (*_rom_mbedtls_ecp_tls_write_point)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt, int format, size_t *olen, unsigned char *buf, size_t blen ); + int (*_rom_mbedtls_ecp_group_load)( mbedtls_ecp_group *grp, mbedtls_ecp_group_id id ); + int (*_rom_mbedtls_ecp_tls_read_group)( mbedtls_ecp_group *grp, const unsigned char **buf, size_t len ); + int (*_rom_mbedtls_ecp_tls_read_group_id)( mbedtls_ecp_group_id *grp, const unsigned char **buf, size_t len ); + int (*_rom_mbedtls_ecp_tls_write_group)( const mbedtls_ecp_group *grp, size_t *olen, unsigned char *buf, size_t blen ); + int (*_rom_mbedtls_ecp_mul)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecp_mul_restartable)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_ecp_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_ecp_muladd)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q ); + int (*_rom_mbedtls_ecp_muladd_restartable)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q, mbedtls_ecp_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_ecp_check_pubkey)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt ); + int (*_rom_mbedtls_ecp_check_privkey)( const mbedtls_ecp_group *grp, const mbedtls_mpi *d ); + int (*_rom_mbedtls_ecp_gen_privkey)( const mbedtls_ecp_group *grp, mbedtls_mpi *d, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecp_gen_keypair_base)( mbedtls_ecp_group *grp, const mbedtls_ecp_point *G, mbedtls_mpi *d, mbedtls_ecp_point *Q, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_ecp_check_pub_priv)( const mbedtls_ecp_keypair *pub, const mbedtls_ecp_keypair *prv ); + int (*_rom_mbedtls_reserved0)(void); + int (*_rom_mbedtls_reserved1)(void); + int (*_rom_mbedtls_gcm_crypt_and_tag)( mbedtls_gcm_context *ctx, int mode, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, size_t tag_len, unsigned char *tag ); + int (*_rom_mbedtls_gcm_starts)( mbedtls_gcm_context *ctx, int mode, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len ); + int (*_rom_mbedtls_gcm_update)( mbedtls_gcm_context *ctx, size_t length, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_gcm_finish)( mbedtls_gcm_context *ctx, unsigned char *tag, size_t tag_len ); + void (*_rom_mbedtls_hmac_drbg_init)( mbedtls_hmac_drbg_context *ctx ); + int (*_rom_mbedtls_hmac_drbg_seed_buf)( mbedtls_hmac_drbg_context *ctx, const mbedtls_md_info_t * md_info, const unsigned char *data, size_t data_len ); + int (*_rom_mbedtls_hmac_drbg_update_ret)( mbedtls_hmac_drbg_context *ctx, const unsigned char *additional, size_t add_len ); + int (*_rom_mbedtls_hmac_drbg_reseed)( mbedtls_hmac_drbg_context *ctx, const unsigned char *additional, size_t len ); + int (*_rom_mbedtls_hmac_drbg_random_with_add)( void *p_rng, unsigned char *output, size_t output_len, const unsigned char *additional, size_t add_len ); + int (*_rom_mbedtls_hmac_drbg_random)( void *p_rng, unsigned char *output, size_t out_len ); + void (*_rom_mbedtls_hmac_drbg_free)( mbedtls_hmac_drbg_context *ctx ); + const int *(*_rom_mbedtls_md_list)( void ); + void (*_rom_mbedtls_md_init)( mbedtls_md_context_t *ctx ); + void (*_rom_mbedtls_md_free)( mbedtls_md_context_t *ctx ); + int (*_rom_mbedtls_md_setup)( mbedtls_md_context_t *ctx, const mbedtls_md_info_t *md_info, int hmac ); + int (*_rom_mbedtls_md_clone)( mbedtls_md_context_t *dst, const mbedtls_md_context_t *src ); + unsigned char (*_rom_mbedtls_md_get_size)( const mbedtls_md_info_t *md_info ); + mbedtls_md_type_t (*_rom_mbedtls_md_get_type)( const mbedtls_md_info_t *md_info ); + int (*_rom_mbedtls_md_starts)( mbedtls_md_context_t *ctx ); + int (*_rom_mbedtls_md_update)( mbedtls_md_context_t *ctx, const unsigned char *input, size_t ilen ); + int (*_rom_mbedtls_md_finish)( mbedtls_md_context_t *ctx, unsigned char *output ); + int (*_rom_mbedtls_md)( const mbedtls_md_info_t *md_info, const unsigned char *input, size_t ilen, unsigned char *output ); + int (*_rom_mbedtls_md_hmac_starts)( mbedtls_md_context_t *ctx, const unsigned char *key, size_t keylen ); + int (*_rom_mbedtls_md_hmac_update)( mbedtls_md_context_t *ctx, const unsigned char *input, size_t ilen ); + int (*_rom_mbedtls_md_hmac_finish)( mbedtls_md_context_t *ctx, unsigned char *output); + int (*_rom_mbedtls_md_hmac_reset)( mbedtls_md_context_t *ctx ); + int (*_rom_mbedtls_oid_get_x509_ext_type)( const mbedtls_asn1_buf *oid, int *ext_type ); + int (*_rom_mbedtls_oid_get_pk_alg)( const mbedtls_asn1_buf *oid, mbedtls_pk_type_t *pk_alg ); + int (*_rom_mbedtls_oid_get_ec_grp)( const mbedtls_asn1_buf *oid, mbedtls_ecp_group_id *grp_id ); + int (*_rom_mbedtls_oid_get_sig_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg, mbedtls_pk_type_t *pk_alg ); + int (*_rom_mbedtls_oid_get_md_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg ); + int (*_rom_mbedtls_oid_get_md_hmac)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_hmac ); + int (*_rom_mbedtls_oid_get_oid_by_md)( mbedtls_md_type_t md_alg, const char **oid, size_t *olen ); + int (*_rom_mbedtls_oid_get_cipher_alg)( const mbedtls_asn1_buf *oid, mbedtls_cipher_type_t *cipher_alg ); + int (*_rom_mbedtls_oid_get_pkcs12_pbe_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg, mbedtls_cipher_type_t *cipher_alg ); + void (*_rom_mbedtls_pem_init)( void *ctx ); + void (*_rom_mbedtls_pem_free)( void *ctx ); + int (*_rom_mbedtls_pkcs12_pbe_sha1_rc4_128)( mbedtls_asn1_buf *pbe_params, int mode, const unsigned char *pwd, size_t pwdlen, const unsigned char *input, size_t len, unsigned char *output ); + int (*_rom_mbedtls_pkcs12_pbe)( mbedtls_asn1_buf *pbe_params, int mode, mbedtls_cipher_type_t cipher_type, mbedtls_md_type_t md_type, const unsigned char *pwd, size_t pwdlen, const unsigned char *input, size_t len, unsigned char *output ); + int (*_rom_mbedtls_pkcs12_derivation)( unsigned char *data, size_t datalen, const unsigned char *pwd, size_t pwdlen, const unsigned char *salt, size_t saltlen, mbedtls_md_type_t mbedtls_md, int id, int iterations ); + int (*_rom_mbedtls_pkcs5_pbes2)( const mbedtls_asn1_buf *pbe_params, int mode, const unsigned char *pwd, size_t pwdlen, const unsigned char *data, size_t datalen, unsigned char *output ); + int (*_rom_mbedtls_pkcs5_pbkdf2_hmac)( mbedtls_md_context_t *ctx, const unsigned char *password, size_t plen, const unsigned char *salt, size_t slen, unsigned int iteration_count, uint32_t key_length, unsigned char *output ); + const mbedtls_pk_info_t *(*_rom_mbedtls_pk_info_from_type)( mbedtls_pk_type_t pk_type ); + void (*_rom_mbedtls_pk_init)( mbedtls_pk_context *ctx ); + void (*_rom_mbedtls_pk_free)( mbedtls_pk_context *ctx ); + void (*_rom_mbedtls_pk_restart_init)( mbedtls_pk_restart_ctx *ctx ); + void (*_rom_mbedtls_pk_restart_free)( mbedtls_pk_restart_ctx *ctx ); + int (*_rom_mbedtls_pk_setup)( mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info ); + int (*_rom_mbedtls_pk_can_do)( const mbedtls_pk_context *ctx, mbedtls_pk_type_t type ); + int (*_rom_mbedtls_pk_verify)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len ); + int (*_rom_mbedtls_pk_verify_restartable)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len, mbedtls_pk_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_pk_verify_ext)( mbedtls_pk_type_t type, const void *options, mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len ); + int (*_rom_mbedtls_pk_sign_restartable)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, unsigned char *sig, size_t *sig_len, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_pk_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_pk_encrypt)( mbedtls_pk_context *ctx, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, size_t osize, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + mbedtls_pk_type_t (*_rom_mbedtls_pk_get_type)( const mbedtls_pk_context *ctx ); + int (*_rom_mbedtls_pk_parse_subpubkey)( unsigned char **p, const unsigned char *end, mbedtls_pk_context *pk ); + void (*_rom_mbedtls_rsa_init)( mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_import)( mbedtls_rsa_context *ctx, const mbedtls_mpi *N, const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *E ); + int (*_rom_mbedtls_rsa_import_raw)( mbedtls_rsa_context *ctx, unsigned char const *N, size_t N_len, unsigned char const *P, size_t P_len, unsigned char const *Q, size_t Q_len, unsigned char const *D, size_t D_len, unsigned char const *E, size_t E_len ); + int (*_rom_mbedtls_rsa_complete)( mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_set_padding)( mbedtls_rsa_context *ctx, int padding, mbedtls_md_type_t hash_id ); + size_t (*_rom_mbedtls_rsa_get_len)( const mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_check_pubkey)( const mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_check_privkey)( const mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_check_pub_priv)( const mbedtls_rsa_context *pub, const mbedtls_rsa_context *prv ); + int (*_rom_mbedtls_rsa_public)( mbedtls_rsa_context *ctx, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_rsa_private)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_rsa_pkcs1_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t ilen, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_rsa_rsaes_pkcs1_v15_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t ilen, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_rsa_rsaes_oaep_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, const unsigned char *label, size_t label_len, size_t ilen, const unsigned char *input, unsigned char *output ); + int (*_rom_mbedtls_rsa_pkcs1_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); + int (*_rom_mbedtls_rsa_rsaes_pkcs1_v15_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); + int (*_rom_mbedtls_rsa_rsaes_oaep_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, const unsigned char *label, size_t label_len, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); + int (*_rom_mbedtls_rsa_pkcs1_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); + int (*_rom_mbedtls_rsa_rsassa_pkcs1_v15_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); + int (*_rom_mbedtls_rsa_rsassa_pss_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); + int (*_rom_mbedtls_rsa_pkcs1_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); + int (*_rom_mbedtls_rsa_rsassa_pkcs1_v15_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); + int (*_rom_mbedtls_rsa_rsassa_pss_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); + int (*_rom_mbedtls_rsa_rsassa_pss_verify_ext)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, mbedtls_md_type_t mgf1_hash_id, int expected_salt_len, const unsigned char *sig ); + void (*_rom_mbedtls_rsa_free)( mbedtls_rsa_context *ctx ); + int (*_rom_mbedtls_rsa_deduce_primes)( mbedtls_mpi const *N, mbedtls_mpi const *E, mbedtls_mpi const *D, mbedtls_mpi *P, mbedtls_mpi *Q ); + int (*_rom_mbedtls_rsa_deduce_private_exponent)( mbedtls_mpi const *P, mbedtls_mpi const *Q, mbedtls_mpi const *E, mbedtls_mpi *D ); + int (*_rom_mbedtls_rsa_deduce_crt)( const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, mbedtls_mpi *DP, mbedtls_mpi *DQ, mbedtls_mpi *QP ); + int (*_rom_mbedtls_rsa_validate_params)( const mbedtls_mpi *N, const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *E, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); + int (*_rom_mbedtls_rsa_validate_crt)( const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *DP, const mbedtls_mpi *DQ, const mbedtls_mpi *QP ); + void (*_rom_mbedtls_sha1_init)( mbedtls_sha1_context *ctx ); + void (*_rom_mbedtls_sha1_free)( mbedtls_sha1_context *ctx ); + void (*_rom_mbedtls_sha1_clone)( mbedtls_sha1_context *dst, const mbedtls_sha1_context *src ); + int (*_rom_mbedtls_sha1_starts)( mbedtls_sha1_context *ctx ); + int (*_rom_mbedtls_sha1_finish)( mbedtls_sha1_context *ctx, unsigned char output[20] ); + void (*_rom_mbedtls_sha256_init)( mbedtls_sha256_context *ctx ); + void (*_rom_mbedtls_sha256_free)( mbedtls_sha256_context *ctx ); + void (*_rom_mbedtls_sha256_clone)( mbedtls_sha256_context *dst, const mbedtls_sha256_context *src ); + int (*_rom_mbedtls_sha256_starts)( mbedtls_sha256_context *ctx, int is224 ); + int (*_rom_mbedtls_sha256_finish)( mbedtls_sha256_context *ctx, unsigned char output[32] ); + int (*_rom_mbedtls_sha256)( const unsigned char *input, size_t ilen, unsigned char output[32], int is224 ); + void (*_rom_mbedtls_sha512_init)( mbedtls_sha512_context *ctx ); + void (*_rom_mbedtls_sha512_free)( mbedtls_sha512_context *ctx ); + void (*_rom_mbedtls_sha512_clone)( mbedtls_sha512_context *dst, const mbedtls_sha512_context *src ); + int (*_rom_mbedtls_sha512_starts)( mbedtls_sha512_context *ctx, int is384 ); + int (*_rom_mbedtls_sha512_update)( mbedtls_sha512_context *ctx, const unsigned char *input, size_t ilen ); + int (*_rom_mbedtls_sha512_finish)( mbedtls_sha512_context *ctx, unsigned char output[64] ); + int (*_rom_mbedtls_internal_sha512_process)( mbedtls_sha512_context *ctx, const unsigned char data[128] ); + int (*_rom_mbedtls_sha512)( const unsigned char *input, size_t ilen, unsigned char output[64], int is384 ); + void (*_rom_mbedtls_ssl_conf_endpoint)( mbedtls_ssl_config *conf, int endpoint ); + void (*_rom_mbedtls_ssl_conf_transport)( mbedtls_ssl_config *conf, int transport ); + void (*_rom_mbedtls_ssl_set_bio)( mbedtls_ssl_context *ssl, void *p_bio, mbedtls_ssl_send_t *f_send, mbedtls_ssl_recv_t *f_recv, mbedtls_ssl_recv_timeout_t *f_recv_timeout ); + int (*_rom_mbedtls_ssl_conf_dh_param_bin)( mbedtls_ssl_config *conf, const unsigned char *dhm_P, size_t P_len, const unsigned char *dhm_G, size_t G_len ); + size_t (*_rom_mbedtls_ssl_get_max_frag_len)( const mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_get_max_out_record_payload)( const mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_handshake)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_handshake_step)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_renegotiate)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_send_alert_message)( mbedtls_ssl_context *ssl, unsigned char level, unsigned char message ); + int (*_rom_mbedtls_ssl_config_defaults)( mbedtls_ssl_config *conf, int endpoint, int transport, int preset ); + void (*_rom_mbedtls_ssl_session_init)( mbedtls_ssl_session *session ); + void (*_rom_mbedtls_ssl_session_free)( mbedtls_ssl_session *session ); + void (*_rom_mbedtls_ssl_transform_free)( mbedtls_ssl_transform *transform ); + void (*_rom_mbedtls_ssl_handshake_free)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_handshake_client_step)( mbedtls_ssl_context *ssl ); + void (*_rom_mbedtls_ssl_handshake_wrapup)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_derive_keys)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_handle_message_type)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_prepare_handshake_record)( mbedtls_ssl_context *ssl ); + void (*_rom_mbedtls_ssl_update_handshake_status)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_read_record)( mbedtls_ssl_context *ssl, unsigned update_hs_digest ); + int (*_rom_mbedtls_ssl_fetch_input)( mbedtls_ssl_context *ssl, size_t nb_want ); + int (*_rom_mbedtls_ssl_write_handshake_msg)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_write_record)( mbedtls_ssl_context *ssl, uint8_t force_flush ); + int (*_rom_mbedtls_ssl_flush_output)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_parse_certificate)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_write_certificate)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_parse_change_cipher_spec)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_write_change_cipher_spec)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_parse_finished)( mbedtls_ssl_context *ssl ); + int (*_rom_mbedtls_ssl_write_finished)( mbedtls_ssl_context *ssl ); + void (*_rom_mbedtls_ssl_optimize_checksum)( mbedtls_ssl_context *ssl, const mbedtls_ssl_ciphersuite_t *ciphersuite_info ); + int (*_rom_mbedtls_ssl_psk_derive_premaster)( mbedtls_ssl_context *ssl, mbedtls_key_exchange_type_t key_ex ); + unsigned char (*_rom_mbedtls_ssl_sig_from_pk)( mbedtls_pk_context *pk ); + mbedtls_pk_type_t (*_rom_mbedtls_ssl_pk_alg_from_sig)( unsigned char sig ); + mbedtls_md_type_t (*_rom_mbedtls_ssl_md_alg_from_hash)( unsigned char hash ); + unsigned char (*_rom_mbedtls_ssl_hash_from_md_alg)( int md ); + int (*_rom_mbedtls_ssl_check_curve)( const mbedtls_ssl_context *ssl, mbedtls_ecp_group_id grp_id ); + int (*_rom_mbedtls_ssl_check_sig_hash)( const mbedtls_ssl_context *ssl, mbedtls_md_type_t md ); + void (*_rom_mbedtls_ssl_write_version)( int major, int minor, int transport, unsigned char ver[2] ); + void (*_rom_mbedtls_ssl_read_version)( int *major, int *minor, int transport, const unsigned char ver[2] ); + int (*_rom_mbedtls_ssl_get_key_exchange_md_ssl_tls)( mbedtls_ssl_context *ssl, unsigned char *output, unsigned char *data, size_t data_len ); + int (*_rom_mbedtls_ssl_get_key_exchange_md_tls1_2)( mbedtls_ssl_context *ssl, unsigned char *hash, size_t *hashlen, unsigned char *data, size_t data_len, mbedtls_md_type_t md_alg ); + int (*_rom_mbedtls_ssl_cf_hmac)( mbedtls_md_context_t *ctx, const unsigned char *add_data, size_t add_data_len, const unsigned char *data, size_t data_len_secret, size_t min_data_len, size_t max_data_len, unsigned char *output ); + void (*_rom_mbedtls_ssl_cf_memcpy_offset)( unsigned char *dst, const unsigned char *src_base, size_t offset_secret, size_t offset_min, size_t offset_max, size_t len ); + int (*_rom_mbedtls_x509_crt_parse_der)( mbedtls_x509_crt *chain, const unsigned char *buf, size_t buflen ); + int (*_rom_mbedtls_x509_crt_verify_restartable)( mbedtls_x509_crt *crt, mbedtls_x509_crt *trust_ca, mbedtls_x509_crl *ca_crl, const mbedtls_x509_crt_profile *profile, const char *cn, uint32_t *flags, int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *), void *p_vrfy, mbedtls_x509_crt_restart_ctx *rs_ctx ); + int (*_rom_mbedtls_x509_crt_check_key_usage)( const mbedtls_x509_crt *crt, unsigned int usage ); + int (*_rom_mbedtls_x509_crt_check_extended_key_usage)( const mbedtls_x509_crt *crt, const char *usage_oid, size_t usage_len ); + int (*_rom_mbedtls_x509_crt_is_revoked)( const mbedtls_x509_crt *crt, const mbedtls_x509_crl *crl ); + void (*_rom_mbedtls_x509_crt_init)( mbedtls_x509_crt *crt ); + void (*_rom_mbedtls_x509_crt_free)( mbedtls_x509_crt *crt ); + void (*_rom_mbedtls_x509_crt_restart_init)( mbedtls_x509_crt_restart_ctx *ctx ); + void (*_rom_mbedtls_x509_crt_restart_free)( mbedtls_x509_crt_restart_ctx *ctx ); + int (*_rom_mbedtls_x509_get_name)( unsigned char **p, const unsigned char *end, mbedtls_x509_name *cur ); + int (*_rom_mbedtls_x509_get_alg_null)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *alg ); + int (*_rom_mbedtls_x509_get_alg)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *alg, mbedtls_x509_buf *params ); + int (*_rom_mbedtls_x509_get_rsassa_pss_params)( const mbedtls_x509_buf *params, mbedtls_md_type_t *md_alg, mbedtls_md_type_t *mgf_md, int *salt_len ); + int (*_rom_mbedtls_x509_get_sig)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *sig ); + int (*_rom_mbedtls_x509_get_sig_alg)( const mbedtls_x509_buf *sig_oid, const mbedtls_x509_buf *sig_params, mbedtls_md_type_t *md_alg, mbedtls_pk_type_t *pk_alg, void **sig_opts ); + int (*_rom_mbedtls_x509_get_time)( unsigned char **p, const unsigned char *end, mbedtls_x509_time *t ); + int (*_rom_mbedtls_x509_get_serial)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *serial ); + int (*_rom_mbedtls_x509_get_ext)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *ext, int tag ); + void (*_mbedtls_mutex_init)( mbedtls_threading_mutex_t *mutex ); + void (*_mbedtls_mutex_free)( mbedtls_threading_mutex_t *mutex ); + int (*_mbedtls_mutex_lock)( mbedtls_threading_mutex_t *mutex ); + int (*_mbedtls_mutex_unlock)( mbedtls_threading_mutex_t *mutex ); + bool (*_mbedtls_allow_unsupported_critical_ext)( void ); + const mbedtls_cipher_info_t *(*_mbedtls_cipher_info_from_type)( const mbedtls_cipher_type_t cipher_type ); + const mbedtls_cipher_info_t *(*_mbedtls_cipher_info_from_values)( const mbedtls_cipher_id_t cipher_id, int key_bitlen, const mbedtls_cipher_mode_t mode ); + void (*_mbedtls_cipher_free)( mbedtls_cipher_context_t *ctx ); + int (*_mbedtls_cipher_setup)( mbedtls_cipher_context_t *ctx, const mbedtls_cipher_info_t *cipher_info ); + int (*_mbedtls_cipher_setkey)( mbedtls_cipher_context_t *ctx, const unsigned char *key, int key_bitlen, const mbedtls_operation_t operation ); + int (*_mbedtls_cipher_set_iv)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len ); + int (*_mbedtls_cipher_update)( mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen ); + int (*_mbedtls_cipher_auth_encrypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, unsigned char *tag, size_t tag_len ); + int (*_mbedtls_cipher_auth_decrypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, const unsigned char *tag, size_t tag_len ); + int (*_mbedtls_hardware_poll)( void *data, unsigned char *output, size_t len, size_t *olen ); + const mbedtls_md_info_t *(*_mbedtls_md_info_from_type)( mbedtls_md_type_t md_type ); + int (*_mbedtls_pem_read_buffer)( void *ctx, const char *header, const char *footer, const unsigned char *data, const unsigned char *pwd, size_t pwdlen, size_t *use_len ); + void *(*_mbedtls_calloc)( size_t n, size_t size ); + void (*_mbedtls_free)( void *ptr ); + int (*_mbedtls_sha1_update)( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen ); + int (*_mbedtls_internal_sha1_process)( mbedtls_sha1_context *ctx, const unsigned char data[64] ); + int (*_mbedtls_sha256_update)( mbedtls_sha256_context *ctx, const unsigned char *input, size_t ilen ); + int (*_mbedtls_internal_sha256_process)( mbedtls_sha256_context *ctx, const unsigned char data[64] ); + const int *(*_mbedtls_ssl_list_ciphersuites)( void ); + const mbedtls_ssl_ciphersuite_t *(*_mbedtls_ssl_ciphersuite_from_id)( int ciphersuite_id ); + mbedtls_pk_type_t (*_mbedtls_ssl_get_ciphersuite_sig_pk_alg)( const mbedtls_ssl_ciphersuite_t *info ); + int (*_mbedtls_ssl_ciphersuite_uses_ec)( const mbedtls_ssl_ciphersuite_t *info ); + int (*_mbedtls_ssl_ciphersuite_uses_psk)( const mbedtls_ssl_ciphersuite_t *info ); + int (*_mbedtls_ssl_handshake_server_step)( mbedtls_ssl_context *ssl ); + int (*_mbedtls_ssl_check_cert_usage)( const mbedtls_x509_crt *cert, const mbedtls_ssl_ciphersuite_t *ciphersuite, int cert_endpoint, uint32_t *flags ); + int (*_mbedtls_x509_time_is_past)( const mbedtls_x509_time *to ); + int (*_mbedtls_x509_time_is_future)( const mbedtls_x509_time *from ); +} mbedtls_rom_funcs_t; + +typedef struct mbedtls_rom_eco4_funcs { + // aes module + void (*_rom_mbedtls_aes_init)(mbedtls_aes_context *ctx); + void (*_rom_mbedtls_aes_free)(mbedtls_aes_context *ctx); + void (*_rom_mbedtls_aes_xts_init)(mbedtls_aes_xts_context *ctx); + void (*_rom_mbedtls_aes_xts_free)(mbedtls_aes_xts_context *ctx); + int (*_rom_mbedtls_aes_setkey_enc)(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_setkey_dec)(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_xts_setkey_enc)(mbedtls_aes_xts_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_xts_setkey_dec)(mbedtls_aes_xts_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_crypt_ecb)(mbedtls_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16]); + int (*_rom_mbedtls_aes_crypt_cbc)(mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_xts)(mbedtls_aes_xts_context *ctx, int mode, size_t length, const unsigned char data_unit[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_cfb128)(mbedtls_aes_context *ctx, int mode, size_t length, size_t *iv_off, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_ofb)(mbedtls_aes_context *ctx, size_t length, size_t *iv_off, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_ctr)(mbedtls_aes_context *ctx, size_t length, size_t *nc_off, unsigned char nonce_counter[16], unsigned char stream_block[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_internal_aes_encrypt)(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); + int (*_rom_mbedtls_internal_aes_decrypt)(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); + // md5 module + void (*_rom_mbedtls_md5_init)(mbedtls_md5_context *ctx); + void (*_rom_mbedtls_md5_free)(mbedtls_md5_context *ctx); + void (*_rom_mbedtls_md5_clone)(mbedtls_md5_context *dst, const mbedtls_md5_context *src); + int (*_rom_mbedtls_md5_starts)(mbedtls_md5_context *ctx); + int (*_rom_mbedtls_md5_update)(mbedtls_md5_context *ctx, const unsigned char *input, size_t ilen); + int (*_rom_mbedtls_md5_finish)(mbedtls_md5_context *ctx, unsigned char output[16]); + int (*_rom_mbedtls_md5)(const unsigned char *input, size_t ilen, unsigned char output[16]); + // bignum module + void (*_rom_mbedtls_mpi_init)(mbedtls_mpi *X); + void (*_rom_mbedtls_mpi_free)(mbedtls_mpi *X); + int (*_rom_mbedtls_mpi_grow)(mbedtls_mpi *X, size_t nblimbs); + int (*_rom_mbedtls_mpi_shrink)(mbedtls_mpi *X, size_t nblimbs); + int (*_rom_mbedtls_mpi_copy)(mbedtls_mpi *X, const mbedtls_mpi *Y); + int (*_rom_mbedtls_mpi_safe_cond_assign)(mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned char assign); + int (*_rom_mbedtls_mpi_safe_cond_swap)(mbedtls_mpi *X, mbedtls_mpi *Y, unsigned char swap); + int (*_rom_mbedtls_mpi_lset)(mbedtls_mpi *X, mbedtls_mpi_sint z); + int (*_rom_mbedtls_mpi_get_bit)(const mbedtls_mpi *X, size_t pos); + int (*_rom_mbedtls_mpi_set_bit)(mbedtls_mpi *X, size_t pos, unsigned char val); + size_t (*_rom_mbedtls_mpi_lsb)(const mbedtls_mpi *X); + size_t (*_rom_mbedtls_mpi_bitlen)(const mbedtls_mpi *X); + size_t (*_rom_mbedtls_mpi_size)(const mbedtls_mpi *X); + int (*_rom_mbedtls_mpi_read_binary)(mbedtls_mpi *X, const unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_mpi_write_binary)(const mbedtls_mpi *X, unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_mpi_shift_l)(mbedtls_mpi *X, size_t count); + int (*_rom_mbedtls_mpi_shift_r)(mbedtls_mpi *X, size_t count); + int (*_rom_mbedtls_mpi_cmp_abs)(const mbedtls_mpi *X, const mbedtls_mpi *Y); + int (*_rom_mbedtls_mpi_cmp_mpi)(const mbedtls_mpi *X, const mbedtls_mpi *Y); + int (*_rom_mbedtls_mpi_lt_mpi_ct)(const mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned *ret); + int (*_rom_mbedtls_mpi_cmp_int)(const mbedtls_mpi *X, mbedtls_mpi_sint z); + int (*_rom_mbedtls_mpi_add_abs)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_sub_abs)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_add_mpi)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_sub_mpi)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_add_int)(mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b); + int (*_rom_mbedtls_mpi_sub_int)(mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b); + int (*_rom_mbedtls_mpi_mul_mpi)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_mul_int)(mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_uint b); + int (*_rom_mbedtls_mpi_div_mpi)(mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_div_int)(mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, mbedtls_mpi_sint b); + int (*_rom_mbedtls_mpi_mod_mpi)(mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_mod_int)(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_sint b); + int (*_rom_mbedtls_mpi_exp_mod)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *E, const mbedtls_mpi *N, mbedtls_mpi *prec_RR); + int (*_rom_mbedtls_mpi_fill_random)(mbedtls_mpi *X, size_t size, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_mpi_gcd)(mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B); + int (*_rom_mbedtls_mpi_inv_mod)(mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *N); + int (*_rom_mbedtls_mpi_is_prime_ext)(const mbedtls_mpi *X, int rounds, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + // sha1 sha256 sha512 module + void (*_rom_mbedtls_sha1_init)(mbedtls_sha1_context *ctx); + void (*_rom_mbedtls_sha1_free)(mbedtls_sha1_context *ctx); + void (*_rom_mbedtls_sha1_clone)(mbedtls_sha1_context *dst, const mbedtls_sha1_context *src); + int (*_rom_mbedtls_sha1_starts)(mbedtls_sha1_context *ctx); + int (*_rom_mbedtls_sha1_finish)(mbedtls_sha1_context *ctx, unsigned char output[20]); + int (*_rom_mbedtls_sha1)(const unsigned char *input, size_t ilen, unsigned char output[20]); + void (*_rom_mbedtls_sha256_init)(mbedtls_sha256_context *ctx); + void (*_rom_mbedtls_sha256_free)(mbedtls_sha256_context *ctx); + void (*_rom_mbedtls_sha256_clone)(mbedtls_sha256_context *dst, const mbedtls_sha256_context *src); + int (*_rom_mbedtls_sha256_starts)(mbedtls_sha256_context *ctx, int is224); + int (*_rom_mbedtls_sha256_finish)(mbedtls_sha256_context *ctx, unsigned char *output); + int (*_rom_mbedtls_sha256)(const unsigned char *input, size_t ilen, unsigned char *output, int is224); + void (*_rom_mbedtls_sha512_init)(mbedtls_sha512_context *ctx); + void (*_rom_mbedtls_sha512_free)(mbedtls_sha512_context *ctx); + void (*_rom_mbedtls_sha512_clone)(mbedtls_sha512_context *dst, const mbedtls_sha512_context *src); + int (*_rom_mbedtls_sha512_starts)(mbedtls_sha512_context *ctx, int is384); + int (*_rom_mbedtls_sha512_update)(mbedtls_sha512_context *ctx, const unsigned char *input, size_t ilen); + int (*_rom_mbedtls_sha512_finish)(mbedtls_sha512_context *ctx, unsigned char *output); + int (*_rom_mbedtls_sha512)(const unsigned char *input, size_t ilen, unsigned char *output, int is384); + // ecp module + // asn1 module + int (*_rom_mbedtls_asn1_get_len)(unsigned char **p, const unsigned char *end, size_t *len); + int (*_rom_mbedtls_asn1_get_tag)(unsigned char **p, const unsigned char *end, size_t *len, int tag); + int (*_rom_mbedtls_asn1_get_bool)(unsigned char **p, const unsigned char *end, int *val); + int (*_rom_mbedtls_asn1_get_int)(unsigned char **p, const unsigned char *end, int *val); + int (*_rom_mbedtls_asn1_get_bitstring)(unsigned char **p, const unsigned char *end, mbedtls_asn1_bitstring *bs); + int (*_rom_mbedtls_asn1_get_bitstring_null)(unsigned char **p, const unsigned char *end, size_t *len); + int (*_rom_mbedtls_asn1_get_sequence_of)(unsigned char **p, const unsigned char *end, mbedtls_asn1_sequence *cur, int tag); + int (*_rom_mbedtls_asn1_get_mpi)(unsigned char **p, const unsigned char *end, mbedtls_mpi *X); + int (*_rom_mbedtls_asn1_get_alg)(unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg, mbedtls_asn1_buf *params); + int (*_rom_mbedtls_asn1_get_alg_null)(unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg); + // asn1write module + int (*_rom_mbedtls_asn1_write_len)(unsigned char **p, const unsigned char *start, size_t len); + int (*_rom_mbedtls_asn1_write_tag)(unsigned char **p, const unsigned char *start, unsigned char tag); + int (*_rom_mbedtls_asn1_write_mpi)(unsigned char **p, const unsigned char *start, const mbedtls_mpi *X); + // ccm module + void (*_rom_mbedtls_ccm_init)(mbedtls_ccm_context *ctx); + int (*_rom_mbedtls_ccm_setkey)(mbedtls_ccm_context *ctx, mbedtls_cipher_id_t cipher, const unsigned char *key, unsigned int keybits); + void (*_rom_mbedtls_ccm_free)(mbedtls_ccm_context *ctx); + int (*_rom_mbedtls_ccm_encrypt_and_tag)(mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, unsigned char *output, unsigned char *tag, size_t tag_len); + int (*_rom_mbedtls_ccm_star_encrypt_and_tag)(mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, unsigned char *output, unsigned char *tag, size_t tag_len); + int (*_rom_mbedtls_ccm_auth_decrypt)(mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, unsigned char *output, const unsigned char *tag, size_t tag_len); + int (*_rom_mbedtls_ccm_star_auth_decrypt)(mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, unsigned char *output, const unsigned char *tag, size_t tag_len); + // cipher module + void (*_rom_mbedtls_cipher_init)(mbedtls_cipher_context_t *ctx); + int (*_rom_mbedtls_cipher_set_padding_mode)(mbedtls_cipher_context_t *ctx, mbedtls_cipher_padding_t mode); + int (*_rom_mbedtls_cipher_reset)(mbedtls_cipher_context_t *ctx); + int (*_rom_mbedtls_cipher_finish)(mbedtls_cipher_context_t *ctx, unsigned char *output, size_t *olen); + int (*_rom_mbedtls_cipher_crypt)(mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen); + int (*_rom_mbedtls_cipher_cmac_starts)(mbedtls_cipher_context_t *ctx, const unsigned char *key, size_t keybits); + int (*_rom_mbedtls_cipher_cmac_update)(mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen); + int (*_rom_mbedtls_cipher_cmac_finish)(mbedtls_cipher_context_t *ctx, unsigned char *output); + // ctr drbg module + void (*_rom_mbedtls_ctr_drbg_init)(mbedtls_ctr_drbg_context *ctx); + int (*_rom_mbedtls_ctr_drbg_seed)(mbedtls_ctr_drbg_context *ctx, int (*f_entropy)(void *, unsigned char *, size_t), void *p_entropy, const unsigned char *custom, size_t len); + void (*_rom_mbedtls_ctr_drbg_free)(mbedtls_ctr_drbg_context *ctx); + int (*_rom_mbedtls_ctr_drbg_reseed)(mbedtls_ctr_drbg_context *ctx, const unsigned char *additional, size_t len); + int (*_rom_mbedtls_ctr_drbg_random_with_add)(void *p_rng, unsigned char *output, size_t output_len, const unsigned char *additional, size_t add_len); + int (*_rom_mbedtls_ctr_drbg_random)(void *p_rng, unsigned char *output, size_t output_len); + // base64 module + int (*_rom_mbedtls_base64_decode)(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen); + //*******************************************************************************************************************************************************************// + // aes module + int (*_rom_mbedtls_aes_crypt_cfb8)(mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output); + // md5 module + // bignum module + void (*_rom_mbedtls_mpi_swap)(mbedtls_mpi *X, mbedtls_mpi *Y); + int (*_rom_mbedtls_mpi_read_string)(mbedtls_mpi *X, int radix, const char *s); + int (*_rom_mbedtls_mpi_write_string)(const mbedtls_mpi *X, int radix, char *buf, size_t buflen, size_t *olen); + int (*_rom_mbedtls_mpi_read_binary_le)(mbedtls_mpi *X, const unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_mpi_write_binary_le)(const mbedtls_mpi *X, unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_mpi_random)(mbedtls_mpi *X, mbedtls_mpi_sint min, const mbedtls_mpi *N, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_mpi_gen_prime)(mbedtls_mpi *X, size_t nbits, int flags, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + // ecp module + int (*_rom_mbedtls_ecp_check_budget)(const mbedtls_ecp_group *grp, mbedtls_ecp_restart_ctx *rs_ctx, unsigned ops); + void (*_rom_mbedtls_ecp_set_max_ops)(unsigned max_ops); + int (*_rom_mbedtls_ecp_restart_is_enabled)(void); + mbedtls_ecp_curve_type (*_rom_mbedtls_ecp_get_type)(const mbedtls_ecp_group *grp); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_list)(void); + const mbedtls_ecp_group_id *(*_rom_mbedtls_ecp_grp_id_list)(void); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_grp_id)(mbedtls_ecp_group_id grp_id); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_tls_id)(uint16_t tls_id); + const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_name)(const char *name); + void (*_rom_mbedtls_ecp_point_init)(mbedtls_ecp_point *pt); + void (*_rom_mbedtls_ecp_group_init)(mbedtls_ecp_group *grp); + void (*_rom_mbedtls_ecp_keypair_init)(mbedtls_ecp_keypair *key); + void (*_rom_mbedtls_ecp_point_free)(mbedtls_ecp_point *pt); + void (*_rom_mbedtls_ecp_group_free)(mbedtls_ecp_group *grp); + void (*_rom_mbedtls_ecp_keypair_free)(mbedtls_ecp_keypair *key); + void (*_rom_mbedtls_ecp_restart_init)(mbedtls_ecp_restart_ctx *ctx); + void (*_rom_mbedtls_ecp_restart_free)(mbedtls_ecp_restart_ctx *ctx); + int (*_rom_mbedtls_ecp_copy)(mbedtls_ecp_point *P, const mbedtls_ecp_point *Q); + int (*_rom_mbedtls_ecp_group_copy)(mbedtls_ecp_group *dst, const mbedtls_ecp_group *src); + int (*_rom_mbedtls_ecp_set_zero)(mbedtls_ecp_point *pt); + int (*_rom_mbedtls_ecp_is_zero)(mbedtls_ecp_point *pt); + int (*_rom_mbedtls_ecp_point_cmp)(const mbedtls_ecp_point *P, const mbedtls_ecp_point *Q); + int (*_rom_mbedtls_ecp_point_read_string)(mbedtls_ecp_point *P, int radix, const char *x, const char *y); + int (*_rom_mbedtls_ecp_point_write_binary)(const mbedtls_ecp_group *grp, const mbedtls_ecp_point *P, int format, size_t *olen, unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_ecp_point_read_binary)(const mbedtls_ecp_group *grp, mbedtls_ecp_point *P, const unsigned char *buf, size_t ilen); + int (*_rom_mbedtls_ecp_tls_read_point)(const mbedtls_ecp_group *grp, mbedtls_ecp_point *pt, const unsigned char **buf, size_t len); + int (*_rom_mbedtls_ecp_tls_write_point)(const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt, int format, size_t *olen, unsigned char *buf, size_t blen); + int (*_rom_mbedtls_ecp_group_load)(mbedtls_ecp_group *grp, mbedtls_ecp_group_id id); + int (*_rom_mbedtls_ecp_tls_read_group)(mbedtls_ecp_group *grp, const unsigned char **buf, size_t len); + int (*_rom_mbedtls_ecp_tls_read_group_id)(mbedtls_ecp_group_id *grp, const unsigned char **buf, size_t len); + int (*_rom_mbedtls_ecp_tls_write_group)(const mbedtls_ecp_group *grp, size_t *olen, unsigned char *buf, size_t blen); + int (*_rom_mbedtls_ecp_mul)(mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_mul_restartable)(mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_ecp_restart_ctx *rs_ctx); + int (*_rom_mbedtls_ecp_muladd)(mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q); + int (*_rom_mbedtls_ecp_muladd_restartable)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q, mbedtls_ecp_restart_ctx *rs_ctx); + int (*_rom_mbedtls_ecp_check_pubkey)(const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt); + int (*_rom_mbedtls_ecp_check_privkey)(const mbedtls_ecp_group *grp, const mbedtls_mpi *d); + int (*_rom_mbedtls_ecp_gen_privkey)(const mbedtls_ecp_group *grp, mbedtls_mpi *d, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_gen_keypair_base)(mbedtls_ecp_group *grp, const mbedtls_ecp_point *G, mbedtls_mpi *d, mbedtls_ecp_point *Q, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_gen_keypair)(mbedtls_ecp_group *grp, mbedtls_mpi *d, mbedtls_ecp_point *Q, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_gen_key)(mbedtls_ecp_group_id grp_id, mbedtls_ecp_keypair *key, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_read_key)(mbedtls_ecp_group_id grp_id, mbedtls_ecp_keypair *key, const unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_ecp_write_key_ext)(const mbedtls_ecp_keypair *key, size_t *olen, unsigned char *buf, size_t buflen); + int (*_rom_mbedtls_ecp_check_pub_priv)( const mbedtls_ecp_keypair *pub, const mbedtls_ecp_keypair *prv, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng); + int (*_rom_mbedtls_ecp_export)(const mbedtls_ecp_keypair *key, mbedtls_ecp_group *grp, mbedtls_mpi *d, mbedtls_ecp_point *Q); + // asn1 module + int (*_rom_mbedtls_asn1_get_enum)(unsigned char **p, const unsigned char *end, int *val); + void (*_rom_mbedtls_asn1_sequence_free)(mbedtls_asn1_sequence *seq); + int (*_rom_mbedtls_asn1_traverse_sequence_of)( unsigned char **p, const unsigned char *end, unsigned char tag_must_mask, unsigned char tag_must_val, unsigned char tag_may_mask, unsigned char tag_may_val, int (*cb)(void *ctx, int tag, unsigned char *start, size_t len), void *ctx); + const mbedtls_asn1_named_data *(*_rom_mbedtls_asn1_find_named_data)(const mbedtls_asn1_named_data *list, const char *oid, size_t len); + void (*_rom_mbedtls_asn1_free_named_data_list)(mbedtls_asn1_named_data **head); + void (*_rom_mbedtls_asn1_free_named_data_list_shallow)(mbedtls_asn1_named_data *name); + // asn1write module + int (*_rom_mbedtls_asn1_write_raw_buffer)(unsigned char **p, const unsigned char *start, const unsigned char *buf, size_t size); + int (*_rom_mbedtls_asn1_write_null)(unsigned char **p, const unsigned char *start); + int (*_rom_mbedtls_asn1_write_oid)(unsigned char **p, const unsigned char *start, const char *oid, size_t oid_len); + int (*_rom_mbedtls_asn1_write_algorithm_identifier)(unsigned char **p, const unsigned char *start, const char *oid, size_t oid_len, size_t par_len); + int (*_rom_mbedtls_asn1_write_bool)(unsigned char **p, const unsigned char *start, int boolean); + int (*_rom_mbedtls_asn1_write_int)(unsigned char **p, const unsigned char *start, int val); + int (*_rom_mbedtls_asn1_write_enum)(unsigned char **p, const unsigned char *start, int val); + int (*_rom_mbedtls_asn1_write_tagged_string)(unsigned char **p, const unsigned char *start, int tag, const char *text, size_t text_len); + int (*_rom_mbedtls_asn1_write_printable_string)(unsigned char **p, const unsigned char *start, const char *text, size_t text_len); + int (*_rom_mbedtls_asn1_write_utf8_string)(unsigned char **p, const unsigned char *start, const char *text, size_t text_len); + int (*_rom_mbedtls_asn1_write_ia5_string)(unsigned char **p, const unsigned char *start, const char *text, size_t text_len); + int (*_rom_mbedtls_asn1_write_bitstring)(unsigned char **p, const unsigned char *start, const unsigned char *buf, size_t bits); + int (*_rom_mbedtls_asn1_write_named_bitstring)(unsigned char **p, const unsigned char *start, const unsigned char *buf, size_t bits); + int (*_rom_mbedtls_asn1_write_octet_string)(unsigned char **p, const unsigned char *start, const unsigned char *buf, size_t size); + mbedtls_asn1_named_data *(*_rom_mbedtls_asn1_store_named_data)(mbedtls_asn1_named_data **list, const char *oid, size_t oid_len, const unsigned char *val, size_t val_len); + // ccm module + int (*_rom_mbedtls_ccm_starts)(mbedtls_ccm_context *ctx, int mode, const unsigned char *iv, size_t iv_len); + int (*_rom_mbedtls_ccm_set_lengths)(mbedtls_ccm_context *ctx, size_t total_ad_len, size_t plaintext_len, size_t tag_len); + int (*_rom_mbedtls_ccm_update_ad)(mbedtls_ccm_context *ctx, const unsigned char *ad, size_t ad_len); + int (*_rom_mbedtls_ccm_update)(mbedtls_ccm_context *ctx, const unsigned char *input, size_t input_len, unsigned char *output, size_t output_size, size_t *output_len); + int (*_rom_mbedtls_ccm_finish)(mbedtls_ccm_context *ctx, unsigned char *tag, size_t tag_len); + // cipher module + const int *(*_rom_mbedtls_cipher_list)(void); + const mbedtls_cipher_info_t *(*_rom_mbedtls_cipher_info_from_string)(const char *cipher_name); + const mbedtls_cipher_info_t *(*_rom_mbedtls_cipher_info_from_type)(const mbedtls_cipher_type_t cipher_type); + const mbedtls_cipher_info_t *(*_rom_mbedtls_cipher_info_from_values)(const mbedtls_cipher_id_t cipher_id, int key_bitlen, const mbedtls_cipher_mode_t mode); + void (*_rom_mbedtls_cipher_free)(mbedtls_cipher_context_t *ctx); + int (*_rom_mbedtls_cipher_setup)(mbedtls_cipher_context_t *ctx, const mbedtls_cipher_info_t *cipher_info); + int (*_rom_mbedtls_cipher_setkey)(mbedtls_cipher_context_t *ctx, const unsigned char *key, int key_bitlen, const mbedtls_operation_t operation); + int (*_rom_mbedtls_cipher_set_iv)(mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len); + int (*_rom_mbedtls_cipher_update_ad)(mbedtls_cipher_context_t *ctx, const unsigned char *ad, size_t ad_len); + int (*_rom_mbedtls_cipher_update)(mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen); + int (*_rom_mbedtls_cipher_write_tag)(mbedtls_cipher_context_t *ctx, unsigned char *tag, size_t tag_len); + int (*_rom_mbedtls_cipher_check_tag)(mbedtls_cipher_context_t *ctx, const unsigned char *tag, size_t tag_len); + int (*_rom_mbedtls_cipher_auth_encrypt_ext)(mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t output_len, size_t *olen, size_t tag_len); + int (*_rom_mbedtls_cipher_auth_decrypt_ext)(mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t output_len, size_t *olen, size_t tag_len); + int (*_rom_mbedtls_cipher_cmac_reset)(mbedtls_cipher_context_t *ctx); + int (*_rom_mbedtls_cipher_cmac)(const mbedtls_cipher_info_t *cipher_info, const unsigned char *key, size_t keylen, const unsigned char *input, size_t ilen, unsigned char *output); + int (*_rom_mbedtls_aes_cmac_prf_128)(const unsigned char *key, size_t key_len, const unsigned char *input, size_t in_len, unsigned char output[16]); + // ctr drbg module + void (*_rom_mbedtls_ctr_drbg_set_prediction_resistance)(mbedtls_ctr_drbg_context *ctx, int resistance); + void (*_rom_mbedtls_ctr_drbg_set_entropy_len)(mbedtls_ctr_drbg_context *ctx, size_t len); + int (*_rom_mbedtls_ctr_drbg_set_nonce_len)(mbedtls_ctr_drbg_context *ctx, size_t len); + void (*_rom_mbedtls_ctr_drbg_set_reseed_interval)(mbedtls_ctr_drbg_context *ctx, int interval); + int (*_rom_mbedtls_ctr_drbg_update)(mbedtls_ctr_drbg_context *ctx, const unsigned char *additional, size_t add_len); + // base64 module + int (*_rom_mbedtls_base64_encode)(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen); + // sha1 sha256 sha512 module + int (*_rom_mbedtls_sha1_update)(mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen); + int (*_rom_mbedtls_sha256_update)(mbedtls_sha256_context *ctx, const unsigned char *input, size_t ilen); + // memory calloc free + void *(*_rom_mbedtls_mem_calloc)(size_t n, size_t size); + void (*_rom_mbedtls_mem_free)(void *ptr); +} mbedtls_rom_eco4_funcs_t; + +#define STRUCT_OFFSET_CHECK(x, y, z) _Static_assert((offsetof(x,y)==(z)), "The variables type of "#x" before "#y" should be "#z) +#define STRUCT_SIZE_CHECK(x, y) _Static_assert((sizeof(x)==(y)), "The sizeof "#x" should be "#y) + +#if (!defined(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL)) +#error "CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL" +#endif + +/* platform_util.c */ +#if (defined(MBEDTLS_PLATFORM_ZEROIZE_ALT)) +#error "MBEDTLS_PLATFORM_ZEROIZE_ALT" +#endif + +/* sha1.c */ +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, total, 0); +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, state, 8); +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, buffer, 28); +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, first_block, 92); +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, mode, 96); +STRUCT_OFFSET_CHECK(mbedtls_sha1_context, sha_state, 100); +STRUCT_SIZE_CHECK(mbedtls_sha1_context, 104); +#if (!defined(MBEDTLS_SHA1_C)) || \ + (!defined(MBEDTLS_SHA1_ALT)) || \ + (defined(MBEDTLS_SHA1_PROCESS_ALT)) +#error "MBEDTLS_SHA1_C" +#endif + +/* sha256.c */ +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, total, 0); +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, state, 8); +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, buffer, 40); +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, first_block, 104); +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, mode, 108); +STRUCT_OFFSET_CHECK(mbedtls_sha256_context, sha_state, 112); +STRUCT_SIZE_CHECK(mbedtls_sha256_context, 116); +#if (!defined(MBEDTLS_SHA256_C)) || \ + (!defined(MBEDTLS_SHA256_ALT)) || \ + (defined(MBEDTLS_SHA256_PROCESS_ALT)) || \ + (defined(MBEDTLS_SHA256_SMALLER)) +#error "!MBEDTLS_SHA256_C" +#endif + +/* sha512.c */ +STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(total), 0); +STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(state), 16); +STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(buffer), 80); +STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(is384), 208); +STRUCT_SIZE_CHECK(mbedtls_sha512_context, 216); +#if (!defined(MBEDTLS_SHA512_C)) || \ + (defined(MBEDTLS_SHA512_ALT)) || \ + (defined(MBEDTLS_SHA512_PROCESS_ALT)) +#error "MBEDTLS_SHA256_C" +#endif + +/* aes.c */ +STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(nr), 0); +STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(rk_offset), 4); +STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(buf), 8); +STRUCT_SIZE_CHECK(mbedtls_aes_context, 280); +STRUCT_OFFSET_CHECK(mbedtls_aes_xts_context, MBEDTLS_PRIVATE(crypt), 0); +STRUCT_OFFSET_CHECK(mbedtls_aes_xts_context, MBEDTLS_PRIVATE(tweak), 280); +STRUCT_SIZE_CHECK(mbedtls_aes_xts_context, 560); +#if (defined(MBEDTLS_HAVE_X86)) || \ + (defined(MBEDTLS_HAVE_X86_64)) +#error "MBEDTLS_HAVE_X86" +#endif +#if (!defined(MBEDTLS_AES_C)) || \ + (defined(MBEDTLS_AES_ALT)) || \ + (defined(MBEDTLS_AES_ENCRYPT_ALT)) || \ + (defined(MBEDTLS_AES_DECRYPT_ALT)) || \ + (defined(MBEDTLS_AES_SETKEY_ENC_ALT)) || \ + (defined(MBEDTLS_AES_SETKEY_DEC_ALT)) +#error "MBEDTLS_AES_C" +#endif +#if (!defined(MBEDTLS_AES_ROM_TABLES)) || \ + (defined(MBEDTLS_AES_FEWER_TABLES)) +#error "MBEDTLS_AES_ROM_TABLES" +#endif +#if (!defined(MBEDTLS_CIPHER_MODE_XTS)) || \ + (!defined(MBEDTLS_CIPHER_MODE_CBC)) || \ + (!defined(MBEDTLS_CIPHER_MODE_CFB)) || \ + (!defined(MBEDTLS_CIPHER_MODE_OFB)) || \ + (!defined(MBEDTLS_CIPHER_MODE_CTR)) +#error "MBEDTLS_CIPHER_MODE" +#endif + +/* asn1parse.c asn1write.c */ +STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, tag, 0); +STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, len, 4); +STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, p, 8); +STRUCT_SIZE_CHECK(mbedtls_asn1_buf, 12); +STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, len, 0); +STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, unused_bits, 4); +STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, p, 8); +STRUCT_SIZE_CHECK(mbedtls_asn1_bitstring, 12); +STRUCT_OFFSET_CHECK(mbedtls_asn1_sequence, buf, 0); +STRUCT_OFFSET_CHECK(mbedtls_asn1_sequence, next, 12); +STRUCT_SIZE_CHECK(mbedtls_asn1_sequence, 16); +STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, oid, 0); +STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, val, 12); +STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, next, 24); +STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, MBEDTLS_PRIVATE(next_merged), 28); +STRUCT_SIZE_CHECK(mbedtls_asn1_named_data, 32); +#if (!defined(MBEDTLS_ASN1_PARSE_C)) +#error "MBEDTLS_ASN1_PARSE_C" +#endif +#if (!defined(MBEDTLS_ASN1_WRITE_C)) +#error "MBEDTLS_ASN1_PARSE_C" +#endif + +/* base64.c */ +#if (!defined(MBEDTLS_BASE64_C)) +#error "MBEDTLS_BASE64_C" +#endif + +/* md5.c */ +#if (defined(MBEDTLS_MD2_C)) || \ + (defined(MBEDTLS_MD4_C)) || \ + (!defined(MBEDTLS_MD5_C)) /* || \ + (defined(MBEDTLS_MD5_ALT)) */ +#error "MBEDTLS_MD_C" +#endif +#ifdef CONFIG_MBEDTLS_ROM_MD5 +STRUCT_OFFSET_CHECK(mbedtls_md5_context, total, 0); +STRUCT_OFFSET_CHECK(mbedtls_md5_context, state, 8); +STRUCT_OFFSET_CHECK(mbedtls_md5_context, buffer, 24); +STRUCT_SIZE_CHECK(mbedtls_md5_context, 88); +#else +STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(total), 0); +STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(state), 8); +STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(buffer), 24); +STRUCT_SIZE_CHECK(mbedtls_md5_context, 88); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/mbedtls_rom/threading_alt.h b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/threading_alt.h new file mode 100644 index 000000000..6dc3349da --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/mbedtls_rom/threading_alt.h @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +typedef struct mbedtls_threading_mutex_t { + SemaphoreHandle_t mutex; + /* is_valid is 0 after a failed init or a free, and nonzero after a + * successful init. This field is not considered part of the public + * API of Mbed TLS and may change without notice. */ + char is_valid; +} mbedtls_threading_mutex_t; + +extern void mbedtls_threading_set_alt(void (*mutex_init)(mbedtls_threading_mutex_t *), + void (*mutex_free)(mbedtls_threading_mutex_t *), + int (*mutex_lock)(mbedtls_threading_mutex_t *), + int (*mutex_unlock)(mbedtls_threading_mutex_t *)); diff --git a/components/mbedtls/mbedtls_v3/port/md/esp_md.c b/components/mbedtls/mbedtls_v3/port/md/esp_md.c new file mode 100644 index 000000000..eb54b5d85 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/md/esp_md.c @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "mbedtls/md5.h" +#include "mbedtls/platform_util.h" + +#if defined(MBEDTLS_MD5_ALT) +#include "md/esp_md.h" + +int esp_md5_finish( mbedtls_md5_context *ctx, unsigned char output[16] ) +{ + esp_rom_md5_final(output, ctx); + + + return 0; +} + +int esp_md5_update( mbedtls_md5_context *ctx, const unsigned char *input, size_t ilen ) +{ + esp_rom_md5_update(ctx, input, ilen); + + + return 0; +} + +void esp_md5_init( mbedtls_md5_context *ctx ) +{ + esp_rom_md5_init(ctx); +} + +int esp_md5_starts( mbedtls_md5_context *ctx ) +{ + esp_md5_init(ctx); + return 0; +} + +void esp_md5_free( mbedtls_md5_context *ctx ) +{ + if (ctx == NULL) { + return; + } + + mbedtls_platform_zeroize( ctx, sizeof( mbedtls_md5_context ) ); +} + +int esp_md5_process( mbedtls_md5_context *ctx, const unsigned char data[64] ) +{ + esp_md5_update(ctx, data, 64); + + return 0; +} + +void esp_md5_clone( mbedtls_md5_context *dst, const mbedtls_md5_context *src ) +{ + *dst = *src; +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/net_sockets.c b/components/mbedtls/mbedtls_v3/port/net_sockets.c new file mode 100644 index 000000000..956a48c16 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/net_sockets.c @@ -0,0 +1,458 @@ +/* + * TCP/IP or UDP/IP networking functions + * modified for LWIP support on ESP32 + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2015 Angus Gratton + */ + +#include +#if !defined(MBEDTLS_NET_C) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_calloc calloc +#define mbedtls_free free +#define mbedtls_time time +#define mbedtls_time_t time_t +#endif + +#include "mbedtls/net_sockets.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Prepare for using the sockets interface + */ +static int net_prepare( void ) +{ + return ( 0 ); +} + +/* + * Initialize a context + */ +void mbedtls_net_init( mbedtls_net_context *ctx ) +{ + ctx->fd = -1; +} + +/* + * Initiate a TCP connection with host:port and the given protocol + */ +int mbedtls_net_connect( mbedtls_net_context *ctx, const char *host, const char *port, int proto ) +{ + int ret; + struct addrinfo hints, *addr_list, *cur; + + if ( ( ret = net_prepare() ) != 0 ) { + return ( ret ); + } + + /* Do name resolution with both IPv6 and IPv4 */ + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = proto == MBEDTLS_NET_PROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_protocol = proto == MBEDTLS_NET_PROTO_UDP ? IPPROTO_UDP : IPPROTO_TCP; + + if ( getaddrinfo( host, port, &hints, &addr_list ) != 0 ) { + return ( MBEDTLS_ERR_NET_UNKNOWN_HOST ); + } + + /* Try the sockaddrs until a connection succeeds */ + ret = MBEDTLS_ERR_NET_UNKNOWN_HOST; + for ( cur = addr_list; cur != NULL; cur = cur->ai_next ) { + int fd = socket( cur->ai_family, cur->ai_socktype, cur->ai_protocol ); + + if ( fd < 0 ) { + ret = MBEDTLS_ERR_NET_SOCKET_FAILED; + continue; + } + + if ( connect( fd, cur->ai_addr, cur->ai_addrlen ) == 0 ) { + ctx->fd = fd; // connected! + ret = 0; + break; + } + + close( fd ); + ret = MBEDTLS_ERR_NET_CONNECT_FAILED; + } + + freeaddrinfo( addr_list ); + + return ( ret ); +} + +/* + * Create a listening socket on bind_ip:port + */ +int mbedtls_net_bind( mbedtls_net_context *ctx, const char *bind_ip, const char *port, int proto ) +{ + int ret; + struct addrinfo hints, *addr_list, *cur; + struct sockaddr_storage *serv_addr = NULL; +#if SO_REUSE + int n = 1; +#endif + + if ( ( ret = net_prepare() ) != 0 ) { + return ( ret ); + } + + /* Bind to IPv6 and/or IPv4, but only in the desired protocol */ + memset( &hints, 0, sizeof( hints ) ); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = proto == MBEDTLS_NET_PROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_protocol = proto == MBEDTLS_NET_PROTO_UDP ? IPPROTO_UDP : IPPROTO_TCP; + + if ( getaddrinfo( bind_ip, port, &hints, &addr_list ) != 0 ) { + return ( MBEDTLS_ERR_NET_UNKNOWN_HOST ); + } + + /* Try the sockaddrs until a binding succeeds */ + ret = MBEDTLS_ERR_NET_UNKNOWN_HOST; + for ( cur = addr_list; cur != NULL; cur = cur->ai_next ) { + int fd = socket( cur->ai_family, cur->ai_socktype, cur->ai_protocol ); + if ( fd < 0 ) { + ret = MBEDTLS_ERR_NET_SOCKET_FAILED; + continue; + } + + /*SO_REUSEADDR option dafault is disable in source code(lwip)*/ +#if SO_REUSE + if ( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, + (const char *) &n, sizeof( n ) ) != 0 ) { + close( fd ); + ret = MBEDTLS_ERR_NET_SOCKET_FAILED; + continue; + } +#endif + serv_addr = (struct sockaddr_storage *) cur->ai_addr; +#if CONFIG_LWIP_IPV4 + if (cur->ai_family == AF_INET) { + /*bind interface dafault don't process the addr is 0xffffffff for TCP Protocol*/ + struct sockaddr_in *p = (struct sockaddr_in *)serv_addr; + p->sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */ + } +#endif // CONFIG_LWIP_IPV4 +#if CONFIG_LWIP_IPV6 + if (cur->ai_family == AF_INET6) { + struct sockaddr_in6 *p = (struct sockaddr_in6 *) serv_addr; + struct in6_addr inaddr_any = IN6ADDR_ANY_INIT; + p->sin6_addr = inaddr_any; + } +#endif // CONFIG_LWIP_IPV6 + + if ( bind( fd, (struct sockaddr *)serv_addr, cur->ai_addrlen ) != 0 ) { + close( fd ); + ret = MBEDTLS_ERR_NET_BIND_FAILED; + continue; + } + + /* Listen only makes sense for TCP */ + if ( proto == MBEDTLS_NET_PROTO_TCP ) { + if ( listen( fd, MBEDTLS_NET_LISTEN_BACKLOG ) != 0 ) { + close( fd ); + ret = MBEDTLS_ERR_NET_LISTEN_FAILED; + continue; + } + } + + /* I we ever get there, it's a success */ + ctx->fd = fd; + ret = 0; + break; + } + + freeaddrinfo( addr_list ); + + return ( ret ); + +} + +/* + * Check if the requested operation would be blocking on a non-blocking socket + * and thus 'failed' with a negative return value. + * + * Note: on a blocking socket this function always returns 0! + */ +static int net_would_block( const mbedtls_net_context *ctx ) +{ + int error = errno; + + switch ( errno = error ) { +#if defined EAGAIN + case EAGAIN: +#endif +#if defined EWOULDBLOCK && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + return ( 1 ); + } + return ( 0 ); +} + +/* + * Accept a connection from a remote client + */ +int mbedtls_net_accept( mbedtls_net_context *bind_ctx, + mbedtls_net_context *client_ctx, + void *client_ip, size_t buf_size, size_t *ip_len ) +{ + int ret; + int type; + + struct sockaddr_storage client_addr; + + socklen_t n = (socklen_t) sizeof( client_addr ); + socklen_t type_len = (socklen_t) sizeof( type ); + + /* Is this a TCP or UDP socket? */ + if ( getsockopt( bind_ctx->fd, SOL_SOCKET, SO_TYPE, + (void *) &type, (socklen_t *) &type_len ) != 0 || + ( type != SOCK_STREAM && type != SOCK_DGRAM ) ) { + return ( MBEDTLS_ERR_NET_ACCEPT_FAILED ); + } + + if ( type == SOCK_STREAM ) { + /* TCP: actual accept() */ + ret = client_ctx->fd = (int) accept( bind_ctx->fd, + (struct sockaddr *) &client_addr, &n ); + } else { + /* UDP: wait for a message, but keep it in the queue */ + char buf[1] = { 0 }; + + ret = recvfrom( bind_ctx->fd, buf, sizeof( buf ), MSG_PEEK, + (struct sockaddr *) &client_addr, &n ); + + } + + if ( ret < 0 ) { + if ( net_would_block( bind_ctx ) != 0 ) { + return ( MBEDTLS_ERR_SSL_WANT_READ ); + } + + return ( MBEDTLS_ERR_NET_ACCEPT_FAILED ); + } + + /* UDP: hijack the listening socket to communicate with the client, + * then bind a new socket to accept new connections */ + if ( type != SOCK_STREAM ) { + struct sockaddr_storage local_addr; + int one = 1; + + if ( connect( bind_ctx->fd, (struct sockaddr *) &client_addr, n ) != 0 ) { + return ( MBEDTLS_ERR_NET_ACCEPT_FAILED ); + } + + client_ctx->fd = bind_ctx->fd; + bind_ctx->fd = -1; /* In case we exit early */ + + n = sizeof( struct sockaddr_storage ); + if ( getsockname( client_ctx->fd, + (struct sockaddr *) &local_addr, &n ) != 0 || + ( bind_ctx->fd = (int) socket( local_addr.ss_family, + SOCK_DGRAM, IPPROTO_UDP ) ) < 0 || + setsockopt( bind_ctx->fd, SOL_SOCKET, SO_REUSEADDR, + (const char *) &one, sizeof( one ) ) != 0 ) { + return ( MBEDTLS_ERR_NET_SOCKET_FAILED ); + } + + if ( bind( bind_ctx->fd, (struct sockaddr *) &local_addr, n ) != 0 ) { + return ( MBEDTLS_ERR_NET_BIND_FAILED ); + } + } + + if ( client_ip != NULL ) { +#ifdef CONFIG_LWIP_IPV4 + if( client_addr.ss_family == AF_INET ) + { + struct sockaddr_in *addr4 = (struct sockaddr_in *) &client_addr; + *ip_len = sizeof( addr4->sin_addr.s_addr ); + + if ( buf_size < *ip_len ) { + return ( MBEDTLS_ERR_NET_BUFFER_TOO_SMALL ); + } + + memcpy( client_ip, &addr4->sin_addr.s_addr, *ip_len ); + } +#endif // CONFIG_LWIP_IPV4 +#ifdef CONFIG_LWIP_IPV6 + if( client_addr.ss_family == AF_INET6 ) + { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &client_addr; + *ip_len = sizeof( addr6->sin6_addr.s6_addr ); + + if( buf_size < *ip_len ) { + return( MBEDTLS_ERR_NET_BUFFER_TOO_SMALL ); + } + + memcpy( client_ip, &addr6->sin6_addr.s6_addr, *ip_len); + } +#endif // CONFIG_LWIP_IPV6 + } + + return ( 0 ); +} + +/* + * Set the socket blocking or non-blocking + */ +int mbedtls_net_set_block( mbedtls_net_context *ctx ) +{ + return ( fcntl( ctx->fd, F_SETFL, fcntl( ctx->fd, F_GETFL, 0 ) & ~O_NONBLOCK ) ); +} + +int mbedtls_net_set_nonblock( mbedtls_net_context *ctx ) +{ + return ( fcntl( ctx->fd, F_SETFL, fcntl( ctx->fd, F_GETFL, 0 ) | O_NONBLOCK ) ); +} + +/* + * Portable usleep helper + */ +void mbedtls_net_usleep( unsigned long usec ) +{ + struct timeval tv; + tv.tv_sec = usec / 1000000; + tv.tv_usec = usec % 1000000; + select( 0, NULL, NULL, NULL, &tv ); +} + +/* + * Read at most 'len' characters + */ +int mbedtls_net_recv( void *ctx, unsigned char *buf, size_t len ) +{ + int ret; + int fd = ((mbedtls_net_context *) ctx)->fd; + + if ( fd < 0 ) { + return ( MBEDTLS_ERR_NET_INVALID_CONTEXT ); + } + + ret = (int) read( fd, buf, len ); + + if ( ret < 0 ) { + if ( net_would_block( ctx ) != 0 ) { + return ( MBEDTLS_ERR_SSL_WANT_READ ); + } + + if ( errno == EPIPE || errno == ECONNRESET ) { + return ( MBEDTLS_ERR_NET_CONN_RESET ); + } + + if ( errno == EINTR ) { + return ( MBEDTLS_ERR_SSL_WANT_READ ); + } + + return ( MBEDTLS_ERR_NET_RECV_FAILED ); + } + + return ( ret ); +} + +/* + * Read at most 'len' characters, blocking for at most 'timeout' ms + */ +int mbedtls_net_recv_timeout( void *ctx, unsigned char *buf, size_t len, + uint32_t timeout ) +{ + int ret; + struct timeval tv; + fd_set read_fds; + int fd = ((mbedtls_net_context *) ctx)->fd; + + if ( fd < 0 ) { + return ( MBEDTLS_ERR_NET_INVALID_CONTEXT ); + } + + FD_ZERO( &read_fds ); + FD_SET( fd, &read_fds ); + + tv.tv_sec = timeout / 1000; + tv.tv_usec = ( timeout % 1000 ) * 1000; + + ret = select( fd + 1, &read_fds, NULL, NULL, timeout == 0 ? NULL : &tv ); + + /* Zero fds ready means we timed out */ + if ( ret == 0 ) { + return ( MBEDTLS_ERR_SSL_TIMEOUT ); + } + + if ( ret < 0 ) { + if ( errno == EINTR ) { + return ( MBEDTLS_ERR_SSL_WANT_READ ); + } + + return ( MBEDTLS_ERR_NET_RECV_FAILED ); + } + + /* This call will not block */ + return ( mbedtls_net_recv( ctx, buf, len ) ); +} + +/* + * Write at most 'len' characters + */ +int mbedtls_net_send( void *ctx, const unsigned char *buf, size_t len ) +{ + int ret; + int fd = ((mbedtls_net_context *) ctx)->fd; + + if ( fd < 0 ) { + return ( MBEDTLS_ERR_NET_INVALID_CONTEXT ); + } + + ret = (int) write( fd, buf, len ); + + if ( ret < 0 ) { + if ( net_would_block( ctx ) != 0 ) { + return ( MBEDTLS_ERR_SSL_WANT_WRITE ); + } + + if ( errno == EPIPE || errno == ECONNRESET ) { + return ( MBEDTLS_ERR_NET_CONN_RESET ); + } + + if ( errno == EINTR ) { + return ( MBEDTLS_ERR_SSL_WANT_WRITE ); + } + + return ( MBEDTLS_ERR_NET_SEND_FAILED ); + } + + return ( ret ); +} + +/* + * Gracefully close the connection + */ +void mbedtls_net_free( mbedtls_net_context *ctx ) +{ + if ( ctx->fd == -1) { + return; + } + + shutdown( ctx->fd, 2); + close(ctx->fd); + + ctx->fd = -1; +} + +#endif /* MBEDTLS_NET_C */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha1.c b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha1.c new file mode 100644 index 000000000..1e73fe5d9 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha1.c @@ -0,0 +1,220 @@ +/* + * SHA-1 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include + +#if defined(MBEDTLS_SHA1_C) && defined(MBEDTLS_SHA1_ALT) + +#include "mbedtls/sha1.h" + +#include +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_block.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = (unsigned char *)v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} +#endif + +void mbedtls_sha1_init( mbedtls_sha1_context *ctx ) +{ + assert(ctx != NULL); + + memset( ctx, 0, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_free( mbedtls_sha1_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + mbedtls_zeroize( ctx, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_clone( mbedtls_sha1_context *dst, + const mbedtls_sha1_context *src ) +{ + memcpy(dst, src, sizeof(mbedtls_sha1_context)); +} + +/* + * SHA-1 context setup + */ +int mbedtls_sha1_starts( mbedtls_sha1_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + memset( ctx, 0, sizeof( mbedtls_sha1_context ) ); + ctx->mode = SHA1; + + return 0; +} + +static void esp_internal_sha_update_state(mbedtls_sha1_context *ctx) +{ + if (ctx->sha_state == ESP_SHA1_STATE_INIT) { + ctx->first_block = true; + ctx->sha_state = ESP_SHA1_STATE_IN_PROCESS; + } else if (ctx->sha_state == ESP_SHA1_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } +} + +static void esp_internal_sha1_block_process(mbedtls_sha1_context *ctx, const uint8_t *data) +{ + esp_sha_block(SHA1, data, ctx->first_block); + + if (ctx->first_block) { + ctx->first_block = false; + } +} + +int mbedtls_internal_sha1_process( mbedtls_sha1_context *ctx, const unsigned char data[64] ) +{ + esp_sha_acquire_hardware(); + esp_internal_sha_update_state(ctx); + esp_sha_block(ctx->mode, data, ctx->first_block); + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + return 0; +} + +int mbedtls_sha1_update( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen ) +{ + size_t fill; + uint32_t left, local_len = 0; + + if ( !ilen || (input == NULL)) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + input += fill; + ilen -= fill; + left = 0; + local_len = 64; + } + + if ( (ilen >= 64) || local_len) { + + esp_sha_acquire_hardware(); + + esp_internal_sha_update_state(ctx); + + /* First process buffered block, if any */ + if ( local_len ) { + esp_internal_sha1_block_process(ctx, ctx->buffer); + } + + while ( ilen >= 64 ) { + esp_internal_sha1_block_process(ctx, input); + + input += 64; + ilen -= 64; + } + + esp_sha_read_digest_state(SHA1, ctx->state); + + esp_sha_release_hardware(); + + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen); + } + return 0; +} + +static const unsigned char sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +* SHA-1 final digest + */ +int mbedtls_sha1_finish( mbedtls_sha1_context *ctx, unsigned char output[20] ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + + if ( ( ret = mbedtls_sha1_update( ctx, sha1_padding, padn ) ) != 0 ) { + return ret; + } + if ( ( ret = mbedtls_sha1_update( ctx, msglen, 8 ) ) != 0 ) { + return ret; + } + + memcpy(output, ctx->state, 20); + + return ret; +} + +#endif /* MBEDTLS_SHA1_C && MBEDTLS_SHA1_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha256.c b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha256.c new file mode 100644 index 000000000..eb456afda --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha256.c @@ -0,0 +1,239 @@ +/* + * SHA-256 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-256 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA256_C) && defined(MBEDTLS_SHA256_ALT) + +#include "mbedtls/sha256.h" + +#include +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_block.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +do { \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} while( 0 ) +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +do { \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} while( 0 ) +#endif + +void mbedtls_sha256_init( mbedtls_sha256_context *ctx ) +{ + assert(ctx != NULL); + + memset( ctx, 0, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_free( mbedtls_sha256_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + mbedtls_zeroize( ctx, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_clone( mbedtls_sha256_context *dst, + const mbedtls_sha256_context *src ) +{ + *dst = *src; +} + +/* + * SHA-256 context setup + */ +int mbedtls_sha256_starts( mbedtls_sha256_context *ctx, int is224 ) +{ + memset( ctx, 0, sizeof( mbedtls_sha256_context ) ); + + if ( is224 ) { + ctx->mode = SHA2_224; + } else { + ctx->mode = SHA2_256; + } + + return 0; +} + +static void esp_internal_sha_update_state(mbedtls_sha256_context *ctx) +{ + if (ctx->sha_state == ESP_SHA256_STATE_INIT) { + ctx->first_block = true; + ctx->sha_state = ESP_SHA256_STATE_IN_PROCESS; + } else if (ctx->sha_state == ESP_SHA256_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } +} + +static void esp_internal_sha256_block_process(mbedtls_sha256_context *ctx, const uint8_t *data) +{ + esp_sha_block(ctx->mode, data, ctx->first_block); + + if (ctx->first_block) { + ctx->first_block = false; + } +} + +int mbedtls_internal_sha256_process( mbedtls_sha256_context *ctx, const unsigned char data[64] ) +{ + esp_sha_acquire_hardware(); + esp_internal_sha_update_state(ctx); + esp_sha_block(ctx->mode, data, ctx->first_block); + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + return 0; +} + +/* + * SHA-256 process buffer + */ +int mbedtls_sha256_update( mbedtls_sha256_context *ctx, const unsigned char *input, + size_t ilen ) +{ + size_t fill; + uint32_t left, local_len = 0; + + if ( ilen == 0 ) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + /* Check if any data pending from previous call to this API */ + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + input += fill; + ilen -= fill; + left = 0; + local_len = 64; + } + + if ( (ilen >= 64) || local_len) { + + esp_sha_acquire_hardware(); + + esp_internal_sha_update_state(ctx); + + /* First process buffered block, if any */ + if ( local_len ) { + esp_internal_sha256_block_process(ctx, ctx->buffer); + } + + while ( ilen >= 64 ) { + esp_internal_sha256_block_process(ctx, input); + + input += 64; + ilen -= 64; + } + esp_sha_read_digest_state(ctx->mode, ctx->state); + + esp_sha_release_hardware(); + + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen); + } + + return 0; +} + +static const unsigned char sha256_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-256 final digest + */ +int mbedtls_sha256_finish( mbedtls_sha256_context *ctx, unsigned char *output ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + if ( ( ret = mbedtls_sha256_update( ctx, sha256_padding, padn ) ) != 0 ) { + return ret; + } + + if ( ( ret = mbedtls_sha256_update( ctx, msglen, 8 ) ) != 0 ) { + return ret; + } + + memcpy(output, ctx->state, 32); + + return ret; +} + +#endif /* MBEDTLS_SHA256_C && MBEDTLS_SHA256_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha512.c b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha512.c new file mode 100644 index 000000000..c36e01dfa --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/block/esp_sha512.c @@ -0,0 +1,290 @@ +/* + * SHA-512 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-512 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA512_C) && defined(MBEDTLS_SHA512_ALT) + +#include "mbedtls/sha512.h" + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define UL64(x) x##ui64 +#else +#define UL64(x) x##ULL +#endif + +#include +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_block.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 64-bit integer manipulation macros (big endian) + */ +#ifndef PUT_UINT64_BE +#define PUT_UINT64_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 56 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 48 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 40 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 32 ); \ + (b)[(i) + 4] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 5] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 6] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 7] = (unsigned char) ( (n) ); \ +} +#endif /* PUT_UINT64_BE */ + +void esp_sha512_set_mode(mbedtls_sha512_context *ctx, esp_sha_type type) +{ + switch (type) { + case SHA2_384: + case SHA2_512224: + case SHA2_512256: + case SHA2_512T: + ctx->mode = type; + break; + default: + ctx->mode = SHA2_512; + break; + } +} + + +/* For SHA512/t mode the intial hash value will depend on t */ +void esp_sha512_set_t( mbedtls_sha512_context *ctx, uint16_t t_val) +{ + ctx->t_val = t_val; +} + +void mbedtls_sha512_init( mbedtls_sha512_context *ctx ) +{ + assert(ctx != NULL); + + memset( ctx, 0, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_free( mbedtls_sha512_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_clone( mbedtls_sha512_context *dst, + const mbedtls_sha512_context *src ) +{ + memcpy(dst, src, sizeof(mbedtls_sha512_context)); +} + +/* + * SHA-512 context setup + */ +int mbedtls_sha512_starts( mbedtls_sha512_context *ctx, int is384 ) +{ + mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) ); + + if ( is384 ) { + ctx->mode = SHA2_384; + } else { + ctx->mode = SHA2_512; + } + + return 0; +} + +static int esp_internal_sha_update_state(mbedtls_sha512_context *ctx) +{ + if (ctx->sha_state == ESP_SHA512_STATE_INIT) { + if (ctx->mode == SHA2_512T) { + int ret = -1; + if ((ret = esp_sha_512_t_init_hash(ctx->t_val)) != 0) { + return ret; + } + ctx->first_block = false; + } else { + ctx->first_block = true; + } + ctx->sha_state = ESP_SHA512_STATE_IN_PROCESS; + + } else if (ctx->sha_state == ESP_SHA512_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } + return 0; +} + +static void esp_internal_sha512_block_process(mbedtls_sha512_context *ctx, const uint8_t *data) +{ + esp_sha_block(ctx->mode, data, ctx->first_block); + + if (ctx->first_block) { + ctx->first_block = false; + } +} + +int mbedtls_internal_sha512_process( mbedtls_sha512_context *ctx, const unsigned char data[128] ) +{ + int ret = -1; + esp_sha_acquire_hardware(); + + ret = esp_internal_sha_update_state(ctx); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_block(ctx->mode, data, ctx->first_block); + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + return ret; +} + +/* + * SHA-512 process buffer + */ +int mbedtls_sha512_update( mbedtls_sha512_context *ctx, const unsigned char *input, + size_t ilen ) +{ + int ret; + size_t fill; + unsigned int left, len, local_len = 0; + + if ( ilen == 0 ) { + return 0; + } + + left = (unsigned int) (ctx->total[0] & 0x7F); + fill = 128 - left; + + ctx->total[0] += (uint64_t) ilen; + + if ( ctx->total[0] < (uint64_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + input += fill; + ilen -= fill; + left = 0; + local_len = 128; + } + + + if ( len || local_len) { + + esp_sha_acquire_hardware(); + + int ret = esp_internal_sha_update_state(ctx); + + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + /* First process buffered block, if any */ + if ( local_len ) { + esp_internal_sha256_block_process(ctx, ctx->buffer); + } + + while ( ilen >= 128 ) { + esp_internal_sha256_block_process(ctx, input); + + input += 64; + ilen -= 64; + } + esp_sha_read_digest_state(ctx->mode, ctx->state); + + esp_sha_release_hardware(); + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen); + } + + return 0; +} + +static const unsigned char sha512_padding[128] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-512 final digest + */ +int mbedtls_sha512_finish( mbedtls_sha512_context *ctx, unsigned char *output ) +{ + int ret = -1; + size_t last, padn; + uint64_t high, low; + unsigned char msglen[16]; + + high = ( ctx->total[0] >> 61 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT64_BE( high, msglen, 0 ); + PUT_UINT64_BE( low, msglen, 8 ); + + last = (size_t)( ctx->total[0] & 0x7F ); + padn = ( last < 112 ) ? ( 112 - last ) : ( 240 - last ); + + if ( ( ret = mbedtls_sha512_update( ctx, sha512_padding, padn ) ) != 0 ) { + return ret; + } + + if ( ( ret = mbedtls_sha512_update( ctx, msglen, 16 ) ) != 0 ) { + return ret; + } + + if (ctx->mode == SHA2_384) { + memcpy(output, ctx->state, 48); + } else { + memcpy(output, ctx->state, 64); + } + + return ret; +} + +#endif /* MBEDTLS_SHA512_C && MBEDTLS_SHA512_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/block/sha.c b/components/mbedtls/mbedtls_v3/port/sha/block/sha.c new file mode 100644 index 000000000..9cffe5e89 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/block/sha.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "soc/soc_caps.h" +#include "soc/periph_defs.h" +#include "esp_private/periph_ctrl.h" + +#include "sha/sha_block.h" +#include "hal/sha_hal.h" + + +static _lock_t s_sha_lock; + +void esp_sha_write_digest_state(esp_sha_type sha_type, void *digest_state) +{ + sha_hal_write_digest(sha_type, digest_state); +} + +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state) +{ + sha_hal_read_digest(sha_type, digest_state); +} + +/* Return block size (in bytes) for a given SHA type */ +inline static size_t block_length(esp_sha_type type) +{ + switch (type) { + case SHA1: + case SHA2_224: + case SHA2_256: + return 64; +#if SOC_SHA_SUPPORT_SHA384 + case SHA2_384: +#endif +#if SOC_SHA_SUPPORT_SHA512 + case SHA2_512: +#endif +#if SOC_SHA_SUPPORT_SHA512_T + case SHA2_512224: + case SHA2_512256: + case SHA2_512T: +#endif + return 128; + default: + return 0; + } +} + + +/* Lock the SHA peripheral and then enable it */ +void esp_sha_acquire_hardware() +{ + _lock_acquire(&s_sha_lock); /* Released when releasing hw with esp_sha_release_hardware() */ + periph_module_enable(PERIPH_SHA_MODULE); +} + +/* Disable SHA peripheral block and then release it */ +void esp_sha_release_hardware() +{ + periph_module_disable(PERIPH_SHA_MODULE); + _lock_release(&s_sha_lock); +} + + +void esp_sha_block(esp_sha_type sha_type, const void *data_block, bool is_first_block) +{ + sha_hal_hash_block(sha_type, data_block, block_length(sha_type) / 4, is_first_block); +} diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha1.c b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha1.c new file mode 100644 index 000000000..409a1d1d5 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha1.c @@ -0,0 +1,220 @@ +/* + * SHA-1 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include + +#if defined(MBEDTLS_SHA1_C) && defined(MBEDTLS_SHA1_ALT) + +#include "mbedtls/sha1.h" + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_dma.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = (unsigned char *)v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} +#endif + +void mbedtls_sha1_init( mbedtls_sha1_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_free( mbedtls_sha1_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + mbedtls_zeroize( ctx, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_clone( mbedtls_sha1_context *dst, + const mbedtls_sha1_context *src ) +{ + memcpy(dst, src, sizeof(mbedtls_sha1_context)); +} + +/* + * SHA-1 context setup + */ +int mbedtls_sha1_starts( mbedtls_sha1_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + memset( ctx, 0, sizeof( mbedtls_sha1_context ) ); + ctx->mode = SHA1; + + return 0; +} + +static void esp_internal_sha_update_state(mbedtls_sha1_context *ctx) +{ + if (ctx->sha_state == ESP_SHA1_STATE_INIT) { + ctx->first_block = true; + ctx->sha_state = ESP_SHA1_STATE_IN_PROCESS; + } else if (ctx->sha_state == ESP_SHA1_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } +} + +static int esp_internal_sha1_dma_process(mbedtls_sha1_context *ctx, + const uint8_t *data, size_t len, + uint8_t *buf, size_t buf_len) +{ + return esp_sha_dma(SHA1, data, len, buf, buf_len, ctx->first_block); +} + +int mbedtls_internal_sha1_process( mbedtls_sha1_context *ctx, const unsigned char data[64] ) +{ + int ret = -1; + esp_sha_acquire_hardware(); + esp_internal_sha_update_state(ctx); + + ret = esp_sha_dma(ctx->mode, data, 64, 0, 0, ctx->first_block); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + return ret; +} + +int mbedtls_sha1_update( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen ) +{ + size_t fill; + uint32_t left, len, local_len = 0; + + if ( !ilen || (input == NULL)) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + input += fill; + ilen -= fill; + left = 0; + local_len = 64; + } + + len = (ilen / 64) * 64; + if ( len || local_len) { + + esp_sha_acquire_hardware(); + + esp_internal_sha_update_state(ctx); + + int ret = esp_internal_sha1_dma_process(ctx, input, len, ctx->buffer, local_len); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(SHA1, ctx->state); + + esp_sha_release_hardware(); + + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input + len, ilen - len ); + } + + return 0; +} + +static const unsigned char sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +* SHA-1 final digest + */ +int mbedtls_sha1_finish( mbedtls_sha1_context *ctx, unsigned char output[20] ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + + if ( ( ret = mbedtls_sha1_update( ctx, sha1_padding, padn ) ) != 0 ) { + return ret; + } + if ( ( ret = mbedtls_sha1_update( ctx, msglen, 8 ) ) != 0 ) { + return ret; + } + + memcpy(output, ctx->state, 20); + + return ret; +} + +#endif /* MBEDTLS_SHA1_C && MBEDTLS_SHA1_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha256.c b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha256.c new file mode 100644 index 000000000..b2d33b04a --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha256.c @@ -0,0 +1,230 @@ +/* + * SHA-256 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-256 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA256_C) && defined(MBEDTLS_SHA256_ALT) + +#include "mbedtls/sha256.h" + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_dma.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +do { \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} while( 0 ) +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +do { \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} while( 0 ) +#endif + +void mbedtls_sha256_init( mbedtls_sha256_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_free( mbedtls_sha256_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + mbedtls_zeroize( ctx, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_clone( mbedtls_sha256_context *dst, + const mbedtls_sha256_context *src ) +{ + *dst = *src; +} + +/* + * SHA-256 context setup + */ +int mbedtls_sha256_starts( mbedtls_sha256_context *ctx, int is224 ) +{ + memset( ctx, 0, sizeof( mbedtls_sha256_context ) ); + + if ( is224 ) { + ctx->mode = SHA2_224; + } else { + ctx->mode = SHA2_256; + } + + return 0; +} + +static void esp_internal_sha_update_state(mbedtls_sha256_context *ctx) +{ + if (ctx->sha_state == ESP_SHA256_STATE_INIT) { + ctx->first_block = true; + ctx->sha_state = ESP_SHA256_STATE_IN_PROCESS; + } else if (ctx->sha_state == ESP_SHA256_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } +} + +int mbedtls_internal_sha256_process( mbedtls_sha256_context *ctx, const unsigned char data[64] ) +{ + int ret = -1; + esp_sha_acquire_hardware(); + esp_internal_sha_update_state(ctx); + + ret = esp_sha_dma(ctx->mode, data, 64, 0, 0, ctx->first_block); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + + return ret; +} + +/* + * SHA-256 process buffer + */ +int mbedtls_sha256_update( mbedtls_sha256_context *ctx, const unsigned char *input, + size_t ilen ) +{ + size_t fill; + uint32_t left, len, local_len = 0; + + if ( ilen == 0 ) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + /* Check if any data pending from previous call to this API */ + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + input += fill; + ilen -= fill; + left = 0; + local_len = 64; + } + + len = (ilen / 64) * 64; + + if ( len || local_len) { + esp_sha_acquire_hardware(); + esp_internal_sha_update_state(ctx); + + int ret = esp_sha_dma(ctx->mode, input, len, ctx->buffer, local_len, ctx->first_block); + + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(ctx->mode, ctx->state); + + esp_sha_release_hardware(); + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input + len, ilen - len ); + } + + return 0; +} + +static const unsigned char sha256_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-256 final digest + */ +int mbedtls_sha256_finish( mbedtls_sha256_context *ctx, unsigned char *output ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + if ( ( ret = mbedtls_sha256_update( ctx, sha256_padding, padn ) ) != 0 ) { + return ret; + } + + if ( ( ret = mbedtls_sha256_update( ctx, msglen, 8 ) ) != 0 ) { + return ret; + } + + memcpy(output, ctx->state, 32); + + return ret; +} + +#endif /* MBEDTLS_SHA256_C && MBEDTLS_SHA256_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha512.c b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha512.c new file mode 100644 index 000000000..4e5bfa003 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha512.c @@ -0,0 +1,293 @@ +/* + * SHA-512 implementation with hardware ESP support added. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-512 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA512_C) && defined(MBEDTLS_SHA512_ALT) + +#include "mbedtls/sha512.h" + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define UL64(x) x##ui64 +#else +#define UL64(x) x##ULL +#endif + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_dma.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 64-bit integer manipulation macros (big endian) + */ +#ifndef PUT_UINT64_BE +#define PUT_UINT64_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 56 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 48 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 40 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 32 ); \ + (b)[(i) + 4] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 5] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 6] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 7] = (unsigned char) ( (n) ); \ +} +#endif /* PUT_UINT64_BE */ + +void esp_sha512_set_mode(mbedtls_sha512_context *ctx, esp_sha_type type) +{ + switch (type) { + case SHA2_384: + case SHA2_512224: + case SHA2_512256: + case SHA2_512T: + ctx->mode = type; + break; + default: + ctx->mode = SHA2_512; + break; + } +} + + +/* For SHA512/t mode the intial hash value will depend on t */ +void esp_sha512_set_t( mbedtls_sha512_context *ctx, uint16_t t_val) +{ + ctx->t_val = t_val; +} + +void mbedtls_sha512_init( mbedtls_sha512_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_free( mbedtls_sha512_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_clone( mbedtls_sha512_context *dst, + const mbedtls_sha512_context *src ) +{ + memcpy(dst, src, sizeof(mbedtls_sha512_context)); +} + +/* + * SHA-512 context setup + */ +int mbedtls_sha512_starts( mbedtls_sha512_context *ctx, int is384 ) +{ + mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) ); + + if ( is384 ) { + ctx->mode = SHA2_384; + } else { + ctx->mode = SHA2_512; + } + + return 0; +} + +static int esp_internal_sha_update_state(mbedtls_sha512_context *ctx) +{ + if (ctx->sha_state == ESP_SHA512_STATE_INIT) { + if (ctx->mode == SHA2_512T) { + int ret = -1; + if ((ret = esp_sha_512_t_init_hash(ctx->t_val)) != 0) { + return ret; + } + ctx->first_block = false; + } else { + ctx->first_block = true; + } + ctx->sha_state = ESP_SHA512_STATE_IN_PROCESS; + } else if (ctx->sha_state == ESP_SHA512_STATE_IN_PROCESS) { + ctx->first_block = false; + esp_sha_write_digest_state(ctx->mode, ctx->state); + } + return 0; +} + +static int esp_internal_sha512_dma_process(mbedtls_sha512_context *ctx, + const uint8_t *data, size_t len, + uint8_t *buf, size_t buf_len) +{ + + + return esp_sha_dma(ctx->mode, data, len, buf, buf_len, ctx->first_block); + + +} + +int mbedtls_internal_sha512_process( mbedtls_sha512_context *ctx, const unsigned char data[128] ) +{ + int ret = -1; + esp_sha_acquire_hardware(); + + ret = esp_internal_sha_update_state(ctx); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + ret = esp_internal_sha512_dma_process(ctx, data, 128, 0, 0); + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(ctx->mode, ctx->state); + esp_sha_release_hardware(); + + return ret; + +} + +/* + * SHA-512 process buffer + */ +int mbedtls_sha512_update( mbedtls_sha512_context *ctx, const unsigned char *input, + size_t ilen ) +{ + size_t fill; + unsigned int left, len, local_len = 0; + + if ( ilen == 0 ) { + return 0; + } + + left = (unsigned int) (ctx->total[0] & 0x7F); + fill = 128 - left; + + ctx->total[0] += (uint64_t) ilen; + + if ( ctx->total[0] < (uint64_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + input += fill; + ilen -= fill; + left = 0; + local_len = 128; + } + + len = (ilen / 128) * 128; + + if ( len || local_len) { + + esp_sha_acquire_hardware(); + + int ret = esp_internal_sha_update_state(ctx); + + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + ret = esp_internal_sha512_dma_process(ctx, input, len, ctx->buffer, local_len); + + if (ret != 0) { + esp_sha_release_hardware(); + return ret; + } + + esp_sha_read_digest_state(ctx->mode, ctx->state); + + esp_sha_release_hardware(); + + } + + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input + len, ilen - len ); + } + + return 0; +} + +static const unsigned char sha512_padding[128] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-512 final digest + */ +int mbedtls_sha512_finish( mbedtls_sha512_context *ctx, unsigned char *output ) +{ + int ret = -1; + size_t last, padn; + uint64_t high, low; + unsigned char msglen[16]; + + high = ( ctx->total[0] >> 61 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT64_BE( high, msglen, 0 ); + PUT_UINT64_BE( low, msglen, 8 ); + + last = (size_t)( ctx->total[0] & 0x7F ); + padn = ( last < 112 ) ? ( 112 - last ) : ( 240 - last ); + + if ( ( ret = mbedtls_sha512_update( ctx, sha512_padding, padn ) ) != 0 ) { + return ret; + } + + if ( ( ret = mbedtls_sha512_update( ctx, msglen, 16 ) ) != 0 ) { + return ret; + } + + if (ctx->mode == SHA2_384) { + memcpy(output, ctx->state, 48); + } else { + memcpy(output, ctx->state, 64); + } + + return ret; +} + +#endif /* MBEDTLS_SHA512_C && MBEDTLS_SHA512_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_crypto_dma_impl.c b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_crypto_dma_impl.c new file mode 100644 index 000000000..331751f95 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_crypto_dma_impl.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "esp_sha_dma_priv.h" + +#include "soc/soc_caps.h" +#include "soc/crypto_dma_reg.h" +#include "hal/crypto_dma_ll.h" + + +esp_err_t esp_sha_dma_start(const lldesc_t *input) +{ + crypto_dma_ll_set_mode(CRYPTO_DMA_SHA); + crypto_dma_ll_reset(); + + crypto_dma_ll_outlink_set((intptr_t)input); + crypto_dma_ll_outlink_start(); + + return ESP_OK; +} diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_gdma_impl.c b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_gdma_impl.c new file mode 100644 index 000000000..bf6528304 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/esp_sha_gdma_impl.c @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_sha_dma_priv.h" +#include "esp_crypto_shared_gdma.h" + +esp_err_t esp_sha_dma_start(const lldesc_t *input) +{ + return esp_crypto_shared_gdma_start(input, NULL, GDMA_TRIG_PERIPH_SHA); +} diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/include/esp_sha_dma_priv.h b/components/mbedtls/mbedtls_v3/port/sha/dma/include/esp_sha_dma_priv.h new file mode 100644 index 000000000..fdb758fc3 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/include/esp_sha_dma_priv.h @@ -0,0 +1,38 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "soc/lldesc.h" +#include "soc/soc_caps.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Start the DMA engine + * + * @param input SHA input descriptor (outlink) + * @return + * - ESP_OK: Successfully started the DMA + * - ESP_ERR_INVALID_STATE: No DMA channel available + */ +esp_err_t esp_sha_dma_start(const lldesc_t *input); + + +#ifdef __cplusplus +} +#endif diff --git a/components/mbedtls/mbedtls_v3/port/sha/dma/sha.c b/components/mbedtls/mbedtls_v3/port/sha/dma/sha.c new file mode 100644 index 000000000..0e0977168 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/dma/sha.c @@ -0,0 +1,348 @@ +/* + * ESP hardware accelerated SHA1/256/512 implementation + * based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016-2020, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include +#include +#include + +#include "esp_log.h" +#include "esp_memory_utils.h" +#include "esp_crypto_lock.h" +#include "esp_attr.h" +#include "soc/lldesc.h" +#include "soc/ext_mem_defs.h" +#include "soc/periph_defs.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "esp_private/periph_ctrl.h" +#include "sys/param.h" + +#include "sha/sha_dma.h" +#include "hal/sha_hal.h" +#include "soc/soc_caps.h" +#include "esp_sha_dma_priv.h" + +#if CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32s3/rom/cache.h" +#elif CONFIG_IDF_TARGET_ESP32C2 +#include "esp32c2/rom/cache.h" +#endif + +#if SOC_SHA_GDMA +#define SHA_LOCK() esp_crypto_sha_aes_lock_acquire() +#define SHA_RELEASE() esp_crypto_sha_aes_lock_release() +#elif SOC_SHA_CRYPTO_DMA +#define SHA_LOCK() esp_crypto_dma_lock_acquire() +#define SHA_RELEASE() esp_crypto_dma_lock_release() +#endif + +const static char *TAG = "esp-sha"; +static bool s_check_dma_capable(const void *p); + +/* These are static due to: + * * Must be in DMA capable memory, so stack is not a safe place to put them + * * To avoid having to malloc/free them for every DMA operation + */ +static DRAM_ATTR lldesc_t s_dma_descr_input; +static DRAM_ATTR lldesc_t s_dma_descr_buf; + +void esp_sha_write_digest_state(esp_sha_type sha_type, void *digest_state) +{ + sha_hal_write_digest(sha_type, digest_state); +} + +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state) +{ + sha_hal_read_digest(sha_type, digest_state); +} + +/* Return block size (in bytes) for a given SHA type */ +inline static size_t block_length(esp_sha_type type) +{ + switch (type) { + case SHA1: + case SHA2_224: + case SHA2_256: + return 64; +#if SOC_SHA_SUPPORT_SHA384 + case SHA2_384: +#endif +#if SOC_SHA_SUPPORT_SHA512 + case SHA2_512: +#endif +#if SOC_SHA_SUPPORT_SHA512_T + case SHA2_512224: + case SHA2_512256: + case SHA2_512T: +#endif + return 128; + default: + return 0; + } +} + + +/* Enable SHA peripheral and then lock it */ +void esp_sha_acquire_hardware() +{ + SHA_LOCK(); /* Released when releasing hw with esp_sha_release_hardware() */ + + /* Enable SHA and DMA hardware */ +#if SOC_SHA_CRYPTO_DMA + periph_module_enable(PERIPH_SHA_DMA_MODULE); +#elif SOC_SHA_GDMA + periph_module_enable(PERIPH_SHA_MODULE); +#endif +} + +/* Disable SHA peripheral block and then release it */ +void esp_sha_release_hardware() +{ + /* Disable SHA and DMA hardware */ +#if SOC_SHA_CRYPTO_DMA + periph_module_disable(PERIPH_SHA_DMA_MODULE); +#elif SOC_SHA_GDMA + periph_module_disable(PERIPH_SHA_MODULE); +#endif + + SHA_RELEASE(); +} + +#if SOC_SHA_SUPPORT_SHA512_T +/* The initial hash value for SHA512/t is generated according to the + algorithm described in the TRM, chapter SHA-Accelerator +*/ +int esp_sha_512_t_init_hash(uint16_t t) +{ + uint32_t t_string = 0; + uint8_t t0, t1, t2, t_len; + + if (t == 384) { + ESP_LOGE(TAG, "Invalid t for SHA512/t, t = %u,cannot be 384", t); + return -1; + } + + if (t <= 9) { + t_string = (uint32_t)((1 << 23) | ((0x30 + t) << 24)); + t_len = 0x48; + } else if (t <= 99) { + t0 = t % 10; + t1 = (t / 10) % 10; + t_string = (uint32_t)((1 << 15) | ((0x30 + t0) << 16) | + (((0x30 + t1) << 24))); + t_len = 0x50; + } else if (t <= 512) { + t0 = t % 10; + t1 = (t / 10) % 10; + t2 = t / 100; + t_string = (uint32_t)((1 << 7) | ((0x30 + t0) << 8) | + (((0x30 + t1) << 16) + ((0x30 + t2) << 24))); + t_len = 0x58; + } else { + ESP_LOGE(TAG, "Invalid t for SHA512/t, t = %u, must equal or less than 512", t); + return -1; + } + + sha_hal_sha512_init_hash(t_string, t_len); + + return 0; +} + +#endif //SOC_SHA_SUPPORT_SHA512_T + + +/* Hash the input block by block, using non-DMA mode */ +static void esp_sha_block_mode(esp_sha_type sha_type, const uint8_t *input, uint32_t ilen, + const uint8_t *buf, uint32_t buf_len, bool is_first_block) +{ + size_t blk_len = 0; + size_t blk_word_len = 0; + int num_block = 0; + + blk_len = block_length(sha_type); + blk_word_len = blk_len / 4; + num_block = ilen / blk_len; + + if (buf_len != 0) { + sha_hal_hash_block(sha_type, buf, blk_word_len, is_first_block); + is_first_block = false; + } + + for (int i = 0; i < num_block; i++) { + sha_hal_hash_block(sha_type, input + blk_len * i, blk_word_len, is_first_block); + is_first_block = false; + } +} + + + +static int esp_sha_dma_process(esp_sha_type sha_type, const void *input, uint32_t ilen, + const void *buf, uint32_t buf_len, bool is_first_block); + +/* Performs SHA on multiple blocks at a time using DMA + splits up into smaller operations for inputs that exceed a single DMA list + */ +int esp_sha_dma(esp_sha_type sha_type, const void *input, uint32_t ilen, + const void *buf, uint32_t buf_len, bool is_first_block) +{ + int ret = 0; + unsigned char *dma_cap_buf = NULL; + + if (buf_len > block_length(sha_type)) { + ESP_LOGE(TAG, "SHA DMA buf_len cannot exceed max size for a single block"); + return -1; + } + + /* DMA cannot access memory in flash, hash block by block instead of using DMA */ + if (!s_check_dma_capable(input) && (ilen != 0)) { + esp_sha_block_mode(sha_type, input, ilen, buf, buf_len, is_first_block); + return 0; + } + +#if (CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE) + if (esp_ptr_external_ram(input)) { + Cache_WriteBack_Addr((uint32_t)input, ilen); + } + if (esp_ptr_external_ram(buf)) { + Cache_WriteBack_Addr((uint32_t)buf, buf_len); + } +#endif + + /* Copy to internal buf if buf is in non DMA capable memory */ + if (!s_check_dma_capable(buf) && (buf_len != 0)) { + dma_cap_buf = heap_caps_malloc(sizeof(unsigned char) * buf_len, MALLOC_CAP_8BIT|MALLOC_CAP_DMA|MALLOC_CAP_INTERNAL); + if (dma_cap_buf == NULL) { + ESP_LOGE(TAG, "Failed to allocate buf memory"); + ret = -1; + goto cleanup; + } + memcpy(dma_cap_buf, buf, buf_len); + buf = dma_cap_buf; + } + + uint32_t dma_op_num; + + if (ilen > 0) { + /* Number of DMA operations based on maximum chunk size in single operation */ + dma_op_num = (ilen + SOC_SHA_DMA_MAX_BUFFER_SIZE - 1) / SOC_SHA_DMA_MAX_BUFFER_SIZE; + } else { + /* For zero input length, we must allow at-least 1 DMA operation to see + * if there is any pending data that is yet to be copied out */ + dma_op_num = 1; + } + + /* The max amount of blocks in a single hardware operation is 2^6 - 1 = 63 + Thus we only do a single DMA input list + dma buf list, + which is max 3968/64 + 64/64 = 63 blocks */ + for (int i = 0; i < dma_op_num; i++) { + + int dma_chunk_len = MIN(ilen, SOC_SHA_DMA_MAX_BUFFER_SIZE); + + ret = esp_sha_dma_process(sha_type, input, dma_chunk_len, buf, buf_len, is_first_block); + + if (ret != 0) { + goto cleanup; + } + + ilen -= dma_chunk_len; + input = (uint8_t *)input + dma_chunk_len; + + // Only append buf to the first operation + buf_len = 0; + is_first_block = false; + } + +cleanup: + free(dma_cap_buf); + return ret; +} + + +/* Performs SHA on multiple blocks at a time */ +static esp_err_t esp_sha_dma_process(esp_sha_type sha_type, const void *input, uint32_t ilen, + const void *buf, uint32_t buf_len, bool is_first_block) +{ + int ret = 0; + lldesc_t *dma_descr_head; + size_t num_blks = (ilen + buf_len) / block_length(sha_type); + + memset(&s_dma_descr_input, 0, sizeof(lldesc_t)); + memset(&s_dma_descr_buf, 0, sizeof(lldesc_t)); + + /* DMA descriptor for Memory to DMA-SHA transfer */ + if (ilen) { + s_dma_descr_input.length = ilen; + s_dma_descr_input.size = ilen; + s_dma_descr_input.owner = 1; + s_dma_descr_input.eof = 1; + s_dma_descr_input.buf = (uint8_t *)input; + dma_descr_head = &s_dma_descr_input; + } + /* Check after input to overide head if there is any buf*/ + if (buf_len) { + s_dma_descr_buf.length = buf_len; + s_dma_descr_buf.size = buf_len; + s_dma_descr_buf.owner = 1; + s_dma_descr_buf.eof = 1; + s_dma_descr_buf.buf = (uint8_t *)buf; + dma_descr_head = &s_dma_descr_buf; + } + + /* Link DMA lists */ + if (buf_len && ilen) { + s_dma_descr_buf.eof = 0; + s_dma_descr_buf.empty = (uint32_t)(&s_dma_descr_input); + } + + if (esp_sha_dma_start(dma_descr_head) != ESP_OK) { + ESP_LOGE(TAG, "esp_sha_dma_start failed, no DMA channel available"); + return -1; + } + + sha_hal_hash_dma(sha_type, num_blks, is_first_block); + + sha_hal_wait_idle(); + + return ret; +} + +static bool s_check_dma_capable(const void *p) +{ + bool is_capable = false; +#if CONFIG_SPIRAM + is_capable |= esp_ptr_dma_ext_capable(p); +#endif + is_capable |= esp_ptr_dma_capable(p); + + return is_capable; +} diff --git a/components/mbedtls/mbedtls_v3/port/sha/esp_sha.c b/components/mbedtls/mbedtls_v3/port/sha/esp_sha.c new file mode 100644 index 000000000..4648e4afc --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/esp_sha.c @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "hal/sha_types.h" +#include "soc/soc_caps.h" +#include "esp_log.h" + +#include +#include +#include + +#if SOC_SHA_SUPPORT_PARALLEL_ENG +#include "sha/sha_parallel_engine.h" +#elif SOC_SHA_SUPPORT_DMA +#include "sha/sha_dma.h" +#else +#include "sha/sha_block.h" +#endif + +static const char *TAG = "esp_sha"; + +void esp_sha(esp_sha_type sha_type, const unsigned char *input, size_t ilen, unsigned char *output) +{ + union { +#if SOC_SHA_SUPPORT_SHA1 + mbedtls_sha1_context sha1; +#endif +#if SOC_SHA_SUPPORT_SHA256 + mbedtls_sha256_context sha256; +#endif +#if SOC_SHA_SUPPORT_SHA384 || SOC_SHA_SUPPORT_SHA512 + mbedtls_sha512_context sha512; +#endif + } ctx; + + int ret __attribute__((unused)); + assert(input != NULL && output != NULL); + +#if SOC_SHA_SUPPORT_SHA1 + if (sha_type == SHA1) { + mbedtls_sha1_init(&ctx.sha1); + mbedtls_sha1_starts(&ctx.sha1); + ret = mbedtls_sha1_update(&ctx.sha1, input, ilen); + assert(ret == 0); + ret = mbedtls_sha1_finish(&ctx.sha1, output); + assert(ret == 0); + mbedtls_sha1_free(&ctx.sha1); + return; + } +#endif //SOC_SHA_SUPPORT_SHA1 + +#if SOC_SHA_SUPPORT_SHA256 + if (sha_type == SHA2_256) { + mbedtls_sha256_init(&ctx.sha256); + mbedtls_sha256_starts(&ctx.sha256, 0); + ret = mbedtls_sha256_update(&ctx.sha256, input, ilen); + assert(ret == 0); + ret = mbedtls_sha256_finish(&ctx.sha256, output); + assert(ret == 0); + mbedtls_sha256_free(&ctx.sha256); + return; + } +#endif //SOC_SHA_SUPPORT_SHA256 + +#if SOC_SHA_SUPPORT_SHA384 + if (sha_type == SHA2_384) { + mbedtls_sha512_init(&ctx.sha512); + mbedtls_sha512_starts(&ctx.sha512, 1); + ret = mbedtls_sha512_update(&ctx.sha512, input, ilen); + assert(ret == 0); + ret = mbedtls_sha512_finish(&ctx.sha512, output); + assert(ret == 0); + mbedtls_sha512_free(&ctx.sha512); + return; + } +#endif //SOC_SHA_SUPPORT_SHA384 + +#if SOC_SHA_SUPPORT_SHA512 + if (sha_type == SHA2_512) { + mbedtls_sha512_init(&ctx.sha512); + mbedtls_sha512_starts(&ctx.sha512, 0); + ret = mbedtls_sha512_update(&ctx.sha512, input, ilen); + assert(ret == 0); + ret = mbedtls_sha512_finish(&ctx.sha512, output); + assert(ret == 0); + mbedtls_sha512_free(&ctx.sha512); + return; + } +#endif //SOC_SHA_SUPPORT_SHA512 + + ESP_LOGE(TAG, "SHA type %d not supported", (int)sha_type); + abort(); +} diff --git a/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha1.c b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha1.c new file mode 100644 index 000000000..9790a7273 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha1.c @@ -0,0 +1,423 @@ +/* + * SHA-1 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include + +#if defined(MBEDTLS_SHA1_C) && defined(MBEDTLS_SHA1_ALT) + +#include "mbedtls/sha1.h" + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_parallel_engine.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = (unsigned char *)v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} +#endif + +void mbedtls_sha1_init( mbedtls_sha1_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_free( mbedtls_sha1_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_unlock_engine(SHA1); + } + mbedtls_zeroize( ctx, sizeof( mbedtls_sha1_context ) ); +} + +void mbedtls_sha1_clone( mbedtls_sha1_context *dst, + const mbedtls_sha1_context *src ) +{ + *dst = *src; + + if (src->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + /* Copy hardware digest state out to cloned state, + which will be a software digest. + */ + esp_sha_read_digest_state(SHA1, dst->state); + dst->mode = ESP_MBEDTLS_SHA1_SOFTWARE; + } +} + + +/* + * SHA-1 context setup + */ +int mbedtls_sha1_starts( mbedtls_sha1_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; + + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_unlock_engine(SHA1); + } + ctx->mode = ESP_MBEDTLS_SHA1_UNUSED; + + return 0; +} + + +static void mbedtls_sha1_software_process( mbedtls_sha1_context *ctx, const unsigned char data[64] ) +{ + uint32_t temp, W[16], A, B, C, D, E; + + GET_UINT32_BE( W[ 0], data, 0 ); + GET_UINT32_BE( W[ 1], data, 4 ); + GET_UINT32_BE( W[ 2], data, 8 ); + GET_UINT32_BE( W[ 3], data, 12 ); + GET_UINT32_BE( W[ 4], data, 16 ); + GET_UINT32_BE( W[ 5], data, 20 ); + GET_UINT32_BE( W[ 6], data, 24 ); + GET_UINT32_BE( W[ 7], data, 28 ); + GET_UINT32_BE( W[ 8], data, 32 ); + GET_UINT32_BE( W[ 9], data, 36 ); + GET_UINT32_BE( W[10], data, 40 ); + GET_UINT32_BE( W[11], data, 44 ); + GET_UINT32_BE( W[12], data, 48 ); + GET_UINT32_BE( W[13], data, 52 ); + GET_UINT32_BE( W[14], data, 56 ); + GET_UINT32_BE( W[15], data, 60 ); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define R(t) \ +( \ + temp = W[( t - 3 ) & 0x0F] ^ W[( t - 8 ) & 0x0F] ^ \ + W[( t - 14 ) & 0x0F] ^ W[ t & 0x0F], \ + ( W[t & 0x0F] = S(temp,1) ) \ +) + +#define P(a,b,c,d,e,x) \ +{ \ + e += S(a,5) + F(b,c,d) + K + x; b = S(b,30); \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + +#define F(x,y,z) (z ^ (x & (y ^ z))) +#define K 0x5A827999 + + P( A, B, C, D, E, W[0] ); + P( E, A, B, C, D, W[1] ); + P( D, E, A, B, C, W[2] ); + P( C, D, E, A, B, W[3] ); + P( B, C, D, E, A, W[4] ); + P( A, B, C, D, E, W[5] ); + P( E, A, B, C, D, W[6] ); + P( D, E, A, B, C, W[7] ); + P( C, D, E, A, B, W[8] ); + P( B, C, D, E, A, W[9] ); + P( A, B, C, D, E, W[10] ); + P( E, A, B, C, D, W[11] ); + P( D, E, A, B, C, W[12] ); + P( C, D, E, A, B, W[13] ); + P( B, C, D, E, A, W[14] ); + P( A, B, C, D, E, W[15] ); + P( E, A, B, C, D, R(16) ); + P( D, E, A, B, C, R(17) ); + P( C, D, E, A, B, R(18) ); + P( B, C, D, E, A, R(19) ); + +#undef K +#undef F + +#define F(x,y,z) (x ^ y ^ z) +#define K 0x6ED9EBA1 + + P( A, B, C, D, E, R(20) ); + P( E, A, B, C, D, R(21) ); + P( D, E, A, B, C, R(22) ); + P( C, D, E, A, B, R(23) ); + P( B, C, D, E, A, R(24) ); + P( A, B, C, D, E, R(25) ); + P( E, A, B, C, D, R(26) ); + P( D, E, A, B, C, R(27) ); + P( C, D, E, A, B, R(28) ); + P( B, C, D, E, A, R(29) ); + P( A, B, C, D, E, R(30) ); + P( E, A, B, C, D, R(31) ); + P( D, E, A, B, C, R(32) ); + P( C, D, E, A, B, R(33) ); + P( B, C, D, E, A, R(34) ); + P( A, B, C, D, E, R(35) ); + P( E, A, B, C, D, R(36) ); + P( D, E, A, B, C, R(37) ); + P( C, D, E, A, B, R(38) ); + P( B, C, D, E, A, R(39) ); + +#undef K +#undef F + +#define F(x,y,z) ((x & y) | (z & (x | y))) +#define K 0x8F1BBCDC + + P( A, B, C, D, E, R(40) ); + P( E, A, B, C, D, R(41) ); + P( D, E, A, B, C, R(42) ); + P( C, D, E, A, B, R(43) ); + P( B, C, D, E, A, R(44) ); + P( A, B, C, D, E, R(45) ); + P( E, A, B, C, D, R(46) ); + P( D, E, A, B, C, R(47) ); + P( C, D, E, A, B, R(48) ); + P( B, C, D, E, A, R(49) ); + P( A, B, C, D, E, R(50) ); + P( E, A, B, C, D, R(51) ); + P( D, E, A, B, C, R(52) ); + P( C, D, E, A, B, R(53) ); + P( B, C, D, E, A, R(54) ); + P( A, B, C, D, E, R(55) ); + P( E, A, B, C, D, R(56) ); + P( D, E, A, B, C, R(57) ); + P( C, D, E, A, B, R(58) ); + P( B, C, D, E, A, R(59) ); + +#undef K +#undef F + +#define F(x,y,z) (x ^ y ^ z) +#define K 0xCA62C1D6 + + P( A, B, C, D, E, R(60) ); + P( E, A, B, C, D, R(61) ); + P( D, E, A, B, C, R(62) ); + P( C, D, E, A, B, R(63) ); + P( B, C, D, E, A, R(64) ); + P( A, B, C, D, E, R(65) ); + P( E, A, B, C, D, R(66) ); + P( D, E, A, B, C, R(67) ); + P( C, D, E, A, B, R(68) ); + P( B, C, D, E, A, R(69) ); + P( A, B, C, D, E, R(70) ); + P( E, A, B, C, D, R(71) ); + P( D, E, A, B, C, R(72) ); + P( C, D, E, A, B, R(73) ); + P( B, C, D, E, A, R(74) ); + P( A, B, C, D, E, R(75) ); + P( E, A, B, C, D, R(76) ); + P( D, E, A, B, C, R(77) ); + P( C, D, E, A, B, R(78) ); + P( B, C, D, E, A, R(79) ); + +#undef K +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; +} + + +static int esp_internal_sha1_parallel_engine_process( mbedtls_sha1_context *ctx, const unsigned char data[64], bool read_digest ) +{ + bool first_block = false; + + if (ctx->mode == ESP_MBEDTLS_SHA1_UNUSED) { + /* try to use hardware for this digest */ + if (esp_sha_try_lock_engine(SHA1)) { + ctx->mode = ESP_MBEDTLS_SHA1_HARDWARE; + first_block = true; + } else { + ctx->mode = ESP_MBEDTLS_SHA1_SOFTWARE; + } + } + + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_block(SHA1, data, first_block); + if (read_digest) { + esp_sha_read_digest_state(SHA1, ctx->state); + } + } else { + mbedtls_sha1_software_process(ctx, data); + } + + return 0; +} + + +int mbedtls_internal_sha1_process( mbedtls_sha1_context *ctx, const unsigned char data[64] ) +{ + return esp_internal_sha1_parallel_engine_process(ctx, data, true); +} + + +/* + * SHA-1 process buffer + */ +int mbedtls_sha1_update( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen ) +{ + int ret = -1; + size_t fill; + uint32_t left; + + if ( ilen == 0 ) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + if ( ( ret = esp_internal_sha1_parallel_engine_process( ctx, ctx->buffer, false ) ) != 0 ) { + return ret; + } + + input += fill; + ilen -= fill; + left = 0; + } + + while ( ilen >= 64 ) { + if ( ( ret = esp_internal_sha1_parallel_engine_process( ctx, input, false ) ) != 0 ) { + return ret; + } + + input += 64; + ilen -= 64; + } + + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_read_digest_state(SHA1, ctx->state); + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen ); + } + + return 0; +} + +static const unsigned char sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +* SHA-1 final digest + */ +int mbedtls_sha1_finish( mbedtls_sha1_context *ctx, unsigned char output[20] ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + if ( ( ret = mbedtls_sha1_update( ctx, sha1_padding, padn ) ) != 0 ) { + goto out; + } + if ( ( ret = mbedtls_sha1_update( ctx, msglen, 8 ) ) != 0 ) { + goto out; + } + + /* if state is in hardware, read it out */ + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_read_digest_state(SHA1, ctx->state); + } + + PUT_UINT32_BE( ctx->state[0], output, 0 ); + PUT_UINT32_BE( ctx->state[1], output, 4 ); + PUT_UINT32_BE( ctx->state[2], output, 8 ); + PUT_UINT32_BE( ctx->state[3], output, 12 ); + PUT_UINT32_BE( ctx->state[4], output, 16 ); + +out: + if (ctx->mode == ESP_MBEDTLS_SHA1_HARDWARE) { + esp_sha_unlock_engine(SHA1); + ctx->mode = ESP_MBEDTLS_SHA1_SOFTWARE; + } + + return ret; +} + +#endif /* MBEDTLS_SHA1_C && MBEDTLS_SHA1_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha256.c b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha256.c new file mode 100644 index 000000000..10f6f22fe --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha256.c @@ -0,0 +1,390 @@ +/* + * SHA-256 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-256 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA256_C) && defined(MBEDTLS_SHA256_ALT) + +#include "mbedtls/sha256.h" + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_parallel_engine.h" + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 32-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT32_BE +#define GET_UINT32_BE(n,b,i) \ +do { \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} while( 0 ) +#endif + +#ifndef PUT_UINT32_BE +#define PUT_UINT32_BE(n,b,i) \ +do { \ + (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) ); \ +} while( 0 ) +#endif + +void mbedtls_sha256_init( mbedtls_sha256_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_free( mbedtls_sha256_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_unlock_engine(SHA2_256); + } + mbedtls_zeroize( ctx, sizeof( mbedtls_sha256_context ) ); +} + +void mbedtls_sha256_clone( mbedtls_sha256_context *dst, + const mbedtls_sha256_context *src ) +{ + *dst = *src; + + if (src->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + /* Copy hardware digest state out to cloned state, + which will become a software digest. + */ + esp_sha_read_digest_state(SHA2_256, dst->state); + dst->mode = ESP_MBEDTLS_SHA256_SOFTWARE; + } +} + +/* + * SHA-256 context setup + */ +int mbedtls_sha256_starts( mbedtls_sha256_context *ctx, int is224 ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + if ( is224 == 0 ) { + /* SHA-256 */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; + } else { + /* SHA-224 */ + ctx->state[0] = 0xC1059ED8; + ctx->state[1] = 0x367CD507; + ctx->state[2] = 0x3070DD17; + ctx->state[3] = 0xF70E5939; + ctx->state[4] = 0xFFC00B31; + ctx->state[5] = 0x68581511; + ctx->state[6] = 0x64F98FA7; + ctx->state[7] = 0xBEFA4FA4; + } + + ctx->is224 = is224; + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_unlock_engine(SHA2_256); + } + ctx->mode = ESP_MBEDTLS_SHA256_UNUSED; + return 0; +} + +static const uint32_t K[] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, +}; + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) \ +( \ + W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16] \ +) + +#define P(a,b,c,d,e,f,g,h,x,K) \ +{ \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ +} + + +static void mbedtls_sha256_software_process( mbedtls_sha256_context *ctx, const unsigned char data[64] ) +{ + uint32_t temp1, temp2, W[64]; + uint32_t A[8]; + unsigned int i; + + for ( i = 0; i < 8; i++ ) { + A[i] = ctx->state[i]; + } + +#if defined(MBEDTLS_SHA256_SMALLER) + for ( i = 0; i < 64; i++ ) { + if ( i < 16 ) { + GET_UINT32_BE( W[i], data, 4 * i ); + } else { + R( i ); + } + + P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i], K[i] ); + + temp1 = A[7]; A[7] = A[6]; A[6] = A[5]; A[5] = A[4]; A[4] = A[3]; + A[3] = A[2]; A[2] = A[1]; A[1] = A[0]; A[0] = temp1; + } +#else /* MBEDTLS_SHA256_SMALLER */ + for ( i = 0; i < 16; i++ ) { + GET_UINT32_BE( W[i], data, 4 * i ); + } + + for ( i = 0; i < 16; i += 8 ) { + P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i + 0], K[i + 0] ); + P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[i + 1], K[i + 1] ); + P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[i + 2], K[i + 2] ); + P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], W[i + 3], K[i + 3] ); + P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], W[i + 4], K[i + 4] ); + P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], W[i + 5], K[i + 5] ); + P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], W[i + 6], K[i + 6] ); + P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], W[i + 7], K[i + 7] ); + } + + for ( i = 16; i < 64; i += 8 ) { + P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], R(i + 0), K[i + 0] ); + P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], R(i + 1), K[i + 1] ); + P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], R(i + 2), K[i + 2] ); + P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], R(i + 3), K[i + 3] ); + P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], R(i + 4), K[i + 4] ); + P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], R(i + 5), K[i + 5] ); + P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], R(i + 6), K[i + 6] ); + P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], R(i + 7), K[i + 7] ); + } +#endif /* MBEDTLS_SHA256_SMALLER */ + + for ( i = 0; i < 8; i++ ) { + ctx->state[i] += A[i]; + } +} + + +static int esp_internal_sha256_parallel_engine_process( mbedtls_sha256_context *ctx, const unsigned char data[64], bool read_digest ) +{ + bool first_block = false; + + if (ctx->mode == ESP_MBEDTLS_SHA256_UNUSED) { + /* try to use hardware for this digest */ + if (!ctx->is224 && esp_sha_try_lock_engine(SHA2_256)) { + ctx->mode = ESP_MBEDTLS_SHA256_HARDWARE; + first_block = true; + } else { + ctx->mode = ESP_MBEDTLS_SHA256_SOFTWARE; + } + } + + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_block(SHA2_256, data, first_block); + if (read_digest) { + esp_sha_read_digest_state(SHA2_256, ctx->state); + } + } else { + mbedtls_sha256_software_process(ctx, data); + } + + return 0; +} + + +int mbedtls_internal_sha256_process( mbedtls_sha256_context *ctx, const unsigned char data[64] ) +{ + return esp_internal_sha256_parallel_engine_process(ctx, data, true); +} + + +/* + * SHA-256 process buffer + */ +int mbedtls_sha256_update( mbedtls_sha256_context *ctx, const unsigned char *input, + size_t ilen ) +{ + int ret = -1; + size_t fill; + uint32_t left; + + if ( ilen == 0 ) { + return 0; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (uint32_t) ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if ( ctx->total[0] < (uint32_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + + if ( ( ret = esp_internal_sha256_parallel_engine_process( ctx, ctx->buffer, false ) ) != 0 ) { + return ret; + } + + input += fill; + ilen -= fill; + left = 0; + } + + while ( ilen >= 64 ) { + if ( ( ret = esp_internal_sha256_parallel_engine_process( ctx, input, false ) ) != 0 ) { + return ret; + } + + input += 64; + ilen -= 64; + } + + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_read_digest_state(SHA2_256, ctx->state); + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen ); + } + + return 0; +} + +static const unsigned char sha256_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-256 final digest + */ +int mbedtls_sha256_finish( mbedtls_sha256_context *ctx, unsigned char *output ) +{ + int ret = -1; + uint32_t last, padn; + uint32_t high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_BE( high, msglen, 0 ); + PUT_UINT32_BE( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + if ( ( ret = mbedtls_sha256_update( ctx, sha256_padding, padn ) ) != 0 ) { + goto out; + } + + if ( ( ret = mbedtls_sha256_update( ctx, msglen, 8 ) ) != 0 ) { + goto out; + } + + /* if state is in hardware, read it out */ + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_read_digest_state(SHA2_256, ctx->state); + } + + PUT_UINT32_BE( ctx->state[0], output, 0 ); + PUT_UINT32_BE( ctx->state[1], output, 4 ); + PUT_UINT32_BE( ctx->state[2], output, 8 ); + PUT_UINT32_BE( ctx->state[3], output, 12 ); + PUT_UINT32_BE( ctx->state[4], output, 16 ); + PUT_UINT32_BE( ctx->state[5], output, 20 ); + PUT_UINT32_BE( ctx->state[6], output, 24 ); + + if ( ctx->is224 == 0 ) { + PUT_UINT32_BE( ctx->state[7], output, 28 ); + } + +out: + if (ctx->mode == ESP_MBEDTLS_SHA256_HARDWARE) { + esp_sha_unlock_engine(SHA2_256); + ctx->mode = ESP_MBEDTLS_SHA256_SOFTWARE; + } + + return ret; +} + +#endif /* MBEDTLS_SHA256_C && MBEDTLS_SHA256_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha512.c b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha512.c new file mode 100644 index 000000000..b305cdf16 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/esp_sha512.c @@ -0,0 +1,428 @@ +/* + * SHA-512 implementation with hardware ESP32 support added. + * Uses mbedTLS software implementation for failover when concurrent + * SHA operations are in use. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + */ +/* + * The SHA-512 Secure Hash Standard was published by NIST in 2002. + * + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + */ + +#include + +#if defined(MBEDTLS_SHA512_C) && defined(MBEDTLS_SHA512_ALT) + +#include "mbedtls/sha512.h" + +#if defined(_MSC_VER) || defined(__WATCOMC__) +#define UL64(x) x##ui64 +#else +#define UL64(x) x##ULL +#endif + +#include + +#if defined(MBEDTLS_SELF_TEST) +#if defined(MBEDTLS_PLATFORM_C) +#include "mbedtls/platform.h" +#else +#include +#define mbedtls_printf printf +#endif /* MBEDTLS_PLATFORM_C */ +#endif /* MBEDTLS_SELF_TEST */ + +#include "sha/sha_parallel_engine.h" + +inline static esp_sha_type sha_type(const mbedtls_sha512_context *ctx) +{ + return ctx->is384 ? SHA2_384 : SHA2_512; +} + +/* Implementation that should never be optimized out by the compiler */ +static void mbedtls_zeroize( void *v, size_t n ) +{ + volatile unsigned char *p = v; + while ( n-- ) { + *p++ = 0; + } +} + +/* + * 64-bit integer manipulation macros (big endian) + */ +#ifndef GET_UINT64_BE +#define GET_UINT64_BE(n,b,i) \ +{ \ + (n) = ( (uint64_t) (b)[(i) ] << 56 ) \ + | ( (uint64_t) (b)[(i) + 1] << 48 ) \ + | ( (uint64_t) (b)[(i) + 2] << 40 ) \ + | ( (uint64_t) (b)[(i) + 3] << 32 ) \ + | ( (uint64_t) (b)[(i) + 4] << 24 ) \ + | ( (uint64_t) (b)[(i) + 5] << 16 ) \ + | ( (uint64_t) (b)[(i) + 6] << 8 ) \ + | ( (uint64_t) (b)[(i) + 7] ); \ +} +#endif /* GET_UINT64_BE */ + +#ifndef PUT_UINT64_BE +#define PUT_UINT64_BE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) >> 56 ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 48 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 40 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 32 ); \ + (b)[(i) + 4] = (unsigned char) ( (n) >> 24 ); \ + (b)[(i) + 5] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 6] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 7] = (unsigned char) ( (n) ); \ +} +#endif /* PUT_UINT64_BE */ + +void mbedtls_sha512_init( mbedtls_sha512_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_free( mbedtls_sha512_context *ctx ) +{ + if ( ctx == NULL ) { + return; + } + + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_unlock_engine(sha_type(ctx)); + } + mbedtls_zeroize( ctx, sizeof( mbedtls_sha512_context ) ); +} + +void mbedtls_sha512_clone( mbedtls_sha512_context *dst, + const mbedtls_sha512_context *src ) +{ + *dst = *src; + + if (src->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + /* Copy hardware digest state out to cloned state, + which will be a software digest. + + Always read 512 bits of state, even for SHA-384 + (SHA-384 state is identical to SHA-512, only + digest is truncated.) + */ + esp_sha_read_digest_state(SHA2_512, dst->state); + dst->mode = ESP_MBEDTLS_SHA512_SOFTWARE; + } +} + + +/* + * SHA-512 context setup + */ +int mbedtls_sha512_starts( mbedtls_sha512_context *ctx, int is384 ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + if ( is384 == 0 ) { + /* SHA-512 */ + ctx->state[0] = UL64(0x6A09E667F3BCC908); + ctx->state[1] = UL64(0xBB67AE8584CAA73B); + ctx->state[2] = UL64(0x3C6EF372FE94F82B); + ctx->state[3] = UL64(0xA54FF53A5F1D36F1); + ctx->state[4] = UL64(0x510E527FADE682D1); + ctx->state[5] = UL64(0x9B05688C2B3E6C1F); + ctx->state[6] = UL64(0x1F83D9ABFB41BD6B); + ctx->state[7] = UL64(0x5BE0CD19137E2179); + } else { + /* SHA-384 */ + ctx->state[0] = UL64(0xCBBB9D5DC1059ED8); + ctx->state[1] = UL64(0x629A292A367CD507); + ctx->state[2] = UL64(0x9159015A3070DD17); + ctx->state[3] = UL64(0x152FECD8F70E5939); + ctx->state[4] = UL64(0x67332667FFC00B31); + ctx->state[5] = UL64(0x8EB44A8768581511); + ctx->state[6] = UL64(0xDB0C2E0D64F98FA7); + ctx->state[7] = UL64(0x47B5481DBEFA4FA4); + } + + ctx->is384 = is384; + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_unlock_engine(sha_type(ctx)); + } + ctx->mode = ESP_MBEDTLS_SHA512_UNUSED; + + return 0; +} + +/* + * Round constants + */ +static const uint64_t K[80] = { + UL64(0x428A2F98D728AE22), UL64(0x7137449123EF65CD), + UL64(0xB5C0FBCFEC4D3B2F), UL64(0xE9B5DBA58189DBBC), + UL64(0x3956C25BF348B538), UL64(0x59F111F1B605D019), + UL64(0x923F82A4AF194F9B), UL64(0xAB1C5ED5DA6D8118), + UL64(0xD807AA98A3030242), UL64(0x12835B0145706FBE), + UL64(0x243185BE4EE4B28C), UL64(0x550C7DC3D5FFB4E2), + UL64(0x72BE5D74F27B896F), UL64(0x80DEB1FE3B1696B1), + UL64(0x9BDC06A725C71235), UL64(0xC19BF174CF692694), + UL64(0xE49B69C19EF14AD2), UL64(0xEFBE4786384F25E3), + UL64(0x0FC19DC68B8CD5B5), UL64(0x240CA1CC77AC9C65), + UL64(0x2DE92C6F592B0275), UL64(0x4A7484AA6EA6E483), + UL64(0x5CB0A9DCBD41FBD4), UL64(0x76F988DA831153B5), + UL64(0x983E5152EE66DFAB), UL64(0xA831C66D2DB43210), + UL64(0xB00327C898FB213F), UL64(0xBF597FC7BEEF0EE4), + UL64(0xC6E00BF33DA88FC2), UL64(0xD5A79147930AA725), + UL64(0x06CA6351E003826F), UL64(0x142929670A0E6E70), + UL64(0x27B70A8546D22FFC), UL64(0x2E1B21385C26C926), + UL64(0x4D2C6DFC5AC42AED), UL64(0x53380D139D95B3DF), + UL64(0x650A73548BAF63DE), UL64(0x766A0ABB3C77B2A8), + UL64(0x81C2C92E47EDAEE6), UL64(0x92722C851482353B), + UL64(0xA2BFE8A14CF10364), UL64(0xA81A664BBC423001), + UL64(0xC24B8B70D0F89791), UL64(0xC76C51A30654BE30), + UL64(0xD192E819D6EF5218), UL64(0xD69906245565A910), + UL64(0xF40E35855771202A), UL64(0x106AA07032BBD1B8), + UL64(0x19A4C116B8D2D0C8), UL64(0x1E376C085141AB53), + UL64(0x2748774CDF8EEB99), UL64(0x34B0BCB5E19B48A8), + UL64(0x391C0CB3C5C95A63), UL64(0x4ED8AA4AE3418ACB), + UL64(0x5B9CCA4F7763E373), UL64(0x682E6FF3D6B2B8A3), + UL64(0x748F82EE5DEFB2FC), UL64(0x78A5636F43172F60), + UL64(0x84C87814A1F0AB72), UL64(0x8CC702081A6439EC), + UL64(0x90BEFFFA23631E28), UL64(0xA4506CEBDE82BDE9), + UL64(0xBEF9A3F7B2C67915), UL64(0xC67178F2E372532B), + UL64(0xCA273ECEEA26619C), UL64(0xD186B8C721C0C207), + UL64(0xEADA7DD6CDE0EB1E), UL64(0xF57D4F7FEE6ED178), + UL64(0x06F067AA72176FBA), UL64(0x0A637DC5A2C898A6), + UL64(0x113F9804BEF90DAE), UL64(0x1B710B35131C471B), + UL64(0x28DB77F523047D84), UL64(0x32CAAB7B40C72493), + UL64(0x3C9EBE0A15C9BEBC), UL64(0x431D67C49C100D4C), + UL64(0x4CC5D4BECB3E42B6), UL64(0x597F299CFC657E2A), + UL64(0x5FCB6FAB3AD6FAEC), UL64(0x6C44198C4A475817) +}; + + +static void mbedtls_sha512_software_process( mbedtls_sha512_context *ctx, const unsigned char data[128] ) +{ + int i; + uint64_t temp1, temp2, W[80]; + uint64_t A, B, C, D, E, F, G, H; + +#define SHR(x,n) (x >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (64 - n))) + +#define S0(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7)) +#define S1(x) (ROTR(x,19) ^ ROTR(x,61) ^ SHR(x, 6)) + +#define S2(x) (ROTR(x,28) ^ ROTR(x,34) ^ ROTR(x,39)) +#define S3(x) (ROTR(x,14) ^ ROTR(x,18) ^ ROTR(x,41)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define P(a,b,c,d,e,f,g,h,x,K) \ +{ \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ +} + + for ( i = 0; i < 16; i++ ) { + GET_UINT64_BE( W[i], data, i << 3 ); + } + + for ( ; i < 80; i++ ) { + W[i] = S1(W[i - 2]) + W[i - 7] + + S0(W[i - 15]) + W[i - 16]; + } + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + F = ctx->state[5]; + G = ctx->state[6]; + H = ctx->state[7]; + i = 0; + + do { + P( A, B, C, D, E, F, G, H, W[i], K[i] ); i++; + P( H, A, B, C, D, E, F, G, W[i], K[i] ); i++; + P( G, H, A, B, C, D, E, F, W[i], K[i] ); i++; + P( F, G, H, A, B, C, D, E, W[i], K[i] ); i++; + P( E, F, G, H, A, B, C, D, W[i], K[i] ); i++; + P( D, E, F, G, H, A, B, C, W[i], K[i] ); i++; + P( C, D, E, F, G, H, A, B, W[i], K[i] ); i++; + P( B, C, D, E, F, G, H, A, W[i], K[i] ); i++; + } while ( i < 80 ); + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; + ctx->state[5] += F; + ctx->state[6] += G; + ctx->state[7] += H; +} + + +static int esp_internal_sha512_parallel_engine_process( mbedtls_sha512_context *ctx, const unsigned char data[128], bool read_digest ) +{ + bool first_block = false; + + if (ctx->mode == ESP_MBEDTLS_SHA512_UNUSED) { + /* try to use hardware for this digest */ + if (esp_sha_try_lock_engine(sha_type(ctx))) { + ctx->mode = ESP_MBEDTLS_SHA512_HARDWARE; + first_block = true; + } else { + ctx->mode = ESP_MBEDTLS_SHA512_SOFTWARE; + } + } + + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_block(sha_type(ctx), data, first_block); + if (read_digest) { + esp_sha_read_digest_state(sha_type(ctx), ctx->state); + } + } else { + mbedtls_sha512_software_process(ctx, data); + } + + return 0; +} + + +int mbedtls_internal_sha512_process( mbedtls_sha512_context *ctx, const unsigned char data[128] ) +{ + return esp_internal_sha512_parallel_engine_process(ctx, data, true); +} + + +/* + * SHA-512 process buffer + */ +int mbedtls_sha512_update( mbedtls_sha512_context *ctx, const unsigned char *input, + size_t ilen ) +{ + int ret = -1; + size_t fill; + unsigned int left; + + if ( ilen == 0 ) { + return 0; + } + + left = (unsigned int) (ctx->total[0] & 0x7F); + fill = 128 - left; + + ctx->total[0] += (uint64_t) ilen; + + if ( ctx->total[0] < (uint64_t) ilen ) { + ctx->total[1]++; + } + + if ( left && ilen >= fill ) { + memcpy( (void *) (ctx->buffer + left), input, fill ); + if ( ( ret = esp_internal_sha512_parallel_engine_process( ctx, ctx->buffer, false ) ) != 0 ) { + return ret; + } + + input += fill; + ilen -= fill; + left = 0; + } + + while ( ilen >= 128 ) { + if ( ( ret = esp_internal_sha512_parallel_engine_process( ctx, input, false ) ) != 0 ) { + return ret; + } + + input += 128; + ilen -= 128; + } + + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_read_digest_state(sha_type(ctx), ctx->state); + } + + if ( ilen > 0 ) { + memcpy( (void *) (ctx->buffer + left), input, ilen ); + } + + return 0; +} + +static const unsigned char sha512_padding[128] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * SHA-512 final digest + */ +int mbedtls_sha512_finish( mbedtls_sha512_context *ctx, unsigned char *output ) +{ + int ret = -1; + size_t last, padn; + uint64_t high, low; + unsigned char msglen[16]; + + high = ( ctx->total[0] >> 61 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT64_BE( high, msglen, 0 ); + PUT_UINT64_BE( low, msglen, 8 ); + + last = (size_t)( ctx->total[0] & 0x7F ); + padn = ( last < 112 ) ? ( 112 - last ) : ( 240 - last ); + + if ( ( ret = mbedtls_sha512_update( ctx, sha512_padding, padn ) ) != 0 ) { + goto out; + } + + if ( ( ret = mbedtls_sha512_update( ctx, msglen, 16 ) ) != 0 ) { + goto out; + } + + /* if state is in hardware, read it out */ + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_read_digest_state(sha_type(ctx), ctx->state); + } + + PUT_UINT64_BE( ctx->state[0], output, 0 ); + PUT_UINT64_BE( ctx->state[1], output, 8 ); + PUT_UINT64_BE( ctx->state[2], output, 16 ); + PUT_UINT64_BE( ctx->state[3], output, 24 ); + PUT_UINT64_BE( ctx->state[4], output, 32 ); + PUT_UINT64_BE( ctx->state[5], output, 40 ); + + if ( ctx->is384 == 0 ) { + PUT_UINT64_BE( ctx->state[6], output, 48 ); + PUT_UINT64_BE( ctx->state[7], output, 56 ); + } + +out: + if (ctx->mode == ESP_MBEDTLS_SHA512_HARDWARE) { + esp_sha_unlock_engine(sha_type(ctx)); + ctx->mode = ESP_MBEDTLS_SHA512_SOFTWARE; + } + + return ret; +} + +#endif /* MBEDTLS_SHA512_C && MBEDTLS_SHA512_ALT */ diff --git a/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/sha.c b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/sha.c new file mode 100644 index 000000000..8c025d707 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/port/sha/parallel_engine/sha.c @@ -0,0 +1,222 @@ +/* + * ESP32 hardware accelerated SHA1/256/512 implementation + * based on mbedTLS FIPS-197 compliant version. + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * Additions Copyright (C) 2016, Espressif Systems (Shanghai) PTE Ltd + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* + * The SHA-1 standard was published by NIST in 1993. + * + * http://www.itl.nist.gov/fipspubs/fip180-1.htm + */ + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_cpu.h" + +#include "hal/sha_hal.h" +#include "hal/sha_types.h" +#include "sha/sha_parallel_engine.h" +#include "soc/hwcrypto_periph.h" +#include "esp_private/periph_ctrl.h" + +/* + Single spinlock for SHA engine memory block +*/ +static portMUX_TYPE memory_block_lock = portMUX_INITIALIZER_UNLOCKED; + + +/* Binary semaphore managing the state of each concurrent SHA engine. + + Available = noone is using this SHA engine + Taken = a SHA session is running on this SHA engine + + Indexes: + 0 = SHA1 + 1 = SHA2_256 + 2 = SHA2_384 or SHA2_512 +*/ +static SemaphoreHandle_t engine_states[3]; + +static uint8_t engines_in_use; + +/* Spinlock for engines_in_use counter +*/ +static portMUX_TYPE engines_in_use_lock = portMUX_INITIALIZER_UNLOCKED; + +/* Return block size (in words) for a given SHA type */ +inline static size_t block_length(esp_sha_type type) +{ + switch (type) { + case SHA1: + case SHA2_256: + return 64 / 4; + case SHA2_384: + case SHA2_512: + return 128 / 4; + default: + return 0; + } +} + +/* Index into the engine_states array */ +inline static size_t sha_engine_index(esp_sha_type type) +{ + switch (type) { + case SHA1: + return 0; + case SHA2_256: + return 1; + default: + return 2; + } +} + +void esp_sha_lock_memory_block(void) +{ + portENTER_CRITICAL(&memory_block_lock); +} + +void esp_sha_unlock_memory_block(void) +{ + portEXIT_CRITICAL(&memory_block_lock); +} + +static SemaphoreHandle_t sha_get_engine_state(esp_sha_type sha_type) +{ + unsigned idx = sha_engine_index(sha_type); + volatile SemaphoreHandle_t *engine = &engine_states[idx]; + SemaphoreHandle_t result = *engine; + + if (result == NULL) { + // Create a new semaphore for 'in use' flag + SemaphoreHandle_t new_engine = xSemaphoreCreateBinary(); + assert(new_engine != NULL); + xSemaphoreGive(new_engine); // start available + + // try to atomically set the previously NULL *engine to new_engine + if (!esp_cpu_compare_and_set((volatile uint32_t *)engine, 0, (uint32_t)new_engine)) { + // we lost a race setting *engine + vSemaphoreDelete(new_engine); + } + result = *engine; + } + return result; +} + +static bool esp_sha_lock_engine_common(esp_sha_type sha_type, TickType_t ticks_to_wait); + +bool esp_sha_try_lock_engine(esp_sha_type sha_type) +{ + return esp_sha_lock_engine_common(sha_type, 0); +} + +void esp_sha_lock_engine(esp_sha_type sha_type) +{ + esp_sha_lock_engine_common(sha_type, portMAX_DELAY); +} + +static bool esp_sha_lock_engine_common(esp_sha_type sha_type, TickType_t ticks_to_wait) +{ + SemaphoreHandle_t engine_state = sha_get_engine_state(sha_type); + BaseType_t result = xSemaphoreTake(engine_state, ticks_to_wait); + + if (result == pdFALSE) { + // failed to take semaphore + return false; + } + + portENTER_CRITICAL(&engines_in_use_lock); + + if (engines_in_use == 0) { + /* Just locked first engine, + so enable SHA hardware */ + periph_module_enable(PERIPH_SHA_MODULE); + } + + engines_in_use++; + assert(engines_in_use <= 3); + + portEXIT_CRITICAL(&engines_in_use_lock); + + return true; +} + + +void esp_sha_unlock_engine(esp_sha_type sha_type) +{ + SemaphoreHandle_t engine_state = sha_get_engine_state(sha_type); + + portENTER_CRITICAL(&engines_in_use_lock); + + engines_in_use--; + + if (engines_in_use == 0) { + /* About to release last engine, so + disable SHA hardware */ + periph_module_disable(PERIPH_SHA_MODULE); + } + + portEXIT_CRITICAL(&engines_in_use_lock); + + xSemaphoreGive(engine_state); +} + +void esp_sha_read_digest_state(esp_sha_type sha_type, void *digest_state) +{ +#ifndef NDEBUG + { + SemaphoreHandle_t engine_state = sha_get_engine_state(sha_type); + assert(uxSemaphoreGetCount(engine_state) == 0 && + "SHA engine should be locked" ); + } +#endif + + // preemptively do this before entering the critical section, then re-check once in it + sha_hal_wait_idle(); + + esp_sha_lock_memory_block(); + + sha_hal_read_digest(sha_type, digest_state); + + esp_sha_unlock_memory_block(); +} + +void esp_sha_block(esp_sha_type sha_type, const void *data_block, bool first_block) +{ +#ifndef NDEBUG + { + SemaphoreHandle_t engine_state = sha_get_engine_state(sha_type); + assert(uxSemaphoreGetCount(engine_state) == 0 && + "SHA engine should be locked" ); + } +#endif + + // preemptively do this before entering the critical section, then re-check once in it + sha_hal_wait_idle(); + esp_sha_lock_memory_block(); + + sha_hal_hash_block(sha_type, data_block, block_length(sha_type), first_block); + + esp_sha_unlock_memory_block(); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/CMakeLists.txt b/components/mbedtls/mbedtls_v3/test_apps/CMakeLists.txt new file mode 100644 index 000000000..16481610b --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/CMakeLists.txt @@ -0,0 +1,7 @@ +#This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mbedtls_test) diff --git a/components/mbedtls/mbedtls_v3/test_apps/README.md b/components/mbedtls/mbedtls_v3/test_apps/README.md new file mode 100644 index 000000000..a8b7833fa --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | diff --git a/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p192.pem b/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p192.pem new file mode 100644 index 000000000..a0ca456b8 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p192.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MF8CAQEEGNav/J4c/nx4R+a30zxRJ2e7l15YTyZlkqAKBggqhkjOPQMBAaE0AzIA +BIhHJTy0t4ckXgfhx/x2D2uD9oF9m1/EuZz8qu3vugLDHApVF+CdEMsjrn4PH01p +1Q== +-----END EC PRIVATE KEY----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p256.pem b/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p256.pem new file mode 100644 index 000000000..3f6b70853 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/ecdsa_key_p256.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBElYzI+FTYu0axLSDoANj0kfm1/oxBzMCDLx89MK0fzoAoGCCqGSM49 +AwEHoUQDQgAEoo9SYCCbVDwTL1Gxib/H+oRcVpYqAGfdfIwPY4t2f7n2TIdbWptZ +CsRTBHINfN6sfq1JjPdcwxwegfJHAXQF1Q== +-----END EC PRIVATE KEY----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/CMakeLists.txt b/components/mbedtls/mbedtls_v3/test_apps/main/CMakeLists.txt new file mode 100644 index 000000000..1017c93a5 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/CMakeLists.txt @@ -0,0 +1,18 @@ +set(TEST_CRTS "crts/server_cert_chain.pem" + "crts/prvtkey.pem" + "crts/server_cert_bundle" + "crts/bad_md_crt.pem" + "crts/wrong_sig_crt_esp32_com.pem" + "crts/correct_sig_crt_esp32_com.pem") + +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + PRIV_REQUIRES cmock test_utils mbedtls esp_timer unity spi_flash + EMBED_TXTFILES ${TEST_CRTS} + WHOLE_ARCHIVE) + +idf_component_get_property(mbedtls mbedtls COMPONENT_LIB) +target_compile_definitions(${mbedtls} INTERFACE "-DMBEDTLS_DEPRECATED_WARNING") +target_compile_definitions(mbedtls PUBLIC "-DMBEDTLS_DEPRECATED_WARNING") +target_compile_definitions(mbedcrypto PUBLIC "-DMBEDTLS_DEPRECATED_WARNING") +target_compile_definitions(mbedx509 PUBLIC "-DMBEDTLS_DEPRECATED_WARNING") diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/app_main.c b/components/mbedtls/mbedtls_v3/test_apps/main/app_main.c new file mode 100644 index 000000000..27735fb98 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/app_main.c @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "mbedtls/aes.h" +#include "memory_checks.h" +#include "soc/soc_caps.h" + +/* setUp runs before every test */ +void setUp(void) +{ + // Execute mbedtls_aes_init operation to allocate AES interrupt + // allocation memory which is considered as leak otherwise +#if SOC_AES_SUPPORTED + mbedtls_aes_context ctx; + mbedtls_aes_init(&ctx); +#endif // SOC_AES_SUPPORTED + + test_utils_record_free_mem(); + test_utils_set_leak_level(CONFIG_UNITY_CRITICAL_LEAK_LEVEL_GENERAL, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL); + test_utils_set_leak_level(CONFIG_UNITY_WARN_LEAK_LEVEL_GENERAL, ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_GENERAL); + test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_LWIP); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + /* some FreeRTOS stuff is cleaned up by idle task */ + vTaskDelay(5); + + /* clean up some of the newlib's lazy allocations */ + esp_reent_cleanup(); + + /* check if unit test has caused heap corruption in any heap */ + TEST_ASSERT_MESSAGE( heap_caps_check_integrity(MALLOC_CAP_INVALID, true), "The test has corrupted the heap"); + + test_utils_finish_and_evaluate_leaks(test_utils_get_leak_level(ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_ALL), + test_utils_get_leak_level(ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_ALL)); + +} + +static void test_task(void *pvParameters) +{ + vTaskDelay(2); /* Delay a bit to let the main task be deleted */ + unity_run_menu(); +} + +void app_main(void) +{ + xTaskCreatePinnedToCore(test_task, "testTask", CONFIG_UNITY_FREERTOS_STACK_SIZE, NULL, CONFIG_UNITY_FREERTOS_PRIORITY, NULL, CONFIG_UNITY_FREERTOS_CPU); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/bad_md_crt.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/bad_md_crt.pem new file mode 100644 index 000000000..e1a1d307b --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/bad_md_crt.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEizCCA3OgAwIBAgIQBUb+GCP34ZQdo5/OFMRhczANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xNzExMDYxMjIzNDVaFw0yNzExMDYxMjIzNDVaMF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFEdlb1RydXN0IFJTQSBDQSAyMDE4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv4rRY03hGOqHXegWPI9/tr6HFzekDPgxP59FVEAh150Hm8oDI0q9m+2FAmM/n4W57Cjv8oYi2/hNVEHFtEJ/zzMXAQ6CkFLTxzSkwaEB2jKgQK0fWeQz/KDDlqxobNPomXOMJhB3y7c/OTLo0lko7geG4gk7hfiqafapa59YrXLIW4dmrgjgdPstU0Nigz2PhUwRl9we/FAwuIMIMl5cXMThdSBK66XWdS3cLX184ND+fHWhTkAChJrZDVouoKzzNYoq6tZaWmyOLKv23v14RyZ5eqoi6qnmcRID0/i6U9J5nL1krPYbY7tNjzgC+PBXXcWqJVoMXcUw/iBTGWzpwwIDAQABo4IBQDCCATwwHQYDVR0OBBYEFJBY/7CcdahRVHex7fKjQxY4nmzFMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA0GCSqGSIb3DQEBCwUAA4IBAQAw8YdVPYQI/C5earp80s3VLOO+AtpdiXft9OlWwJLwKlUtRfccKj8QW/Pp4b7h6QAlufejwQMb455OjpIbCZVS+awY/R8pAYsXCnM09GcSVe4ivMswyoCZP/vPEn/LPRhHhdgUPk8MlD979RGoUWz7qGAwqJChi28uRds3thx+vRZZIbEyZ62No0tJPzsSGSz8nQ//jP8BIwrzBAUH5WcBAbmvgWfrKcuv+PyGPqRcc4T55TlzrBnzAzZ3oClo9fTvO9PuiHMKrC6V6mgi0s2sa/gbXlPCD9Z24XUMxJElwIVTDuKB0Q4YMMlnpN/QChJ4B0AFsQ+DU0NCO+f78Xf7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQkCAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/correct_sig_crt_esp32_com.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/correct_sig_crt_esp32_com.pem new file mode 100644 index 000000000..2b7c6e2fa --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/correct_sig_crt_esp32_com.pem @@ -0,0 +1,6 @@ +-----BEGIN CERTIFICATE----- +MIIFXDCCBESgAwIBAgISA9EyvtMwECvtdhru9l2xnEOgMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDEwMjMxODE2MDFaFw0yMTAxMjExODE2MDFaMBgxFjAUBgNVBAMTDXd3dy5lc3AzMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGvklnzfCVCwTwNFr9+W263qTDSOK8cS6azwirKQl+62Z4E/fvhxvvmp02xiuWoeDpmqsQQCoZMiR77ziqBEDnf80J1S9ZSX3PRWsAYuDGMajTwiywMa7ttvs4Cm3BmXhSQpYEDTIrT7EVgOljSfkGAStKWK5fbkxMJ11eIQdA5KCLKOOPEofq4I5pgwk/4PGGjPSDA51w/XJyNX85hIMLdwXIrWBukrW+B/GFe7a/gdWZCUY2QMBsFPqwGYKZ41S1xtM4VnpZyMu9bvVmS9fvoYIyYUQ6zlktkLawIo56PIEO7wGu4tSNm62dQW23g7jxRwfLCQ7vUSSxOy35LyplAgMBAAGjggJsMIICaDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHiCm41V5UtbkbDidxwrRbN1Bn58MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8wIwYDVR0RBBwwGoIJZXNwMzIuY29tgg13d3cuZXNwMzIuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAXNxDkv7mq0VEsV6a1FbmEDf71fpH3KFzlLJe5vbHDsoAAAF1VuOmWgAABAMARjBEAiAn6M8aTEpBZ+jZgPV67cuNCzuBfa0cXSmntpONtT0ZXAIgYP2ZEZmwkNrkLmYYrhDsPlNlMikj/Y+sObK31z8k30QAdgB9PvL4j/+IVWgkwsDKnlKJeSvFDngJfy5ql2iZfiLw1wAAAXVW46acAAAEAwBHMEUCIF9duaNUkko6siaTN1qY0Jqep7KQ5l/c9bhxkXLvwOLPAiEAlf6Zn/EaXCVCubM1ouKBaYXggKhWWDVRlfPclyDqmHgwDQYJKoZIhvcNAQELBQADggEBAFjhZtXgJTgDc8x19LWE3LVlPsQBNmO1WIuFaTSOShmXHocIy1pR80TWBa905EB0gdVqw7Ez7e84DkIJlczH+1fPUZs8K1TUtte7iR/NfpExrbFXDiGM7kkq9FzVU2xDzLIvlYPFkIfcY1fiaYcnlsS3F3p+vfVidVel61mtBQ7mM2Mf0Vg3emGcw7uuNq5Q1QWs6ILM+JRePDnOD9JQBbK3XZ8imIXr4ewW2VG85NHTyRXRCEP9PwEsMmpqmsYk8vonNz9GeFEPusQ5BWvLvlpC/lprhgpFdxyPQ8iTqBjG5m36J+6TaQdyFi2kpTdnHzV6uvBgyTEUz/Zw3rjlxT8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsFAAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/prvtkey.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/prvtkey.pem new file mode 100644 index 000000000..bb0a510a7 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/prvtkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAySb2QFrQZjQ1EhfN7I+raKrWSWWeYGppqPk3E1sV2y6LSE3M +7cVZyXxcpnP4mcKos3D9k8sbkt05oKcHR2THpWdz5mJn8A7TfJYWnYRHcRuR85th +XC8Pjf3f+JfjXgr/2a2JqHb4fttxoDRwWP1+kbTHa4iERqTFOIhYB9wD8uzbBHJq +IlIGbBHO9J+JxZKVkgDWZfc7zik3YzvkuWju/PmF73BagGxxRDzodzfxhHq1f96J +w10YS9pkhVFLxzOz3O+buwL8plCQplVnpj6J+2MaY4JlUvCcosDkT0EORAtrYGwZ +KJBKn4dRftk8Wbtns7zoJ2SNUL5TZ2oyAb2dwQIDAQABAoIBAQCRmE3tTs5A69Du +A6TdcTAUVnM8NP1ptBw+XgRrUiaDuzC9aPLHt2zB1e4J3S83vBn3p/UjIIQYzV+E +1OED4AJRyoutWdT5gQG6z7gW00QSrm358aGK49VSZUvT17yOuU9u85kMAvDigVvB +JbOb9f/C3yLoxqtXprPJs4ZkSe/hyB0JtRzauDYZnK4JmtgDfGds1cykohbUaeCW +ExUSbi/EPuroowmjEPFmN4tH/C3GtQonwGfjP76GXm8u5Fg8VXYUmb5pnxYcFdvv +shoasbK5lksgK50VP2vA9Y3mIrThRvkWgcv0TZaQWAF/JtdSXIID5WzfsgLbtDQF +hZLk1dDxAoGBAPfAjPAqapNVl4GqSkUWofUHMzZG85fHoPN3INSA0aMr4X9wHFfQ +pQ0ACxuimQj66Vk4rWww+HrsjPfiNMZzoi1exS1tjbQVyTBffrHj8sSdWt8Gw6MB +Pp5ubnCy9pl4lWNHlJZJp2SwAd10LzrizzAQALeEtRmg8meYGZElVUy7AoGBAM/Z +REXLJgaad5V3A2xehrSnknKUwab4LFIrgirZ6h0RXYo+wEHGJpDvM5Vw3sZT+UaJ +Jdlb3cXbqOxWrKlqjKe/S2vNScP7V2Na8l/ySO93PYE0V1Q+EeuNGi41xWC4Dh7o +D7BX2nDm9YBZzNVxM/30/dTzFM+CKrCARsLIXvWzAoGBAOQ4GRv61qXVyHSHO1cd +HB+sfD5ZaXa9S8Q6TqGx8GrQty4/RbyW1BN/oLvaMgKVr3KixQ3OpnYFhW2qkFbm +mdQVYqkQK+Jh1yyaKwkPI8h98wFTJ8/2C4rByzZBhOumqmYDwBoYyvvzLiSjLAag +e56YfzCOLIzpN6K594M+0q6VAoGBALWR5D1gKRjNqbetHxV1QhHg7WMhJkaZOAaU +MYMDmKvJ9sAE72jGE/y6qYJb9pCk3PdMaf8GbKciq9/CG9Vn2fXUe6txy4XkNEP8 +OA2vFx3yOY18Tumty3PNcNh7arCCOPuw17vCE3ZbnI2CZRj0amnosjFsJHreCDLl +7GrOJX5XAoGASZXbGykpYJTTr5PGPL/eX0koU1RZ9f6fvVdkfeWNGZfJ4oGkxDcO +fJnzq9wC9YREy6f3eoMrix95RPv4Qo1Wwi2PmtyMFvUdsYckFEhxSN3p4Iqn/nQg +6I7VB0yNqw8ZdP1vBkRcg3kk+QO2tci+OTdpDSKmO5nGjuqpsdBM5/o= +-----END RSA PRIVATE KEY----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_bundle b/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_bundle new file mode 100644 index 0000000000000000000000000000000000000000..0b7f09aa06da459957cb5c4df81df1876b9597b8 GIT binary patch literal 400 zcmZQzWJqCDGe|PzHsEAq4rO5zW^(p36gCh9aX5Img7b4zb%RS1OHvI54fsKV>^vO7 z8Hsu68Ht&OLIwgLAub+H*W!Yr)Z*gIG(&L%QIHrj4)FR z?K>|cBO@yVa}y&!15licsfm%1VfiKQGcVqp-yWI65Tev@h;?yT;Xjs#?a#H`M6+DZlP292U!dr13 zmOs2d1@31>a&LIf%>Vs;YufB@Y36@g9vg@BTzhPtBe$8|xKQ1jy-0ZD`o2|<+2 literal 0 HcmV?d00001 diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_chain.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_chain.pem new file mode 100644 index 000000000..afc99a9e3 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_cert_chain.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVTCCAj0CFG5WO5Ukqd/0PnrSPIlQnXNjrCUUMA0GCSqGSIb3DQEBCwUAMGIx +CzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFu +Z2hhaTESMBAGA1UECgwJRXNwcmVzc2lmMRcwFQYDVQQDDA5Fc3ByZXNzaWYgUm9v +dDAeFw0yMDAzMjYwNjQxMTlaFw0yMTAzMjEwNjQxMTlaMGwxCzAJBgNVBAYTAkNO +MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFuZ2hhaTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJJvZAWtBmNDUSF83sj6to +qtZJZZ5gammo+TcTWxXbLotITcztxVnJfFymc/iZwqizcP2TyxuS3TmgpwdHZMel +Z3PmYmfwDtN8lhadhEdxG5Hzm2FcLw+N/d/4l+NeCv/ZrYmodvh+23GgNHBY/X6R +tMdriIRGpMU4iFgH3APy7NsEcmoiUgZsEc70n4nFkpWSANZl9zvOKTdjO+S5aO78 ++YXvcFqAbHFEPOh3N/GEerV/3onDXRhL2mSFUUvHM7Pc75u7AvymUJCmVWemPon7 +YxpjgmVS8JyiwORPQQ5EC2tgbBkokEqfh1F+2TxZu2ezvOgnZI1QvlNnajIBvZ3B +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI2RzAwx1IiyWYPbSQOMjATKG1hiqNJF +fkkqJrSfu93iQyye3Umb/pdUf7v5xgN2NrW5VnRow19VR7uCU4VCCBfx77f0Zp2e +UA13qhT5zljoqgtkU9bHbRfTW/Hq30joKqQz8+Z0Yom6qZA7XjAhXXiHt7I4Noq6 +y+HwH08Xr1nII1c6Zc0cDqK9UV02w2v1RJrnGlq3v/CBpanA/nz4LdP5Jqbh79WW +bCe8+Y7WEYR7K4dKSkDugf8ROAaGuCYAbhRMU3tFjNlMRR/5HcBpy7MfUvX6GcI0 +QCfe4ugnHXQXNxS0rb2uM6yCHOTiQ5MJjBPh9tRYV9bSko5u/NmwsFU= +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_root.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_root.pem new file mode 100644 index 000000000..5854747f9 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/server_root.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDpTCCAo2gAwIBAgIUduK+lv/MILT278PPIYz8HkFzhFUwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM +CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np +ZiBSb290MB4XDTIwMDMyNjA2NDAxMFoXDTI1MDMyNjA2NDAxMFowYjELMAkGA1UE +BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMRIw +EAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3NpZiBSb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9ILzOjsz7dZbABUIoDCBat3MPsv +qd20Jsk3GzkLjY/HjTCMBweq2zt0sRsa+YwCPtQyAsYPtgt/VzRY4TF8jqmSj7Ko +DKOWkbim0O0XDAT8DfkQ32pZC7DnAw/374Vmm/ZmN/yE4zNUjNbjO2weswczcSdL +B3ITsa+OquKYK8J2Pe5gZh/tC0f0I9ks3UplcLyEex8TQZivAK3RL4QWj4j4NJWn +wH5qdizuKStwWEo3FvTP4g95SQItw31HTA8mJcBzCZC0NOZyMckRSmK51XljQ0iU +G7KwK8GNbDC+VUZEt5aGB5QZhCFC2wo5An7u20UHRUWbv4MEgddPDoQ4EwIDAQAB +o1MwUTAdBgNVHQ4EFgQU3inIjbdXp/DgSnVAiJmTlAtKH08wHwYDVR0jBBgwFoAU +3inIjbdXp/DgSnVAiJmTlAtKH08wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAOpLjyXj2e0IaUgJK3lGuJ9u6piehYK1WqPoCR7K7pUnFGyNLu0mq +yfTqNoXV8a7NmM8Vn8ZJ1Gep20JqgvR27o3OE87bT7E/JPUsvbu7MNdfiVxpFWi1 +HxdBrzHr+mcakbhRxI38s3GVNT9Y89Y7FZbE+dqT8SxILk2pVUExfZR/ItazDTxl +95ARCOj/bQPCEN+oLYzS31ORmkJfY2AuJAcJUTCyO4UfpKVFmQeAKlNmTq9Q0a6C +0RlbzZ/PJoB3d265A9fTjlANQ7XzE8GgIJVR7cz5OJzZVxfEr9ME9VfgNrjKyXS3 +FcFQvif6JqX6IbmTenEKi7IfgX2zu1nxtQ== +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/crts/wrong_sig_crt_esp32_com.pem b/components/mbedtls/mbedtls_v3/test_apps/main/crts/wrong_sig_crt_esp32_com.pem new file mode 100644 index 000000000..0d6b8b720 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/crts/wrong_sig_crt_esp32_com.pem @@ -0,0 +1,6 @@ +-----BEGIN CERTIFICATE----- +MIIFXDCCBESgAwIBAgISA9EyvtMwECvtdhru9l2xnEOgMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDEwMjMxODE2MDFaFw0yMTAxMjExODE2MDFaMBgxFjAUBgNVBAMTDXd3dy5lc3AzMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGvklnzfCVCwTwNFr9+W263qTDSOK8cS6azwirKQl+62Z4E/fvhxvvmp02xiuWoeDpmqsQQCoZMiR77ziqBEDnf80J1S9ZSX3PRWsAYuDGMajTwiywMa7ttvs4Cm3BmXhSQpYEDTIrT7EVgOljSfkGAStKWK5fbkxMJ11eIQdA5KCLKOOPEofq4I5pgwk/4PGGjPSDA51w/XJyNX85hIMLdwXIrWBukrW+B/GFe7a/gdWZCUY2QMBsFPqwGYKZ41S1xtM4VnpZyMu9bvVmS9fvoYIyYUQ6zlktkLawIo56PIEO7wGu4tSNm62dQW23g7jxRwfLCQ7vUSSxOy35LyplAgMBAAGjggJsMIICaDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHiCm41V5UtbkbDidxwrRbN1Bn58MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZy8wIwYDVR0RBBwwGoIJZXNwMzIuY29tgg13d3cuZXNwMzIuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAXNxDkv7mq0VEsV6a1FbmEDf71fpH3KFzlLJe5vbHDsoAAAF1VuOmWgAABAMARjBEAiAn6M8aTEpBZ+jZgPV67cuNCzuBfa0cXSmntpONtT0ZXAIgYP2ZEZmwkNrkLmYYrhDsPlNlMikj/Y+sObK31z8k30QAdgB9PvL4j/+IVWgkwsDKnlKJeSvFDngJfy5ql2iZfiLw1wAAAXVW46acAAAEAwBHMEUCIF9duaNUkko6siaTN1qY0Jqep7KQ5l/c9bhxkXLvwOLPAiEAlf6Zn/EaXCVCubM1ouKBaYXggKhWWDVRlfPclyDqmHgwDQYJKoZIhvcNAQELBQADggEBAFjhZtXgJTgDc8x19LWE3LVlPsQBNmO1WIuFaTSOShmXHocIy1pR80TWBa905EB0gdVqw7Ez7e84DkIJlczH+1fPUZs8K1TUtte7iR/NfpExrbFXDiGM7kkq9FzVU2xDzLIvlYPFkIfcY1fiaYcnlsS3F3p+vfVidVel61mtBQ7mM2Mf0Vg3emGcw7uuNq5Q1QWs6ILM+JRePDnOD9JQBbK3XZ8imIXr4ewW2VG85NHTyRXRCEP9PwEsMmpqmsYk8vonNz9GeFEPusQ5BWvLvlpC/lprhgpFdxyPQ8iTqBjG5m36J+6TaQdyFi2kpTdnHzV6uvBgyTEUz/Zw3rjlxT8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsFAAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu1Qg== +-----END CERTIFICATE----- diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_aes.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes.c new file mode 100644 index 000000000..5ac71e82e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes.c @@ -0,0 +1,1588 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* mbedTLS AES test +*/ +#include +#include +#include +#include +#include "mbedtls/aes.h" +#include "mbedtls/gcm.h" +#include "unity.h" +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_heap_caps.h" +#include "test_utils.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_memory_utils.h" +#include "soc/lldesc.h" + + +static const uint8_t key_256[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +}; + +static const uint8_t iv[] = { + 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, +}; + +/* Cipher produced via this Python: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + def as_c_array(byte_arr): + + hex_str = '' + for idx, byte in enumerate(byte_arr): + hex_str += "0x{:02x}, ".format(byte) + bytes_per_line = 8 + if idx % bytes_per_line == bytes_per_line - 1: + hex_str += '\n' + + return hex_str + + key = bytearray(range(32)) + iv = bytearray(range(16, 0, -1)) + + print("Key: \n{}".format(as_c_array(key))) + print("IV: \n{}".format(as_c_array(iv))) + + # Replace CTR with desired mode + cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) + encryptor = cipher.encryptor() + + input_len = 1000 + + plain = b'\x3A'*input_len + print(as_c_array(plain)) + ct = encryptor.update(plain) + encryptor.finalize() + + print("Chipertext: {}".format(as_c_array(ct))) +*/ +TEST_CASE("mbedtls CBC AES-256 test", "[aes]") +{ + const unsigned SZ = 1600; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + + const uint8_t expected_cipher_end[] = { + 0x3e, 0x68, 0x8a, 0x02, 0xe6, 0xf2, 0x6a, 0x9e, + 0x9b, 0xb2, 0xc0, 0xc4, 0x63, 0x63, 0xd9, 0x25, + 0x51, 0xdc, 0xc2, 0x71, 0x96, 0xb3, 0xe5, 0xcd, + 0xbd, 0x0e, 0xf2, 0xef, 0xa9, 0xab, 0xab, 0x2d, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, SZ, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + memcpy(nonce, iv, 16); + mbedtls_aes_setkey_dec(&ctx, key_256, 256); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, SZ, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CBC AES-256 DMA buffer align test", "[aes]") +{ +#define ALIGN_DOWN(val, align) ((val) & ~((align) - 1)) + // Size is taken considering the maximum DMA buffer size + const unsigned SZ = ALIGN_DOWN((2*LLDESC_MAX_NUM_PER_DESC), 16); + mbedtls_aes_context ctx; + uint8_t nonce[16]; + + const uint8_t expected_cipher_end[] = { + 0x9e, 0xcb, 0x1d, 0x24, 0x01, 0xc8, 0x3f, 0xba, + 0xde, 0x76, 0xea, 0x9c, 0xf3, 0x64, 0x23, 0x19, + 0x8c, 0x67, 0xd4, 0x1a, 0xd1, 0xe0, 0xbf, 0xc3, + 0xd2, 0xb8, 0x40, 0x95, 0x89, 0x41, 0x09, 0xdb, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, SZ, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + memcpy(nonce, iv, 16); + mbedtls_aes_setkey_dec(&ctx, key_256, 256); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, SZ, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CTR AES-256 test", "[aes]") +{ + const unsigned SZ = 1000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t stream_block[16]; + size_t nc_off = 0; + + const uint8_t expected_cipher_end[] = { + 0xd4, 0xdc, 0x4f, 0x8f, 0xfe, 0x86, 0xee, 0xb5, + 0x14, 0x7f, 0xba, 0x30, 0x25, 0xa6, 0x7f, 0x6c, + 0xb5, 0x73, 0xaf, 0x90, 0xd7, 0xff, 0x36, 0xba, + 0x2b, 0x1d, 0xec, 0xb9, 0x38, 0xfa, 0x0d, 0xeb, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_ctr(&ctx, SZ, &nc_off, nonce, stream_block, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + nc_off = 0; + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_ctr(&ctx, SZ, &nc_off, nonce, stream_block, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls OFB AES-256 test", "[aes]") +{ + const unsigned SZ = 1000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + size_t nc_off = 0; + + const uint8_t expected_cipher_end[] = { + 0xca, 0xc3, 0x05, 0x77, 0xae, 0xb9, 0x38, 0xd6, + 0x03, 0x0a, 0xad, 0x90, 0x6e, 0xdd, 0xf3, 0x9a, + 0x41, 0x4d, 0x71, 0x30, 0x04, 0x9f, 0xd3, 0x53, + 0xb7, 0x5e, 0xb4, 0xfd, 0x93, 0xf8, 0x31, 0x6a, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_ofb(&ctx, SZ, &nc_off, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + nc_off = 0; + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_ofb(&ctx, SZ, &nc_off, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CFB-8 AES-256 test", "[aes]") +{ + const unsigned SZ = 1000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + + const uint8_t expected_cipher_end[] = { + 0x69, 0xdc, 0x1d, 0x8a, 0x0b, 0x9e, 0xbc, 0x84, + 0x29, 0xa2, 0x04, 0xb6, 0x91, 0x6b, 0xb2, 0x83, + 0x13, 0x23, 0x54, 0xcb, 0xf9, 0x6d, 0xcc, 0x53, + 0x04, 0x59, 0xd1, 0xc9, 0xff, 0xab, 0xe2, 0x37, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_cfb8(&ctx, MBEDTLS_AES_ENCRYPT, SZ, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_cfb8(&ctx, MBEDTLS_AES_DECRYPT, SZ, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CFB-128 AES-256 test", "[aes]") +{ + const unsigned SZ = 1000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + size_t nc_off = 0; + + const uint8_t expected_cipher_end[] = { + 0xf3, 0x64, 0x20, 0xa1, 0x70, 0x2a, 0xd9, 0x3f, + 0xb7, 0x48, 0x8c, 0x2c, 0x1f, 0x65, 0x53, 0xc2, + 0xac, 0xfd, 0x82, 0xe5, 0x31, 0x24, 0x1f, 0x30, + 0xaf, 0xcc, 0x8d, 0xb3, 0xf3, 0x63, 0xe1, 0xa0, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_ENCRYPT, SZ, &nc_off, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + nc_off = 0; + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_DECRYPT, SZ, &nc_off, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +static void aes_ctr_stream_test(void) +{ + const unsigned SZ = 100; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t key[16]; + uint8_t stream_block[16]; + + /* Cipher produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + nonce = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 100) + encryptor.finalize() + ct_arr = "" + for idx, b in enumerate(ct): + if idx % 8 == 0: + ct_arr += '\n' + ct_arr += "0x{}, ".format(binascii.hexlify(b)) + print(ct_arr) + */ + const uint8_t expected_cipher[] = { + 0xc5, 0x78, 0xa7, 0xb4, 0xf3, 0xb9, 0xcb, 0x8b, + 0x09, 0xe0, 0xd6, 0x89, 0x14, 0x6a, 0x19, 0x09, + 0xde, 0xaf, 0x37, 0x19, 0x32, 0x4d, 0xca, 0xf6, + 0xff, 0x6e, 0xd2, 0x5d, 0x87, 0x51, 0xaa, 0x8c, + 0x1c, 0xe3, 0x3b, 0xbb, 0x18, 0xf5, 0xa0, 0x1b, + 0xdc, 0x29, 0x52, 0x63, 0xf6, 0x5d, 0x49, 0x85, + 0x29, 0xf1, 0xf0, 0x69, 0x8f, 0xa6, 0x9f, 0x38, + 0x5c, 0xdd, 0x26, 0xf8, 0x9d, 0x40, 0xa1, 0xff, + 0x52, 0x46, 0xe1, 0x72, 0x70, 0x39, 0x73, 0xff, + 0xd0, 0x5e, 0xe5, 0x3f, 0xc5, 0xed, 0x5c, 0x18, + 0xa7, 0x84, 0xd8, 0xdf, 0x9d, 0xb5, 0x06, 0xb1, + 0xa7, 0xcf, 0x2e, 0x7a, 0x51, 0xfc, 0x44, 0xc5, + 0xb9, 0x5f, 0x22, 0x47, + }; + + + memset(nonce, 0xEE, 16); + memset(key, 0x44, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(plaintext, 0xAA, SZ); + + /* Test that all the end results are the same + no matter how many bytes we encrypt each call + */ + for (int bytes_to_process = 1; bytes_to_process < SZ; bytes_to_process++) { + ESP_LOGD("test", "bytes_to_process %d", bytes_to_process); + memset(nonce, 0xEE, 16); + memset(chipertext, 0x0, SZ); + memset(decryptedtext, 0x0, SZ); + + size_t offset = 0; + // Encrypt + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = (idx + bytes_to_process > SZ) ? (SZ - idx) : bytes_to_process; + + mbedtls_aes_crypt_ctr(&ctx, length, &offset, nonce, + stream_block, plaintext + idx, chipertext + idx ); + } + ESP_LOG_BUFFER_HEXDUMP("expected", expected_cipher, SZ, ESP_LOG_DEBUG); + ESP_LOG_BUFFER_HEXDUMP("actual ", chipertext, SZ, ESP_LOG_DEBUG); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher, chipertext, SZ); + + // Decrypt + memset(nonce, 0xEE, 16); + memset(decryptedtext, 0x22, SZ); + offset = 0; + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = (idx + bytes_to_process > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_ctr(&ctx, length, &offset, nonce, + stream_block, chipertext + idx, decryptedtext + idx ); + } + ESP_LOG_BUFFER_HEXDUMP("decrypted", decryptedtext, SZ, ESP_LOG_DEBUG); + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + } + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CTR stream test", "[aes]") +{ + aes_ctr_stream_test(); +} + + +TEST_CASE("mbedtls OFB stream test", "[aes]") +{ + const unsigned SZ = 100; + mbedtls_aes_context ctx; + uint8_t iv[16]; + uint8_t key[16]; + + /* Cipher produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + iv = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.OFB(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 100) + encryptor.finalize() + ct_arr = "" + for idx, b in enumerate(ct): + if idx % 8 == 0: + ct_arr += '\n' + ct_arr += "0x{}, ".format(binascii.hexlify(b)) + print(ct_arr) + */ + const uint8_t expected_cipher[] = { + 0xc5, 0x78, 0xa7, 0xb4, 0xf3, 0xb9, 0xcb, 0x8b, + 0x09, 0xe0, 0xd6, 0x89, 0x14, 0x6a, 0x19, 0x09, + 0x0a, 0x33, 0x8b, 0xab, 0x82, 0xcb, 0x20, 0x8f, + 0x74, 0x2a, 0x6c, 0xb3, 0xc6, 0xe8, 0x18, 0x89, + 0x09, 0xb6, 0xaf, 0x20, 0xcd, 0xea, 0x74, 0x14, + 0x48, 0x61, 0xe8, 0x4d, 0x50, 0x12, 0x9f, 0x5e, + 0xb8, 0x10, 0x53, 0x3b, 0x74, 0xd9, 0xd0, 0x95, + 0x13, 0xdc, 0x14, 0xcf, 0x0c, 0xa1, 0x90, 0xfd, + 0xa2, 0x58, 0x12, 0xb2, 0x00, 0x2c, 0x5b, 0x7a, + 0x2a, 0x76, 0x80, 0x20, 0x82, 0x39, 0xa2, 0x21, + 0xf8, 0x7a, 0xec, 0xae, 0x82, 0x6a, 0x5c, 0xd3, + 0x04, 0xd9, 0xbd, 0xe4, 0x53, 0xc9, 0xdf, 0x67, + 0xaa, 0x5c, 0xaf, 0xa6, + }; + + + memset(key, 0x44, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(plaintext, 0xAA, SZ); + + /* Test that all the end results are the same + no matter how many bytes we encrypt each call + */ + + for (int bytes_to_process = 1; bytes_to_process < SZ; bytes_to_process++) { + ESP_LOGD("test", "bytes_to_process %d", bytes_to_process); + // Encrypt + memset(iv, 0xEE, 16); + size_t offset = 0; + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = ( (idx + bytes_to_process) > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_ofb(&ctx, length, &offset, iv, plaintext + idx, chipertext + idx); + + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher, chipertext, SZ); + + // Decrypt + memset(iv, 0xEE, 16); + memset(decryptedtext, 0x22, SZ); + offset = 0; + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = (idx + bytes_to_process > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_ofb(&ctx, length, &offset, iv, chipertext + idx, decryptedtext + idx); + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + } + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CFB8 stream test", "[aes]") +{ + const unsigned SZ = 32; + mbedtls_aes_context ctx; + uint8_t iv[16]; + uint8_t key[16]; + + /* Cipher produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + iv = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.CFB8(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 100) + encryptor.finalize() + ct_arr = "" + for idx, b in enumerate(ct): + if idx % 8 == 0: + ct_arr += '\n' + ct_arr += "0x{}, ".format(binascii.hexlify(b)) + print(ct_arr) + */ + const uint8_t expected_cipher[] = { + 0xc5, 0x2f, 0xb0, 0x9b, 0x94, 0x9c, 0xa4, 0x5c, + 0x0f, 0x4d, 0xa1, 0x9d, 0xd1, 0x19, 0xfc, 0x04, + 0xe2, 0x7f, 0x04, 0x82, 0x6a, 0xa3, 0x61, 0xbb, + 0x07, 0x6f, 0xac, 0xb9, 0xdf, 0x00, 0xf9, 0xa8, + 0xc4, 0xbe, 0x9d, 0x4d, 0xd9, 0x42, 0x8a, 0x83, + 0x12, 0x8b, 0xeb, 0xd7, 0x88, 0x70, 0x8a, 0xed, + 0x46, 0x81, 0x5b, 0x4c, 0x14, 0x67, 0xe0, 0xfb, + 0xab, 0x34, 0x90, 0x85, 0x24, 0xd2, 0x6b, 0x64, + 0xdf, 0x1d, 0x04, 0xfd, 0x69, 0xf6, 0x30, 0xbe, + 0xa6, 0xac, 0x0b, 0x54, 0x25, 0x24, 0x67, 0xd6, + 0x09, 0xb1, 0x8f, 0x91, 0x63, 0xbd, 0xdf, 0xa1, + 0x8a, 0xa3, 0x2e, 0xeb, 0x15, 0x7d, 0xe5, 0x37, + 0xe5, 0x5a, 0x9f, 0xa5, + }; + + + memset(key, 0x44, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(plaintext, 0xAA, SZ); + + /* Test that all the end results are the same + no matter how many bytes we encrypt each call + */ + + for (int bytes_to_process = 1; bytes_to_process < SZ; bytes_to_process++) { + memset(iv, 0xEE, 16); + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = ( (idx + bytes_to_process) > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_cfb8(&ctx, MBEDTLS_AES_ENCRYPT, length, iv, plaintext + idx, chipertext + idx); + + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher, chipertext, SZ); + + memset(iv, 0xEE, 16); + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = ( (idx + bytes_to_process) > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_cfb8(&ctx, MBEDTLS_AES_DECRYPT, length, iv, chipertext + idx, decryptedtext + idx); + + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + } + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls CFB128 stream test", "[aes]") +{ + const unsigned SZ = 32; + mbedtls_aes_context ctx; + uint8_t iv[16]; + uint8_t key[16]; + + /* Cipher produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + iv = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 100) + encryptor.finalize() + ct_arr = "" + for idx, b in enumerate(ct): + if idx % 8 == 0: + ct_arr += '\n' + ct_arr += "0x{}, ".format(binascii.hexlify(b)) + print(ct_arr) + */ + const uint8_t expected_cipher[] = { + 0xc5, 0x78, 0xa7, 0xb4, 0xf3, 0xb9, 0xcb, 0x8b, + 0x09, 0xe0, 0xd6, 0x89, 0x14, 0x6a, 0x19, 0x09, + 0xf9, 0x08, 0x7e, 0xe1, 0x92, 0x8a, 0x7c, 0xa4, + 0x25, 0xa5, 0xa7, 0x43, 0x24, 0x8d, 0x85, 0x3e, + 0x99, 0x28, 0xeb, 0x36, 0x59, 0x74, 0x69, 0x0e, + 0x09, 0x9f, 0x4e, 0xc0, 0x6d, 0xc3, 0x2b, 0x80, + 0x01, 0xad, 0xa1, 0x0c, 0x99, 0x90, 0x8b, 0x07, + 0xd6, 0x00, 0xf0, 0x32, 0xd7, 0x6b, 0xa1, 0xf1, + 0x4d, 0x14, 0xd0, 0x28, 0xde, 0x64, 0x23, 0x71, + 0xf4, 0x23, 0x61, 0x12, 0x71, 0xbe, 0x03, 0x74, + 0x99, 0x81, 0x9d, 0x65, 0x48, 0xd9, 0xd4, 0x67, + 0xd1, 0x31, 0xe8, 0x44, 0x27, 0x17, 0xd4, 0x2d, + 0x3d, 0x59, 0xf7, 0xd3, + }; + + + memset(key, 0x44, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(plaintext, 0xAA, SZ); + + /* Test that all the end results are the same + no matter how many bytes we encrypt each call + */ + + //for (int bytes_to_process = 1; bytes_to_process < SZ; bytes_to_process++) { + int bytes_to_process = 17; + size_t offset = 0; + memset(iv, 0xEE, 16); + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = ( (idx + bytes_to_process) > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_ENCRYPT, length, &offset, iv, plaintext + idx, chipertext + idx); + + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher, chipertext, SZ); + + offset = 0; + memset(iv, 0xEE, 16); + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = ( (idx + bytes_to_process) > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_aes_crypt_cfb128(&ctx, MBEDTLS_AES_DECRYPT, length, &offset, iv, chipertext + idx, decryptedtext + idx); + + } + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +/* Cipher produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + nonce = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 100) + encryptor.finalize() + ct_arr = "" + for idx, b in enumerate(ct): + if idx % 8 == 0: + ct_arr += '\n' + ct_arr += "0x{}, ".format(binascii.hexlify(b)) + print(ct_arr) +*/ + +/* Test the case where the input and output buffers point to the same location */ +TEST_CASE("mbedtls CTR, input buf = output buf", "[aes]") +{ + const unsigned SZ = 1000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t stream_block[16]; + size_t nc_off = 0; + + const uint8_t expected_cipher_end[] = { + 0xd4, 0xdc, 0x4f, 0x8f, 0xfe, 0x86, 0xee, 0xb5, + 0x14, 0x7f, 0xba, 0x30, 0x25, 0xa6, 0x7f, 0x6c, + 0xb5, 0x73, 0xaf, 0x90, 0xd7, 0xff, 0x36, 0xba, + 0x2b, 0x1d, 0xec, 0xb9, 0x38, 0xfa, 0x0d, 0xeb, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *buf = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(buf); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(buf, 0x3A, SZ); + + // Encrypt + mbedtls_aes_crypt_ctr(&ctx, SZ, &nc_off, nonce, stream_block, buf, buf); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, buf + SZ - 32, 32); + + // Decrypt + nc_off = 0; + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_ctr(&ctx, SZ, &nc_off, nonce, stream_block, buf, buf); + + for (int i = 0; i < SZ; i++) { + TEST_ASSERT_EQUAL_HEX8(0x3A, buf[i]); + } + + mbedtls_aes_free(&ctx); + free(buf); +} + +TEST_CASE("mbedtls OFB, chained DMA descriptors", "[aes]") +{ + // Max bytes in a single DMA descriptor is 4095 + const unsigned SZ = 6000; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + size_t nc_off = 0; + + const uint8_t expected_cipher_end[] = { + 0xfe, 0xfa, 0xc9, 0x26, 0xb5, 0xc9, 0xea, 0xb0, + 0xdd, 0x1e, 0xe7, 0x0e, 0xfa, 0x5b, 0x4b, 0x94, + 0xaa, 0x5f, 0x60, 0x1e, 0xb2, 0x19, 0x3c, 0x2e, + 0xf6, 0x73, 0x56, 0x9f, 0xa7, 0xd5, 0xb7, 0x21, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_ofb(&ctx, SZ, &nc_off, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + + // Decrypt + nc_off = 0; + memcpy(nonce, iv, 16); + mbedtls_aes_crypt_ofb(&ctx, SZ, &nc_off, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + + + +const uint8_t expected_cipher_ctr_end[] = { + 0x93, 0xca, 0xe0, 0x44, 0x96, 0x6d, 0xcb, 0xb2, + 0xcf, 0x8a, 0x8d, 0x73, 0x8c, 0x6b, 0xfa, 0x4d, + 0xd6, 0xc4, 0x18, 0x49, 0xdd, 0xc6, 0xbf, 0xc2, + 0xb9, 0xf0, 0x09, 0x69, 0x45, 0x42, 0xc6, 0x05, +}; + + +void aes_ctr_alignment_test(uint32_t input_buf_caps, uint32_t output_buf_caps) +{ + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t key[16]; + uint8_t stream_block[16]; + size_t SZ = 32*200; + size_t ALIGNMENT_SIZE_BYTES = 64; + memset(nonce, 0x2F, 16); + memset(key, 0x1E, 16); + + // allocate memory according the requested caps + uint8_t *chipertext = heap_caps_malloc(SZ + ALIGNMENT_SIZE_BYTES, output_buf_caps); + uint8_t *plaintext = heap_caps_malloc(SZ + ALIGNMENT_SIZE_BYTES, input_buf_caps); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(plaintext, 0x26, SZ + ALIGNMENT_SIZE_BYTES); + + size_t offset; + + /* Shift buffers and test for all different misalignments */ + for (int i = 0; i < ALIGNMENT_SIZE_BYTES; i++ ) { + // Encrypt with input buffer in external ram + offset = 0; + memset(nonce, 0x2F, 16); + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, plaintext + i, chipertext + i); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_ctr_end, chipertext + i + SZ - 32, 32); + + // Decrypt + offset = 0; + memset(nonce, 0x2F, 16); + // Decrypt with input buffer in instruction memory, the crypto DMA can't access this + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, chipertext + i, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + } + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls AES internal mem alignment tests", "[aes]") +{ + uint32_t internal_dma_caps = MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL; + aes_ctr_alignment_test(internal_dma_caps, internal_dma_caps); +} + + +#ifdef CONFIG_SPIRAM_USE_MALLOC + +void aes_psram_one_buf_ctr_test(void) +{ + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t key[16]; + uint8_t stream_block[16]; + size_t SZ = 32*200; + size_t ALIGNMENT_SIZE_BYTES = 32; + memset(nonce, 0x2F, 16); + memset(key, 0x1E, 16); + + // allocate external memory + uint8_t *buf = heap_caps_malloc(SZ + ALIGNMENT_SIZE_BYTES, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + + TEST_ASSERT_NOT_NULL(buf); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + memset(buf, 0x26, SZ + ALIGNMENT_SIZE_BYTES); + + size_t offset; + + /* Shift buffers and test for all different misalignments */ + for (int i = 0; i < ALIGNMENT_SIZE_BYTES; i++ ) { + // Encrypt with input buffer in external ram + offset = 0; + memset(buf, 0x26, SZ + ALIGNMENT_SIZE_BYTES); + memset(nonce, 0x2F, 16); + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, buf + i, buf + i); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_ctr_end, buf + i + SZ - 32, 32); + + // Decrypt + offset = 0; + memset(nonce, 0x2F, 16); + // Decrypt with input buffer in instruction memory, the crypto DMA can't access this + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, buf + i, buf); + + TEST_ASSERT_EACH_EQUAL_HEX8(0x26, buf + i, SZ - i); + + } + mbedtls_aes_free(&ctx); + free(buf); +} + + +const uint8_t long_input[] = { + 0xf7, 0xe6, 0x6b, 0x8d, 0x2e, 0xbf, 0x88, 0xd6, + 0xb0, 0x77, 0xdf, 0x72, 0xbf, 0xa8, 0x0, 0x55, + 0xd5, 0xd1, 0x49, 0xa3, 0x2c, 0xc, 0xfe, 0xdb, + 0x17, 0x37, 0xa4, 0x1d, 0x70, 0x6b, 0x99, 0xf5, + 0x9e, 0x6, 0xad, 0x6c, 0xe0, 0x3b, 0xfa, 0x50, + 0x28, 0xb2, 0x62, 0xf2, 0x99, 0x3a, 0xcc, 0xe4, + 0x86, 0x5f, 0x1, 0xf8, 0x69, 0xd7, 0xf5, 0xb2, + 0x8a, 0x5f, 0x5c, 0x38, 0x9f, 0x8a, 0xb8, 0x8c, + 0xea, 0x6, 0xe1, 0x68, 0xff, 0xaf, 0x5d, 0xd9, + 0x1f, 0xa5, 0x5c, 0x8c, 0x52, 0xa1, 0x5f, 0x45, + 0x55, 0xcb, 0x76, 0x59, 0x8f, 0xfe, 0x36, 0xd0, + 0x85, 0x1f, 0x8, 0x90, 0x6f, 0x62, 0xb1, 0x1a, + 0xde, 0x75, 0xab, 0x90, 0xb7, 0x75, 0xe9, 0xa0, + 0xa9, 0xb0, 0xac, 0x61, 0x5, 0x6d, 0x9a, 0xe3, + 0x3b, 0x43, 0x61, 0x13, 0x8c, 0x3a, 0xa0, 0xaa, + 0x91, 0xea, 0x3e, 0xe1, 0x87, 0x35, 0xff, 0x90, + 0xe2, 0x43, 0xa3, 0x70, 0x57, 0x65, 0x2d, 0xa2, + 0x65, 0xe6, 0xde, 0xb0, 0x52, 0x85, 0x5b, 0xb8, + 0x3, 0x8, 0x63, 0x8b, 0xa1, 0xc2, 0xe1, 0x35, + 0x2e, 0xba, 0xe0, 0x84, 0x56, 0x52, 0x5f, 0x12, + 0xd3, 0x22, 0x8d, 0xa5, 0xbb, 0xe1, 0xd3, 0xfc, + 0x18, 0x1c, 0x90, 0x3b, 0x79, 0xe, 0xab, 0x2d, + 0x5e, 0xb0, 0x7, 0xbb, 0x46, 0x73, 0x1d, 0x35, + 0xd9, 0xc5, 0xa7, 0x87, 0x80, 0xf7, 0xee, 0x29, + 0xb5, 0x17, 0xf3, 0xaf, 0x30, 0xe5, 0x19, 0x50, + 0xf9, 0x5d, 0x2b, 0xc3, 0xc0, 0xda, 0x8f, 0xca, + 0x3c, 0x4d, 0xd5, 0xd7, 0x6c, 0xd2, 0x36, 0xa4, + 0x22, 0x8, 0x66, 0x48, 0x31, 0xb4, 0x3d, 0xc2, + 0xf6, 0x6b, 0xce, 0xf0, 0x12, 0xe4, 0x38, 0x5c, + 0xd8, 0x71, 0xea, 0x30, 0x52, 0xdf, 0x34, 0x62, + 0xdc, 0xb4, 0x30, 0xe, 0x74, 0xc, 0x5, 0x14, + 0xf, 0x47, 0x25, 0x5, 0x72, 0xc9, 0x14, 0x7c, + 0x1f, 0x6e, 0xdb, 0x6f, 0x83, 0x6, 0xa0, 0xb2, + 0x7f, 0x29, 0xe6, 0xb6, 0xe3, 0x11, 0x23, 0x4b, + 0x68, 0x92, 0xa, 0x49, 0xb5, 0x9d, 0x5d, 0x39, + 0x90, 0xff, 0x9, 0xa0, 0xa, 0x69, 0x6b, 0x2, + 0x18, 0xfb, 0xca, 0x5a, 0x91, 0x1a, 0xd9, 0x19, + 0x6b, 0xd4, 0x92, 0xd3, 0xd9, 0x7, 0xce, 0xcb, + 0xc7, 0xf3, 0xa1, 0x33, 0xcd, 0xa9, 0xb1, 0x44, + 0x8c, 0x93, 0xcd, 0xac, 0xc1, 0x44, 0x12, 0x48, + 0x95, 0x3, 0xdf, 0xc, 0x2f, 0xfc, 0x34, 0x8d, + 0x3, 0xde, 0xc1, 0xed, 0xdc, 0xf0, 0xfa, 0xa5, + 0xb2, 0x62, 0xcd, 0xa2, 0xbf, 0xf7, 0x7e, 0x47, + 0xb6, 0xcc, 0xe4, 0xa6, 0x4e, 0x51, 0xc6, 0x34, + 0xee, 0x83, 0x21, 0xb7, 0xc2, 0xe3, 0x13, 0x92, + 0xfc, 0xc9, 0x6, 0x6b, 0x91, 0x76, 0x7b, 0x2e, + 0x1e, 0xa2, 0xe0, 0x17, 0xab, 0x10, 0xfa, 0xac, + 0xd1, 0x2, 0x33, 0xb0, 0xd3, 0x3d, 0xb9, 0xce, + 0xea, 0xe9, 0x93, 0x5c, 0x98, 0x14, 0x0, 0xc6, + 0x2c, 0xa6, 0xdb, 0x1f, 0xdc, 0x76, 0xfb, 0xeb, + 0x9d, 0x55, 0xa6, 0x5f, 0xd5, 0x8e, 0x13, 0x39, + 0x88, 0x58, 0xff, 0xe8, 0xb4, 0x98, 0x9e, 0x4b, + 0xe7, 0x46, 0xdc, 0x7a, 0x68, 0x5b, 0xa8, 0xc2, + 0xe5, 0xa9, 0x50, 0xe2, 0x8, 0x31, 0x6, 0x3e, + 0x8e, 0xaf, 0x80, 0x24, 0x4e, 0xbd, 0x73, 0x6d, + 0xd9, 0x4b, 0xb4, 0x3e, 0x84, 0x5e, 0x31, 0x8e, + 0xf7, 0xa8, 0x9b, 0x5e, 0x2c, 0xd5, 0xe9, 0x7c, + 0xca, 0xca, 0xfa, 0x8e, 0x87, 0xbf, 0xf5, 0xa3, + 0x2f, 0x73, 0x2f, 0xc0, 0x5f, 0x46, 0xf4, 0x2, + 0xfd, 0xd1, 0x23, 0x6f, 0xc2, 0xc1, 0xc0, 0x86, + 0x62, 0x43, 0xc3, 0x44, 0x3b, 0x2c, 0x3d, 0xc2, + 0xd5, 0xe0, 0x2, 0xae, 0x1, 0x5a, 0x9, 0x89, + 0x52, 0x34, 0xdf, 0xb1, 0x6c, 0x2b, 0x85, 0x77, + 0xa5, 0x83, 0xe3, 0xa5, 0x50, 0x13, 0x2f, 0xf3, + 0xa6, 0x83, 0x60, 0x33, 0xba, 0xd5, 0xd2, 0x96, + 0x8a, 0xcd, 0xee, 0xfa, 0x76, 0x2a, 0x63, 0xec, + 0x41, 0x3a, 0xf3, 0xe5, 0x9e, 0x1d, 0x5e, 0x46, + 0x8, 0xd7, 0xe2, 0x3a, 0x25, 0x6f, 0x37, 0x7e, + 0x0, 0x2d, 0x3d, 0x1b, 0x86, 0xf4, 0xbe, 0x0, + 0x3c, 0xda, 0x82, 0x4a, 0xa3, 0x8, 0x2a, 0x38, + 0x95, 0xe, 0x38, 0xf8, 0x18, 0x6c, 0x42, 0x6f, + 0x30, 0x19, 0x8e, 0x22, 0xf6, 0xb7, 0x18, 0xb7, + 0x93, 0xd, 0x54, 0x72, 0x4, 0x64, 0xc1, 0x19, + 0x76, 0x6e, 0xfc, 0x9e, 0xb0, 0x7c, 0x20, 0x37, + 0xb0, 0xcb, 0x82, 0x3a, 0x20, 0x1d, 0x12, 0x44, + 0xbf, 0x44, 0xc4, 0x4d, 0x33, 0x7e, 0x7b, 0xeb, + 0xd8, 0xb8, 0xa1, 0x75, 0x9e, 0x47, 0x99, 0x64, + 0x92, 0xd3, 0x21, 0x1d, 0x72, 0x63, 0xc7, 0xb3, + 0x3d, 0xfc, 0xb9, 0x4, 0x65, 0x18, 0x94, 0xcc, + 0x20, 0xfe, 0x6f, 0x66, 0x36, 0xba, 0x36, 0x2a, + 0x7, 0xf0, 0x5e, 0x8a, 0xf2, 0x7, 0x1e, 0x9e, + 0x47, 0x2a, 0xc3, 0x7d, 0x7a, 0x20, 0x3c, 0x30, + 0x6f, 0xbe, 0x43, 0x5e, 0x71, 0x6f, 0xd, 0xb8, + 0x3d, 0x1d, 0x3e, 0x18, 0x65, 0x62, 0x75, 0xe8, + 0x34, 0xfd, 0x72, 0xbb, 0xd9, 0x3f, 0xf0, 0xa2, + 0x55, 0xee, 0x91, 0x12, 0x88, 0xda, 0x7, 0x3d, + 0x44, 0x88, 0x70, 0x1f, 0xe0, 0xbe, 0x4b, 0x88, + 0xa8, 0x8e, 0x28, 0x7, 0x73, 0xfd, 0x3f, 0xff, + 0x3e, 0xb2, 0xb5, 0xdb, 0x18, 0x48, 0x9e, 0x73, + 0x6e, 0xd7, 0x24, 0xa9, 0x25, 0xdb, 0x4, 0xe0, + 0xe0, 0xf4, 0x45, 0xc0, 0x1b, 0x82, 0xdf, 0x4e, + 0x48, 0x60, 0x85, 0x9c, 0xd8, 0x90, 0x32, 0xca, + 0x4b, 0xf9, 0xb4, 0xb8, 0xe1, 0xfe, 0xd2, 0xe0, + 0xb2, 0xd6, 0xb8, 0x19, 0x38, 0x34, 0x17, 0x8d, + 0x5e, 0xdf, 0xf4, 0xf1, 0xac, 0x2c, 0x88, 0x7f, + 0x54, 0xbc, 0xf1, 0x39, 0xf2, 0xaf, 0x5a, 0xff, + 0xa7, 0x96, 0x0, 0xf0, 0x27, 0x79, 0x27, 0x2e, + 0x9c, 0xf1, 0x4b, 0xa3, 0xad, 0xdc, 0x8a, 0x2c, + 0x9, 0x4c, 0xd3, 0xcd, 0xd0, 0x2d, 0xb1, 0xec, + 0x4d, 0x68, 0x40, 0xb8, 0xc5, 0x5, 0xfa, 0xb2, + 0x61, 0xb8, 0x31, 0x5, 0xea, 0xb8, 0xa3, 0x34, + 0xa8, 0x8b, 0x3, 0x5b, 0x22, 0x93, 0xba, 0x91, + 0x33, 0x3f, 0x8b, 0x5e, 0xed, 0x86, 0x23, 0x95, + 0xbc, 0x9e, 0xdf, 0xa9, 0x8c, 0xca, 0xb9, 0x97, + 0x9b, 0xc5, 0xca, 0xf4, 0xff, 0x4d, 0x62, 0x52, + 0x1c, 0xd3, 0x4c, 0x42, 0xbf, 0x8a, 0x25, 0x47, + 0xc7, 0x9, 0x4e, 0xe0, 0xb1, 0x72, 0x7d, 0x2, + 0x8f, 0xca, 0x4f, 0x4, 0xc8, 0x74, 0x82, 0x8e, + 0x53, 0xfd, 0xa1, 0x37, 0xda, 0x29, 0x5c, 0xa3, + 0x83, 0xe9, 0xa8, 0xd8, 0x25, 0x27, 0xfe, 0xf7, + 0x41, 0xc4, 0xb0, 0xee, 0x1d, 0x89, 0x1c, 0xe7, + 0xef, 0x86, 0x68, 0xd8, 0x87, 0x4c, 0x4f, 0x49, + 0xeb, 0xbc, 0xb3, 0x81, 0xa7, 0xf4, 0xb4, 0x9b, + 0xc1, 0x52, 0x93, 0x7e, 0xdf, 0x75, 0x75, 0xfc, + 0x45, 0xb2, 0x86, 0xa9, 0x50, 0xb5, 0xa3, 0xf7, + 0x61, 0x60, 0xe4, 0x13, 0x99, 0xc0, 0xf8, 0x49, + 0x7b, 0x61, 0x8b, 0xa8, 0xfa, 0x77, 0x0, 0xe4, + 0x6, 0x9a, 0xc5, 0x51, 0xe4, 0xeb, 0xaf, 0x5f, + 0xb9, 0x5c, 0x74, 0xc8, 0xf8, 0x3e, 0x62, 0x26, + 0xe, 0xe5, 0x85, 0xca, 0x49, 0xa0, 0x2f, 0xf7, + 0x7, 0x99, 0x3e, 0x5c, 0xe0, 0x72, 0xfa, 0xd4, + 0x80, 0x2e, 0xd6, 0x40, 0x6, 0xde, 0x5f, 0xc5, + 0xc5, 0x1, 0xd, 0xbf, 0xdb, 0xb6, 0xb3, 0x92, + 0x76, 0xb3, 0x3f, 0x3d, 0x5d, 0x1, 0x23, 0xb8, + 0xa, 0xcb, 0x80, 0x17, 0x31, 0x19, 0xc7, 0x64, + 0x69, 0xf1, 0x99, 0x53, 0xe5, 0xf2, 0x9f, 0x9d, + 0x3c, 0xda, 0xcb, 0xa6, 0x94, 0x94, 0x44, 0xd3, + 0xc6, 0x8b, 0xb5, 0xae, 0x45, 0x25, 0xef, 0x2a, + 0x24, 0x1, 0x3a, 0xf6, 0xf, 0xe, 0xcb, 0x10, + 0xc4, 0xe0, 0xf4, 0x3d, 0xf4, 0xf5, 0xea, 0x9b, + 0xd1, 0x16, 0x1b, 0x62, 0x11, 0x3e, 0x20, 0x3a, + 0x68, 0xc8, 0xf0, 0xe, 0x55, 0xbe, 0x51, 0x4d, + 0xbe, 0x1f, 0x4f, 0xda, 0x84, 0xda, 0xc4, 0x9e, + 0x24, 0xd7, 0x46, 0x82, 0x56, 0x4e, 0x61, 0x63, + 0xda, 0x18, 0xea, 0xc6, 0xc3, 0x21, 0x89, 0x18, + 0xe, 0x87, 0xb7, 0x91, 0xfe, 0x8d, 0xe, 0xac, + 0x75, 0x58, 0xe5, 0x9f, 0x1f, 0x93, 0xa6, 0x49, + 0x24, 0xa2, 0xc6, 0xe8, 0x9d, 0x9c, 0x6d, 0xc1, + 0xf, 0xfc, 0xe3, 0x57, 0xd3, 0xc2, 0x10, 0x91, + 0x9a, 0xa8, 0xaa, 0xd7, 0xf, 0xaa, 0x75, 0x90, + 0x4a, 0x10, 0xef, 0xb6, 0xdd, 0x6c, 0xd5, 0x1a, + 0xe3, 0xbb, 0xe0, 0x64, 0x44, 0xc, 0x59, 0xa1, + 0xef, 0x3, 0x52, 0xac, 0xa4, 0x85, 0x3e, 0x40, + 0xee, 0x5c, 0xef, 0xcf, 0xb1, 0xaa, 0x88, 0xe5, + 0x56, 0xb8, 0xcd, 0x87, 0xc7, 0xc6, 0xd3, 0xb4, + 0x85, 0x8f, 0x2a, 0xc9, 0xcd, 0x8a, 0x8b, 0x25, + 0x12, 0x71, 0x76, 0xc9, 0xaa, 0x62, 0x75, 0x80, + 0x6e, 0xa3, 0xf9, 0xa5, 0xfc, 0x90, 0xac, 0x28, + 0x13, 0x82, 0xbb, 0x5d, 0xa6, 0x93, 0x47, 0xd4, + 0xf, 0x3b, 0x19, 0xf6, 0x81, 0xdb, 0x55, 0xb0, + 0x47, 0x75, 0x63, 0x93, 0xb4, 0xdd, 0xf0, 0xaf, + 0xb7, 0x44, 0xcb, 0x7, 0x7b, 0x35, 0xc5, 0xe4, + 0x45, 0xfe, 0xbb, 0x11, 0x1a, 0x90, 0x96, 0x3a, + 0x7, 0x2a, 0xef, 0x9c, 0xc, 0xae, 0x38, 0x26, + 0xef, 0xc2, 0xc3, 0x53, 0xfa, 0x54, 0xcf, 0x6f, + 0xf7, 0xa, 0xea, 0x19, 0xa8, 0xf, 0xbd, 0xa7, + 0x3f, 0xcd, 0x38, 0x2c, 0xf3, 0x97, 0xfb, 0xdb, + 0xcb, 0xc5, 0x83, 0x80, 0x91, 0x3d, 0xc7, 0x29, + 0x67, 0x16, 0xa5, 0xd1, 0x41, 0xd0, 0xa1, 0x9b, + 0xde, 0x13, 0x83, 0x12, 0x36, 0x75, 0x81, 0x71, + 0x6b, 0xbc, 0x72, 0xcb, 0x37, 0x4, 0x6, 0x7c, + 0x3a, 0x22, 0x2b, 0xa, 0x11, 0xd3, 0x33, 0x8f, + 0x3, 0x54, 0x8e, 0x79, 0xb6, 0x36, 0x93, 0x92, + 0xb8, 0xf, 0x24, 0x4a, 0xd3, 0xd5, 0x27, 0x66, + 0xd1, 0xde, 0xe3, 0xaa, 0x4b, 0x2a, 0xe9, 0x22, + 0x9b, 0xbf, 0x6e, 0x9a, 0xf7, 0xa, 0x2f, 0x24, + 0x13, 0xd5, 0xd5, 0xbb, 0xa3, 0xba, 0x8f, 0xfc, + 0x28, 0xa8, 0xbe, 0xe6, 0x9f, 0xea, 0xed, 0xb1, + 0xba, 0xaf, 0xf, 0x1c, 0x1e, 0x51, 0xf8, 0xd7, + 0x1b, 0xa5, 0xa6, 0x63, 0x40, 0x6e, 0x3f, 0xa2, + 0x57, 0x6f, 0x57, 0xe4, 0x27, 0xc2, 0x3c, 0x33, + 0xc6, 0x9c, 0x24, 0xd0, 0x53, 0xc4, 0xfc, 0xed, + 0x8e, 0x1d, 0xf, 0xc3, 0x86, 0x9, 0x3d, 0x1d, + 0xc2, 0xdb, 0x24, 0x1a, 0x65, 0xf4, 0x30, 0xa5, + 0xc, 0x48, 0x37, 0xc5, 0x53, 0x35, 0x3b, 0xab, + 0xd, 0x96, 0x30, 0xd7, 0x1d, 0x66, 0x18, 0xc2, + 0x47, 0x3a, 0xef, 0xbe, 0x2e, 0xe4, 0x54, 0x9d, + 0xc4, 0xa5, 0xb9, 0xb3, 0x4c, 0x12, 0x73, 0x35, + 0xf0, 0x7, 0xe, 0x36, 0x88, 0xb2, 0x4b, 0x29, + 0xb, 0x4e, 0x84, 0x11, 0xaa, 0x9a, 0x3e, 0xb1, + 0xd7, 0xec, 0xfb, 0x7f, 0x10, 0x70, 0x1f, 0x26, + 0xf0, 0x27, 0x46, 0x5d, 0x4, 0x51, 0x97, 0x29, + 0xb4, 0x66, 0x39, 0x1, 0x82, 0x47, 0xd8, 0x5f, + 0xa9, 0xb3, 0xa1, 0xb8, 0xde, 0x1, 0xe1, 0xc4, + 0x47, 0xc5, 0xe8, 0xe6, 0xbb, 0xc0, 0xb6, 0x41, + 0x55, 0x10, 0x79, 0xa8, 0xd0, 0xd, 0x1, 0x56, + 0x29, 0x6c, 0xa5, 0x96, 0x87, 0x59, 0x4b, 0xd, + 0xc8, 0x3, 0x5, 0xaa, 0xa9, 0x6a, 0xb1, 0x10, + 0xbc, 0x1, 0x68, 0xd3, 0xa5, 0x52, 0x41, 0xe1, + 0x1f, 0x53, 0x7, 0xc6, 0xad, 0xb8, 0xc4, 0xf0, + 0x28, 0xe9, 0x3, 0x3a, 0xee, 0xce, 0x2c, 0xe2, + 0xb0, 0xda, 0x78, 0x3d, 0x37, 0x7, 0x2d, 0x1f, + 0xf1, 0x47, 0x81, 0x4, 0x67, 0x6e, 0xd, 0xa1, + 0x2b, 0x4, 0xe8, 0xd9, 0xf4, 0xaf, 0x35, 0xca, + 0xa5, 0xd1, 0xe3, 0xec, 0xc5, 0x82, 0x50, 0x99, + 0x9a, 0xee, 0xea, 0x53, 0x41, 0x86, 0x97, 0x44, + 0xeb, 0x58, 0x43, 0x47, 0xe7, 0xa0, 0xd3, 0x28, + 0xfc, 0xe7, 0x13, 0x8b, 0x56, 0xe3, 0xdb, 0xa9, + 0xcd, 0x9, 0xc8, 0x7, 0x11, 0xeb, 0xbf, 0xac, + 0x76, 0x72, 0x60, 0xaf, 0x9c, 0xba, 0x8a, 0x64, + 0xfb, 0xf4, 0xab, 0x27, 0x29, 0xe7, 0xec, 0x69, + 0x21, 0xcb, 0x5b, 0x79, 0x56, 0x10, 0xc1, 0x8, + 0xd5, 0x5d, 0x93, 0xb1, 0x70, 0x88, 0xf2, 0x19, + 0x41, 0xc6, 0xc2, 0x84, 0xdd, 0xf0, 0xb3, 0x40, + 0x12, 0x71, 0x24, 0x54, 0xc4, 0x5e, 0xfb, 0x5f, + 0x47, 0x8c, 0xa9, 0x4, 0x5a, 0xd5, 0x61, 0x19, + 0xb5, 0x7f, 0xc9, 0xbd, 0x87, 0xb2, 0xcd, 0x57, + 0x99, 0x50, 0x67, 0x1d, 0xb0, 0x1d, 0x82, 0xdd, + 0xef, 0x32, 0x38, 0xb9, 0xc7, 0x86, 0xb4, 0xd2, + 0xd6, 0xe1, 0x33, 0xb2, 0xdb, 0x5e, 0xc2, 0xa3, + 0x49, 0xa6, 0x5f, 0x79, 0x32, 0x50, 0x41, 0x5b, + 0xd7, 0x87, 0x74, 0xf5, 0xc9, 0x9c, 0x78, 0xb7, + 0xb, 0x1f, 0x72, 0xba, 0xd9, 0x3a, 0x4d, 0x18, + 0x45, 0x1d, 0xad, 0xef, 0xc4, 0xdc, 0x30, 0xe8, + 0x2, 0xb1, 0x7f, 0x6c, 0x8f, 0xaa, 0xd0, 0x40, + 0x17, 0xe, 0x58, 0x93, 0x42, 0x49, 0x63, 0x77, + 0x48, 0x55, 0x90, 0x2f, 0x7c, 0x3b, 0xee, 0x3c, + 0xac, 0xd, 0xd8, 0x72, 0x23, 0xd7, 0xa5, 0x6e, + 0xb0, 0xd2, 0x91, 0x25, 0x60, 0x9a, 0x52, 0xab, + 0xbd, 0x63, 0xce, 0xba, 0xda, 0xb1, 0xd7, 0xc7, + 0x3d, 0x21, 0x4e, 0x9c, 0x5a, 0x1e, 0x8d, 0xf4, + 0xa, 0xdb, 0xd9, 0xf, 0x20, 0x7e, 0xfb, 0xbf, + 0x36, 0x9c, 0x4f, 0xbd, 0xf7, 0xdb, 0x5b, 0xa2, + 0x6, 0xb2, 0x0, 0xe2, 0xa2, 0x9e, 0x4e, 0x19, + 0xd4, 0x69, 0xa9, 0x51, 0x69, 0x8b, 0xf5, 0xe1, + 0xad, 0x89, 0x8, 0xc5, 0x4f, 0xac, 0x1b, 0x7d, + 0xe7, 0xa, 0x9, 0x7d, 0x34, 0xf5, 0x3f, 0x46, + 0x80, 0xb9, 0xb9, 0x45, 0x58, 0xcd, 0x6c, 0xb5, + 0x5f, 0x60, 0xeb, 0x5a, 0xe3, 0xa3, 0x8, 0x5e, + 0xb1, 0xc4, 0x73, 0xc5, 0xa5, 0x67, 0x56, 0xd3, + 0xc6, 0x8a, 0x55, 0x6b, 0xd7, 0xd7, 0xc, 0x20, + 0xe6, 0xc, 0x73, 0x8, 0x2, 0x4b, 0xfb, 0xdd, + 0x4d, 0x4e, 0xa8, 0xb8, 0xd8, 0x4b, 0x53, 0x2f, + 0xc2, 0xfb, 0x5d, 0xa1, 0x6a, 0x16, 0x6b, 0xe, + 0xf1, 0xa1, 0xa5, 0x5b, 0xdf, 0x9c, 0x23, 0xb5, + 0x94, 0x9c, 0xae, 0x7b, 0xbe, 0x42, 0xb5, 0x79, + 0x80, 0xc3, 0x43, 0x41, 0xa4, 0x1b, 0x18, 0xfc, + 0x52, 0xcf, 0x43, 0xc5, 0x80, 0x7b, 0xbd, 0xc1, + 0x20, 0x5e, 0x65, 0xec, 0xc5, 0xfc, 0x3, 0xec, + 0x8f, 0x61, 0x66, 0xf5, 0x15, 0x67, 0xc8, 0xb6, + 0xef, 0x9a, 0xba, 0xb7, 0xcb, 0x2c, 0xac, 0x1b, + 0x50, 0xda, 0xb6, 0x29, 0xa4, 0x37, 0xe9, 0x96, + 0xa0, 0x7, 0x7d, 0x49, 0xa6, 0xce, 0xf3, 0xf0, + 0x19, 0xdf, 0x61, 0xc7, 0xa4, 0x7b, 0x5a, 0xd4, + 0x99, 0xb2, 0x64, 0xe7, 0xd1, 0x6b, 0x7f, 0xe8, + 0xb8, 0xd3, 0x89, 0xee, 0x96, 0xc0, 0xed, 0x5d, + 0x7e, 0x48, 0x2, 0xd2, 0x25, 0xd0, 0x5, 0xef, + 0x93, 0x72, 0x7c, 0x8c, 0xbd, 0x6e, 0x49, 0xd3, + 0x38, 0x46, 0x1c, 0xff, 0x28, 0x4e, 0x1b, 0xad, + 0x39, 0x2f, 0x65, 0x26, 0xe2, 0x70, 0x3d, 0xb8, + 0x7a, 0xd3, 0x38, 0x38, 0xfc, 0x3a, 0x67, 0x78, + 0xdb, 0x9, 0xcb, 0xbf, 0xc9, 0xe1, 0xee, 0x69, + 0x2b, 0xd, 0xb1, 0x79, 0x13, 0xd0, 0xa5, 0x75, + 0x6, 0x8, 0x79, 0xa7, 0x7c, 0xc, 0xe7, 0x1b, + 0x9c, 0x36, 0x64, 0xbe, 0x20, 0x65, 0xa2, 0xd4, + 0xd9, 0xc, 0x68, 0xe, 0x88, 0x2b, 0x93, 0x60, + 0xf1, 0xa5, 0x82, 0xc5, 0x4d, 0x2b, 0x7d, 0x73, + 0xe9, 0x13, 0x8c, 0xc1, 0x8, 0xbd, 0x21, 0x65, + 0x77, 0x2f, 0x34, 0xb1, 0x97, 0x9f, 0xd8, 0x55, + 0xcf, 0x75, 0xc2, 0xf2, 0x41, 0x68, 0xc1, 0x9c, + 0x1c, 0xd7, 0x23, 0xbf, 0x83, 0x2a, 0x9, 0x66, + 0xce, 0x8f, 0xd2, 0x12, 0x79, 0x93, 0xef, 0x8, + 0x9b, 0xeb, 0x2f, 0xc, 0xe4, 0x5b, 0x71, 0x1a, + 0xef, 0x11, 0x65, 0xd8, 0x6d, 0x8c, 0x59, 0x53, + 0x70, 0x1d, 0xb5, 0x81, 0xff, 0xc0, 0x7d, 0x87, + 0xa5, 0x21, 0x5d, 0x9f, 0x63, 0xb2, 0xe7, 0xe9, + 0xd0, 0x49, 0x41, 0xc7, 0x3c, 0xe1, 0x2b, 0xb1, + 0xac, 0x15, 0xcd, 0xb0, 0xa8, 0xdc, 0xae, 0x3b, + 0xef, 0x32, 0x98, 0x8c, 0xc7, 0x40, 0xa6, 0x81, + 0x1, 0xa1, 0x7d, 0x89, 0x46, 0x99, 0x91, 0x24, + 0xce, 0xb2, 0x70, 0x82, 0x92, 0xf3, 0x60, 0x66, + 0x34, 0x6, 0x37, 0xad, 0x5c, 0xed, 0xc3, 0x27, + 0x68, 0x8c, 0x56, 0xe7, 0xf, 0x73, 0x5c, 0x7e, + 0x9e, 0xd0, 0x8c, 0x99, 0x5a, 0xb1, 0x15, 0x98, + 0xbb, 0x79, 0x9f, 0xd1, 0x69, 0xce, 0x76, 0x5, + 0xcb, 0x8e, 0x18, 0xb3, 0x84, 0x65, 0xa9, 0x2, + 0xbc, 0x43, 0x8b, 0x7e, 0xe9, 0xe2, 0xe6, 0x74, + 0x31, 0x8d, 0xe7, 0xa2, 0x42, 0x8f, 0xca, 0x38, + 0x59, 0x85, 0x25, 0x47, 0xd2, 0x86, 0x47, 0x9, + 0xc2, 0x11, 0x2, 0x91, 0xe6, 0xf3, 0x47, 0xc2, + 0x9c, 0x28, 0x2f, 0xbb, 0xac, 0xde, 0x9f, 0xd, + 0xc2, 0x96, 0x4f, 0x43, 0xca, 0x32, 0xed, 0x34, + 0xba, 0xad, 0xef, 0xbe, 0x68, 0xc7, 0xa2, 0x83, + 0xaf, 0xe, 0xd3, 0x72, 0x52, 0xd1, 0x76, 0x3d, + 0x9a, 0x98, 0x39, 0xf4, 0x3e, 0x14, 0x27, 0xff, + 0xb2, 0x37, 0x23, 0xc5, 0x6d, 0x66, 0xef, 0xaa, + 0xfe, 0xe7, 0xe4, 0x86, 0xa1, 0xe, 0x4e, 0x36, + 0x64, 0xb1, 0x67, 0xf, 0x94, 0x6f, 0x77, 0xd5, + 0xec, 0xe2, 0x5e, 0xc8, 0xe3, 0x64, 0x29, 0x92, + 0xd, 0x20, 0x34, 0x9f, 0x19, 0x6e, 0x85, 0xf8, + 0x48, 0x78, 0xb0, 0xf, 0x42, 0xb2, 0x8c, 0xea, + 0xc2, 0x4d, 0xd3, 0x23, 0xb, 0x4d, 0x20, 0x33, + 0xc7, 0x46, 0x0, 0x45, 0x37, 0xc6, 0xcb, 0xd0, + 0xec, 0x11, 0xc6, 0x74, 0x91, 0x7d, 0x6b, 0x54, + 0x56, 0x10, 0x8d, 0xd0, 0xce, 0xe8, 0x57, 0x3b, + 0x83, 0xd8, 0x25, 0x51, 0x79, 0x48, 0xa, 0xa5, + 0xc3, 0xe4, 0x65, 0x33, 0xb2, 0x89, 0xa6, 0x4c, + 0xe8, 0xc8, 0x9e, 0xce, 0xea, 0x2a, 0x55, 0x40, + 0xfc, 0x26, 0x29, 0xd4, 0x2d, 0x7e, 0xe1, 0xb1, + 0x4d, 0x65, 0x1, 0xe9, 0x98, 0xc9, 0xf4, 0x69, + 0x10, 0xd9, 0xa3, 0xf9, 0x34, 0xaf, 0x3c, 0x34, + 0x64, 0x23, 0xde, 0xb8, 0x1c, 0x33, 0x18, 0x74, + 0x67, 0xb4, 0x4a, 0x71, 0xa6, 0x89, 0x2, 0xfe, + 0xf7, 0xf1, 0x32, 0xc7, 0x98, 0xad, 0xe5, 0x10, + 0x98, 0x3c, 0x6c, 0xaf, 0x1f, 0x13, 0x3d, 0xcc, + 0xfc, 0x3b, 0x67, 0x33, 0x34, 0xc9, 0x31, 0xcd, + 0x3f, 0xd, 0x3c, 0x5a, 0xb6, 0xc2, 0x8, 0xea, + 0xe2, 0xae, 0xdd, 0xfc, 0x6f, 0xca, 0xb5, 0x67, + 0x11, 0xce, 0xd5, 0xda, 0x3a, 0x8b, 0x7, 0xf2, + 0xc0, 0x9e, 0x78, 0x18, 0x92, 0x9f, 0x64, 0x26, + 0x9f, 0x66, 0x62, 0x66, 0xa1, 0x7e, 0x3, 0xf5, + 0xb9, 0xe6, 0x74, 0x20, 0x88, 0xb7, 0x7e, 0x62, + 0x7a, 0x33, 0x21, 0x9, 0x9c, 0x91, 0x3b, 0x62, + 0x9, 0x46, 0xd3, 0xd1, 0x1f, 0xc5, 0x3a, 0x8f, + 0x69, 0x27, 0x2c, 0x7b, 0xec, 0xda, 0x79, 0xf1, + 0xc9, 0xe9, 0x98, 0xd0, 0xa, 0xc9, 0xf6, 0x37, + 0x28, 0xf8, 0xfc, 0xe, 0xdc, 0xf, 0xe9, 0x23, + 0xf6, 0x84, 0x25, 0x96, 0x2c, 0x24, 0x14, 0xd7, + 0xe2, 0x5e, 0x1c, 0x56, 0x7f, 0x99, 0x98, 0x62, + 0x76, 0xcc, 0x84, 0x44, 0xd6, 0xb9, 0x47, 0x2b, + 0x52, 0xfb, 0x42, 0x40, 0xf3, 0x63, 0xaf, 0xd4, + 0x10, 0x5, 0xf9, 0x3b, 0xc8, 0x53, 0xa9, 0x45, + 0xa4, 0x50, 0x41, 0x83, 0xe8, 0x4a, 0x9, 0xb6, + 0xf1, 0x77, 0x70, 0xe3, 0x61, 0x30, 0xd8, 0x90, + 0x49, 0x52, 0x4b, 0x4a, 0xf2, 0x66, 0x84, 0xaf, + 0x71, 0x1, 0x40, 0x66, 0xf6, 0x3, 0xc9, 0x23, + 0xb1, 0x1a, 0xc1, 0xb2, 0xf7, 0x35, 0x1a, 0xc9, + 0x3a, 0x75, 0xb1, 0xa7, 0x4, 0xff, 0x69, 0xa, + 0x90, 0x58, 0xd4, 0xf4, 0x16, 0x79, 0xe1, 0xae, + 0x39, 0x9d, 0xbb, 0x32, 0x6b, 0x3, 0xe2, 0xf5, + 0x73, 0x83, 0x7e, 0x3c, 0xf8, 0x29, 0xab, 0xcc, + 0xdc, 0xf0, 0x13, 0xdb, 0x86, 0x28, 0x88, 0x8e, + 0xde, 0x6a, 0x29, 0xf1, 0xea, 0x0, 0x83, 0x97, + 0x1, 0x32, 0x5f, 0xaa, 0x5b, 0x1b, 0xe4, 0x87, + 0xec, 0x90, 0x45, 0xc7, 0xc5, 0x6c, 0x11, 0x83, + 0x95, 0xab, 0xdd, 0x71, 0x69, 0x24, 0xc, 0x5c, + 0xc0, 0xf3, 0xc1, 0xb0, 0x5e, 0x1, 0x5e, 0x4, + 0xa1, 0x6e, 0x6e, 0x7d, 0x3f, 0x6f, 0xbd, 0x5d, + 0x9, 0x8f, 0x23, 0x53, 0x74, 0x4b, 0xa9, 0x53, + 0xd2, 0x10, 0xa1, 0xc0, 0x8e, 0x18, 0xa, 0x2f, + 0x88, 0x8d, 0x4b, 0xf8, 0xc2, 0x3d, 0xeb, 0x34, + 0x23, 0xa, 0x80, 0xc, 0x69, 0x21, 0x3, 0xc1, + 0x6f, 0xbe, 0xdf, 0xf6, 0x2c, 0x27, 0x77, 0xa2, + 0xc5, 0x5c, 0x9, 0x54, 0x5d, 0x4a, 0x4c, 0xb, + 0x6b, 0xb5, 0x88, 0x11, 0x42, 0x62, 0x39, 0x89, + 0x9e, 0x36, 0xd3, 0x91, 0xf6, 0x70, 0x18, 0x35, + 0x79, 0xaf, 0x73, 0xf3, 0x0, 0x75, 0x5a, 0xa3, + 0xce, 0xf1, 0x42, 0x80, 0x19, 0x5e, 0x42, 0x56, + 0x53, 0x85, 0xbb, 0xf4, 0x29, 0xac, 0x84, 0x1d, + 0x97, 0x1, 0x1c, 0xc4, 0x58, 0xcb, 0x33, 0xc4, + 0xdc, 0x1e, 0x59, 0x8f, 0x48, 0xa9, 0x59, 0xfd, + 0xaf, 0xa3, 0x5c, 0x19, 0x17, 0x6b, 0x46, 0x2d, + 0xab, 0x44, 0xa3, 0xcc, 0x1a, 0xaa, 0x23, 0x4e, + 0x58, 0x37, 0x7b, 0x11, 0x14, 0xc2, 0xf1, 0xc9, + 0x58, 0x99, 0xd3, 0x3c, 0xec, 0xb9, 0xbe, 0x17, + 0x3c, 0x8d, 0x1c, 0x87, 0x9d, 0xe1, 0xb9, 0xad, + 0x68, 0x36, 0xd5, 0xfc, 0x24, 0x9b, 0x34, 0x5, + 0x26, 0xac, 0x15, 0x9f, 0xd6, 0x70, 0x74, 0x6c, + 0x72, 0xf, 0x6, 0x6, 0x5a, 0xc, 0xc0, 0x78, + 0x47, 0x8e, 0xcf, 0xf2, 0xce, 0x8, 0xe2, 0xa4, + 0xc6, 0x7d, 0x2d, 0x70, 0x14, 0xe2, 0xc6, 0xfc, + 0x63, 0x7a, 0x42, 0x8c, 0x45, 0xae, 0xe8, 0x3b, + 0x30, 0x48, 0xda, 0x3e, 0x14, 0xb5, 0x8b, 0x10, + 0xae, 0x56, 0xbd, 0x17, 0xdf, 0xcb, 0x63, 0xf5, + 0xb, 0x2b, 0xd7, 0x34, 0x7c, 0x96, 0x43, 0xe9, + 0x17, 0xd4, 0x53, 0x2b, 0x4e, 0xba, 0x61, 0x57, + 0x92, 0xdb, 0xe8, 0x37, 0xf4, 0xa3, 0x59, 0x88, + 0x74, 0xc2, 0x3c, 0x5d, 0x54, 0x30, 0xb9, 0x6, + 0xbe, 0x75, 0x13, 0xe8, 0xf2, 0xe8, 0xcb, 0x45, + 0x73, 0x70, 0xaf, 0x94, 0xe6, 0xc5, 0xb0, 0xdf, + 0xd2, 0xd5, 0x57, 0x97, 0x7c, 0x97, 0xde, 0x55, + 0xaf, 0xbb, 0xed, 0x19, 0x35, 0x17, 0xf4, 0x23, + 0x38, 0x9c, 0xce, 0x37, 0xfe, 0xd8, 0x4e, 0xd8, + 0x99, 0xba, 0x33, 0x22, 0xf2, 0xeb, 0xab, 0x97, + 0xee, 0x9d, 0xab, 0x67, 0x95, 0x35, 0xdf, 0xc8, + 0xb6, 0xa0, 0xf, 0x15, 0x51, 0xa9, 0x76, 0x15, + 0xdd, 0xbd, 0xac, 0x12, 0xce, 0x51, 0xde, 0x68, + 0x15, 0xaf, 0x27, 0xcf, 0xd1, 0xba, 0x7c, 0x17, + 0xef, 0xbf, 0xbb, 0xc0, 0x6e, 0x58, 0x73, 0xf6, + 0x57, 0xe1, 0x8d, 0xb0, 0x9a, 0x5a, 0x9, 0x19, + 0xef, 0xdd, 0x4, 0xe1, 0x76, 0x94, 0x31, 0xd7, + 0x26, 0x9f, 0x9c, 0x27, 0xc4, 0x2b, 0x4b, 0xf6, + 0x3b, 0xa1, 0x8c, 0xf4, 0x21, 0xde, 0x39, 0x14, + 0x5a, 0x54, 0xac, 0x95, 0x2f, 0xa0, 0x60, 0x53, + 0x87, 0x5b, 0x71, 0x92, 0xae, 0xf9, 0x6c, 0x62, + 0x76, 0x7e, 0x91, 0x11, 0xa6, 0xf4, 0xf2, 0xa8, + 0xdf, 0xc1, 0xf6, 0x3a, 0xdb, 0x34, 0x96, 0x9, + 0x71, 0xb4, 0x4, 0xfa, 0xd4, 0x3, 0x46, 0x16, + 0x78, 0x41, 0x42, 0x7d, 0x15, 0x68, 0x63, 0x55, + 0x23, 0x4, 0x46, 0x5d, 0xe1, 0xd8, 0xe7, 0x5f, + 0x55, 0x39, 0xd2, 0x45, 0xb2, 0x0, 0x35, 0xde, + 0xd8, 0x9d, 0xc7, 0x3a, 0x8f, 0x37, 0x7e, 0xe5, + 0x9e, 0xcf, 0xd1, 0x6a, 0x22, 0xe1, 0x51, 0xb2, + 0xe6, 0x99, 0x3e, 0x83, 0xeb, 0x34, 0x9d, 0x34, + 0x7, 0x1c, 0xbe, 0x91, 0x69, 0x9e, 0xaa, 0xcb, + 0x86, 0xd2, 0xb6, 0xed, 0xa5, 0x4, 0xf9, 0x7d, + 0xf8, 0xba, 0x2a, 0x27, 0x38, 0xe1, 0xaa, 0x22, + 0x94, 0x46, 0x1f, 0x1b, 0xcf, 0xc4, 0x78, 0x88, + 0x3d, 0x50, 0x83, 0x30, 0x61, 0x87, 0xb6, 0x38, + 0x5b, 0x4f, 0x5a, 0x3, 0x2d, 0x5d, 0xa6, 0x33, + 0x38, 0xe7, 0x8b, 0x60, 0x1, 0x8e, 0xde, 0x69, + 0x8e, 0x4d, 0x60, 0x24, 0x3b, 0x47, 0x4b, 0x56, + 0xea, 0xf9, 0xc8, 0xfa, 0x2d, 0x65, 0x7b, 0xad, + 0xee, 0xe4, 0x91, 0x20, 0x6f, 0x64, 0x6e, 0x81, + 0x69, 0xda, 0xf5, 0x3c, 0x3d, 0xff, 0x4c, 0xe9, + 0x9b, 0x4d, 0xa8, 0x67, 0x9e, 0x67, 0x7f, 0x84, + 0xdb, 0x7a, 0xb7, 0x24, 0x32, 0xa0, 0x80, 0x16, + 0x55, 0x2d, 0x1d, 0xc1, 0x3a, 0x19, 0xd3, 0x17, + 0x74, 0x8e, 0x2a, 0x5c, 0xf6, 0x71, 0xf7, 0x25, + 0x3a, 0x54, 0x28, 0xef, 0x50, 0x78, 0x14, 0x5, + 0x49, 0x8a, 0xbb, 0x71, 0xb2, 0xed, 0xa2, 0x5b, + 0xff, 0x2, 0xe, 0xd8, 0x1a, 0x8b, 0x3c, 0xcc, + 0x58, 0x27, 0x71, 0x2d, 0xb, 0x11, 0x9f, 0x6, + 0xc3, 0xfd, 0x37, 0x19, 0xdb, 0xec, 0xa5, 0x4b, + 0x93, 0x81, 0xb6, 0xff, 0xd4, 0xf5, 0x7b, 0xf5, + 0x49, 0x5b, 0x95, 0x9, 0xa4, 0xca, 0xa5, 0x33, + 0x9a, 0xfc, 0x97, 0xec, 0x7b, 0xb, 0xb9, 0x2e, + 0x3b, 0x9d, 0x52, 0xc2, 0xa2, 0x9, 0xc8, 0xbf, + 0x39, 0x16, 0xce, 0x42, 0x3, 0x4b, 0xe3, 0xfc, + 0xfd, 0xc, 0x37, 0x96, 0x10, 0x36, 0xad, 0x44, + 0xda, 0xc5, 0x58, 0x3e, 0x78, 0x52, 0xa1, 0x65, + 0xed, 0x89, 0xe7, 0xea, 0xbf, 0xa8, 0x6a, 0xf2, + 0xa7, 0x8e, 0x9d, 0x1, 0x25, 0x83, 0x57, 0x5f, + 0x51, 0xe6, 0xe1, 0xa4, 0x4f, 0xf6, 0x81, 0xd7, + 0xe6, 0x98, 0x29, 0x98, 0x58, 0xfe, 0xda, 0x45, + 0xab, 0x38, 0x6, 0x91, 0x97, 0xb7, 0xa3, 0x4f, + 0x93, 0x8d, 0x8a, 0x8b, 0x5, 0xe9, 0x5, 0x98, + 0x3b, 0xc4, 0xb7, 0xe1, 0x68, 0x58, 0xa0, 0x3b, + 0x99, 0xea, 0x8a, 0xa9, 0xfb, 0x55, 0xe2, 0xc7, + 0x1d, 0x87, 0x3, 0x40, 0x24, 0x13, 0x28, 0x6a, + 0x34, 0x8a, 0xff, 0x62, 0x91, 0xb8, 0x7d, 0x28, + 0x1a, 0xd2, 0xfc, 0x4e, 0xa3, 0xda, 0x66, 0x69, + 0x15, 0xc0, 0xda, 0x15, 0x3e, 0x67, 0x12, 0x95, + 0x6, 0x1b, 0xf4, 0x60, 0xe4, 0x39, 0x82, 0xe9, + 0x2e, 0xbe, 0xab, 0x8c, 0x2c, 0x6e, 0xd6, 0x40, + 0x91, 0xc0, 0x68, 0xf7, 0xa2, 0x41, 0xd0, 0xa8, + 0x7, 0xab, 0x13, 0x34, 0x16, 0xf4, 0x73, 0x4f, + 0x1d, 0x21, 0x1a, 0x7d, 0xad, 0x43, 0x12, 0xf, + 0xb7, 0xfe, 0xa3, 0x81, 0xe9, 0xb5, 0x2d, 0xd3, + 0xa, 0x29, 0xb5, 0x32, 0xcb, 0x49, 0x6f, 0x1, + 0x90, 0x45, 0x62, 0xca, 0x1b, 0x66, 0x39, 0x88, + 0x1c, 0xee, 0x30, 0xa8, 0xb5, 0x37, 0xd0, 0xfa, + 0x46, 0x52, 0x16, 0x30, 0x17, 0xcf, 0x88, 0xd0, + 0x4, 0x5d, 0xde, 0x5e, 0x4f, 0xe7, 0xa9, 0xbf, + 0x3c, 0x29, 0x3a, 0x63, 0x67, 0x23, 0xb3, 0x7c, + 0x51, 0x17, 0xfe, 0x8d, 0xdb, 0xc8, 0x8d, 0x70, + 0xe9, 0x6f, 0x56, 0xe5, 0x44, 0xb2, 0x94, 0xeb, + 0x47, 0xca, 0x3a, 0xdc, 0xe3, 0x33, 0x87, 0x9c, + 0xe8, 0x89, 0x4b, 0x41, 0xb8, 0xb3, 0x69, 0xb0, + 0x7f, 0xc8, 0xc7, 0x74, 0xf5, 0xcb, 0x20, 0xad, + 0xea, 0xbb, 0x3d, 0x11, 0xc6, 0xc0, 0xd2, 0x88, + 0x8b, 0x16, 0xee, 0x62, 0x5a, 0x4d, 0x32, 0xe7, + 0x48, 0xae, 0xab, 0x5e, 0xc2, 0x83, 0xc4, 0xfc, + 0xd1, 0xb9, 0x71, 0xf2, 0x9, 0x7f, 0xdc, 0xbc, + 0x28, 0x74, 0xa0, 0x37, 0xa9, 0x5b, 0x6c, 0x7c, + 0x9b, 0x61, 0x94, 0x88, 0xf7, 0x40, 0x84, 0x75, + 0xa5, 0x50, 0xab, 0xb0, 0x92, 0x66, 0x10, 0x66, + 0xf6, 0xec, 0x6b, 0x5e, 0x31, 0x9b, 0xc4, 0xfa, + 0x95, 0x8b, 0xe7, 0xd4, 0xba, 0x81, 0xd2, 0x85, + 0x30, 0x4, 0x8b, 0x3d, 0xfa, 0x8a, 0x8f, 0x9b, + 0x54, 0x6a, 0x4d, 0x35, 0xa2, 0xe9, 0x58, 0x95, + 0xe3, 0xd1, 0x71, 0xcd, 0x3a, 0x54, 0xae, 0xd9, + 0x5c, 0x83, 0xd, 0x15, 0x64, 0x66, 0xee, 0x39, + 0xa1, 0x85, 0xe2, 0x28, 0xf5, 0x66, 0x5f, 0xec, + 0x39, 0x70, 0x96, 0x2c, 0x72, 0x9e, 0x57, 0xfd, + 0x57, 0x27, 0xb7, 0xda, 0x79, 0x39, 0xd8, 0x3b, + 0x2e, 0xa3, 0xb0, 0xde, 0xbf, 0x60, 0xb6, 0x42, + 0x78, 0x9d, 0x8f, 0xe8, 0x1c, 0x7c, 0x45, 0x72, + 0x3, 0xc4, 0xd5, 0x81, 0xf6, 0xe6, 0x9, 0x29, + 0x1e, 0xcd, 0xf3, 0xe, 0xd6, 0x65, 0xee, 0x6d, + 0x90, 0x17, 0x95, 0x20, 0x54, 0xf1, 0xd, 0x2f, + 0xa0, 0xac, 0xe3, 0x4b, 0xfc, 0xa4, 0xdc, 0xab, + 0x9d, 0x9e, 0x32, 0x63, 0x72, 0xd1, 0xb4, 0xef, + 0xf1, 0x83, 0xa7, 0xd7, 0x2b, 0x1a, 0x9a, 0x9e, + 0xfa, 0x1e, 0xb, 0x2b, 0xdc, 0x7b, 0x87, 0x96, + 0xf, 0xdb, 0x75, 0xb9, 0x6, 0x2b, 0xd3, 0x95, + 0xc5, 0xb3, 0x9, 0x53, 0x94, 0x54, 0x1f, 0xd0, + 0x75, 0x5a, 0x36, 0x6a, 0x7c, 0x82, 0xdb, 0xb1, + 0xa2, 0x17, 0xbc, 0xeb, 0x1f, 0xfa, 0x34, 0x3d, + 0xee, 0x68, 0xee, 0x93, 0x33, 0xfb, 0xcb, 0xd2, + 0xa3, 0xd1, 0x24, 0x5e, 0xf4, 0x9, 0xbe, 0x5a, + 0x68, 0x9e, 0x3e, 0xd4, 0x81, 0xcd, 0xa3, 0x1e, + 0x2, 0x13, 0xb4, 0x79, 0x94, 0xc9, 0xb2, 0xde, + 0x56, 0xf1, 0x7b, 0x2f, 0xe2, 0x56, 0xe1, 0x10, + 0xf4, 0x73, 0x2d, 0xc9, 0xca, 0x4d, 0x5f, 0x11, + 0x9e, 0xd6, 0x3c, 0x73, 0x12, 0x57, 0xe9, 0x14, + 0xe0, 0x8d, 0xdd, 0x4b, 0x8a, 0xbb, 0xb3, 0x78, + 0xbe, 0x16, 0x94, 0x93, 0x51, 0x33, 0x7a, 0xa5, + 0x41, 0x14, 0x60, 0x82, 0x94, 0x67, 0x70, 0xea, + 0xe6, 0x3, 0x7f, 0xc5, 0xa0, 0x20, 0x15, 0x88, + 0x53, 0xe3, 0x7e, 0x16, 0x52, 0xe4, 0xca, 0xa0, + 0x6f, 0xb9, 0x68, 0x4e, 0x30, 0xb9, 0x8c, 0xe6, + 0x9c, 0x5e, 0xc2, 0x93, 0xf9, 0xe1, 0x41, 0x4b, + 0x18, 0x42, 0x6f, 0x8f, 0x96, 0x3d, 0x2b, 0x28, + 0xd5, 0x53, 0x62, 0xdd, 0x6b, 0xd0, 0xf8, 0x2e, + 0xa6, 0x97, 0xe5, 0x87, 0xc5, 0xf6, 0x96, 0x7b, + 0xc4, 0x3e, 0x84, 0xc9, 0xf6, 0x34, 0x63, 0x46, + 0xe1, 0x10, 0xa5, 0x91, 0x6b, 0xff, 0x10, 0x3f, + 0x50, 0x2e, 0xd7, 0x39, 0x12, 0x7a, 0x15, 0x85, + 0xed, 0x99, 0xdb, 0x9b, 0x99, 0x6b, 0xfa, 0xfa, + 0x93, 0x7, 0x44, 0xbe, 0xbe, 0x60, 0x23, 0xc1, + 0xec, 0x5c, 0xf6, 0x93, 0x38, 0xf9, 0x89, 0x0, + 0xc5, 0x5f, 0x5b, 0xe2, 0x9d, 0x2b, 0xea, 0x6b, + 0x2e, 0xee, 0xb7, 0x4a, 0x4e, 0x8d, 0xd0, 0x35, + 0xe9, 0xc1, 0x5, 0x2b, 0x83, 0xb7, 0x72, 0x25, + 0xbb, 0xbe, 0xe8, 0x15, 0xf4, 0x74, 0x69, 0x69, + 0x67, 0x8c, 0x5c, 0x31, 0x79, 0x78, 0x2e, 0x43, + 0x83, 0xd1, 0xdd, 0x9, 0xc3, 0xa1, 0x0, 0x13, + 0x31, 0x4b, 0x86, 0xce, 0xee, 0xd7, 0xec, 0xb1, + 0x2c, 0x38, 0x46, 0x68, 0x62, 0xd9, 0x84, 0xdb, + 0x24, 0x62, 0x82, 0xc, 0x12, 0xb7, 0x4f, 0x86, + 0x54, 0x18, 0xc6, 0xd7, 0x94, 0x8b, 0xf2, 0x4c, + 0x17, 0x98, 0xaa, 0xe0, +}; + +const uint8_t expected_cipher_long_input_end[] = { + 0x05, 0x95, 0x58, 0x7b, 0xb4, 0x60, 0x15, + 0x32, 0x9f, 0x38, 0xcc, 0x98, 0x1b, 0xbe, 0x10, 0xa5, 0x06, 0x67, 0xae, 0x38, + 0xbd, 0x7d, 0xb5, 0xcd, 0x58, 0x32, 0xdd, 0x9e, + 0x6a, 0xde, 0xe3, 0x53, +}; + +void aes_ext_flash_ctr_test(uint32_t output_buf_caps) +{ + mbedtls_aes_context ctx; + uint8_t nonce[16]; + uint8_t key[16]; + uint8_t stream_block[16]; + size_t SZ = sizeof(long_input); + memset(nonce, 0x2F, 16); + memset(key, 0x1E, 16); + + uint8_t *chipertext = heap_caps_malloc(SZ, output_buf_caps); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + + size_t offset; + + // Encrypt with input buffer in external flash + offset = 0; + memset(nonce, 0x2F, 16); + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, long_input, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_long_input_end, chipertext + SZ - 32, 32); + + // Decrypt + offset = 0; + memset(nonce, 0x2F, 16); + // Decrypt with input buffer in external flash, the crypto DMA can't access this + mbedtls_aes_crypt_ctr(&ctx, SZ, &offset, nonce, stream_block, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(long_input, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(chipertext); + free(decryptedtext); +} + +/* Tests how crypto DMA handles data in external memory */ +TEST_CASE("mbedtls AES PSRAM tests", "[aes]") +{ + aes_ctr_alignment_test(MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + aes_ctr_alignment_test(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + aes_ctr_alignment_test(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + aes_psram_one_buf_ctr_test(); +} + +/* Tests how crypto DMA handles data from external flash */ +TEST_CASE("mbedtls AES external flash tests", "[aes]") +{ + aes_ext_flash_ctr_test(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + aes_ext_flash_ctr_test(MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); +} +#endif // CONFIG_SPIRAM_USE_MALLOC + + +static SemaphoreHandle_t done_sem; + +static void __attribute__((unused)) aes_ctr_stream_test_task(void *pv) +{ + aes_ctr_stream_test(); + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +#if CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK + +TEST_CASE("mbedtls AES stack in RTC RAM", "[mbedtls]") +{ + done_sem = xSemaphoreCreateBinary(); + static StaticTask_t rtc_task; + size_t STACK_SIZE = 3072; + uint8_t *rtc_stack = heap_caps_calloc(STACK_SIZE, 1, MALLOC_CAP_RTCRAM); + TEST_ASSERT(esp_ptr_in_rtc_dram_fast(rtc_stack)); + + TEST_ASSERT_NOT_NULL(xTaskCreateStatic(aes_ctr_stream_test_task, "aes_ctr_task", STACK_SIZE, NULL, + 3, rtc_stack, &rtc_task)); + TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)); + + /* Give task time to cleanup before freeing stack */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + free(rtc_stack); + + vSemaphoreDelete(done_sem); +} + +#endif //CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK + +#if CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY && CONFIG_SPIRAM_USE_MALLOC + +TEST_CASE("mbedtls AES stack in PSRAM", "[mbedtls]") +{ + done_sem = xSemaphoreCreateBinary(); + static StaticTask_t psram_task; + size_t STACK_SIZE = 3072; + uint8_t *psram_stack = heap_caps_calloc(STACK_SIZE, 1, MALLOC_CAP_SPIRAM); + + TEST_ASSERT(esp_ptr_external_ram(psram_stack)); + + TEST_ASSERT_NOT_NULL(xTaskCreateStatic(aes_ctr_stream_test_task, "aes_ctr_task", STACK_SIZE, NULL, + 3, psram_stack, &psram_task)); + TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)); + + /* Give task time to cleanup before freeing stack */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + free(psram_stack); + + vSemaphoreDelete(done_sem); +} + +#endif //CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY && CONFIG_SPIRAM_USE_MALLOC diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_gcm.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_gcm.c new file mode 100644 index 000000000..1946d275e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_gcm.c @@ -0,0 +1,885 @@ +/* mbedTLS GCM test + * + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include "mbedtls/aes.h" +#include "mbedtls/gcm.h" +#include "unity.h" +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "test_utils.h" +#include "ccomp_timer.h" +#include "sys/param.h" + +#if CONFIG_MBEDTLS_HARDWARE_AES + +/* + Python example code for generating test vectors + + import os, binascii + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + + def as_c_array(byte_arr): + hex_str = '' + for idx, byte in enumerate(byte_arr): + hex_str += "0x{:02x}, ".format(byte) + bytes_per_line = 8 + if idx % bytes_per_line == bytes_per_line - 1: + hex_str += '\n' + + return hex_str + + key = b'\x44' * 16 + iv = b'\xEE' * 16 + data = b'\xAA' * 3200 + aad = b'\x76' * 16 + + aesgcm = AESGCM(key) + + ct = aesgcm.encrypt(iv, data, aad) + + print(as_c_array(ct)) +*/ + +TEST_CASE("mbedtls GCM stream test", "[aes-gcm]") +{ + + const unsigned SZ = 100; + mbedtls_gcm_context ctx; + uint8_t nonce[16]; + uint8_t key[16]; + uint8_t tag[16]; + mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES; + + const uint8_t expected_cipher[] = { + 0x03, 0x92, 0x13, 0x49, 0x1f, 0x1f, 0x24, 0x41, + 0xe8, 0xeb, 0x89, 0x47, 0x50, 0x0a, 0xce, 0xa3, + 0xc7, 0x1c, 0x10, 0x70, 0xb0, 0x89, 0x82, 0x5e, + 0x0f, 0x4a, 0x23, 0xee, 0xd2, 0xfc, 0xff, 0x45, + 0x61, 0x4c, 0xd1, 0xfb, 0x6d, 0xe2, 0xbe, 0x67, + 0x6f, 0x94, 0x72, 0xa3, 0xe7, 0x04, 0x99, 0xb3, + 0x4a, 0x46, 0xf9, 0x2b, 0xaf, 0xac, 0xa9, 0x0e, + 0x43, 0x7e, 0x8b, 0xc4, 0xbf, 0x49, 0xa4, 0x83, + 0x9c, 0x31, 0x11, 0x1c, 0x09, 0xac, 0x90, 0xdf, + 0x00, 0x34, 0x08, 0xe5, 0x70, 0xa3, 0x7e, 0x4b, + 0x36, 0x48, 0x5a, 0x3f, 0x28, 0xc7, 0x1c, 0xd9, + 0x1b, 0x1b, 0x49, 0x96, 0xe9, 0x7c, 0xea, 0x54, + 0x7c, 0x71, 0x29, 0x0d + }; + const uint8_t expected_tag[] = { + 0x35, 0x1c, 0x21, 0xc6, 0xbc, 0x6b, 0x18, 0x52, + 0x90, 0xe1, 0xf2, 0x5b, 0xe1, 0xf6, 0x15, 0xee, + }; + + + memset(nonce, 0x89, 16); + memset(key, 0x56, 16); + + // allocate internal memory + uint8_t *ciphertext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(ciphertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + memset(plaintext, 0xAB, SZ); + /* Test that all the end results are the same + no matter how many bytes we encrypt each call + */ + for (int bytes_to_process = 16; bytes_to_process < SZ; bytes_to_process = bytes_to_process + 16) { + memset(nonce, 0x89, 16); + memset(ciphertext, 0x0, SZ); + memset(decryptedtext, 0x0, SZ); + memset(tag, 0x0, 16); + + mbedtls_gcm_init(&ctx); + mbedtls_gcm_setkey(&ctx, cipher, key, 128); + mbedtls_gcm_starts( &ctx, MBEDTLS_AES_ENCRYPT, nonce, sizeof(nonce) ); + mbedtls_gcm_update_ad( &ctx, NULL, 0 ); + + size_t olen; + // Encrypt + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + size_t length = (idx + bytes_to_process > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_gcm_update(&ctx, plaintext + idx, length, ciphertext + idx, length, &olen); + } + mbedtls_gcm_finish( &ctx, NULL, 0, &olen, tag, sizeof(tag) ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher, ciphertext, SZ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_tag, tag, sizeof(tag)); + + // Decrypt + memset(nonce, 0x89, 16); + mbedtls_gcm_free( &ctx ); + + mbedtls_gcm_init(&ctx); + mbedtls_gcm_setkey(&ctx, cipher, key, 128); + mbedtls_gcm_starts( &ctx, MBEDTLS_AES_DECRYPT, nonce, sizeof(nonce)); + mbedtls_gcm_update_ad( &ctx, NULL, 0 ); + + for (int idx = 0; idx < SZ; idx = idx + bytes_to_process) { + // Limit length of last call to avoid exceeding buffer size + + size_t length = (idx + bytes_to_process > SZ) ? (SZ - idx) : bytes_to_process; + mbedtls_gcm_update(&ctx, ciphertext + idx, length, decryptedtext + idx, length, &olen); + } + mbedtls_gcm_finish( &ctx, NULL, 0, &olen, tag, sizeof(tag) ); + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + mbedtls_gcm_free( &ctx ); + } + free(plaintext); + free(ciphertext); + free(decryptedtext); +} + +TEST_CASE("mbedtls AES GCM self-tests", "[aes-gcm]") +{ + TEST_ASSERT_FALSE_MESSAGE(mbedtls_gcm_self_test(1), "AES GCM self-test should pass."); +} + +typedef struct { + uint8_t *plaintext; + size_t plaintext_length; + uint32_t output_caps; + uint8_t *add_buf; + size_t add_length; + uint8_t *iv; + size_t iv_length; + uint8_t *key; + size_t key_bits; + size_t tag_len; +} aes_gcm_test_cfg_t; + +typedef struct { + const uint8_t *expected_tag; + const uint8_t *ciphertext_last_block; // Last block of the ciphertext +} aes_gcm_test_expected_res_t; + + +typedef enum { + AES_GCM_TEST_CRYPT_N_TAG, + AES_GCM_TEST_START_UPDATE_FINISH, +} aes_gcm_test_type_t; + +static void aes_gcm_test(aes_gcm_test_cfg_t *cfg, aes_gcm_test_expected_res_t *res, aes_gcm_test_type_t aes_gcm_type) +{ + mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES; + mbedtls_gcm_context ctx; + + uint8_t tag_buf_encrypt[16] = {}; + uint8_t tag_buf_decrypt[16] = {}; + uint8_t iv_buf[16] = {}; + + uint8_t *ciphertext = heap_caps_malloc(cfg->plaintext_length, cfg->output_caps); + uint8_t *output = heap_caps_malloc(cfg->plaintext_length, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + + if (cfg->plaintext_length != 0) { + TEST_ASSERT_NOT_NULL(ciphertext); + TEST_ASSERT_NOT_NULL(output); + } + + memset(ciphertext, 0, cfg->plaintext_length); + memset(output, 0, cfg->plaintext_length); + memcpy(iv_buf, cfg->iv, cfg->iv_length); + + mbedtls_gcm_init(&ctx); + mbedtls_gcm_setkey(&ctx, cipher, cfg->key, cfg->key_bits); + size_t olen; + /* Encrypt and tag */ + if (aes_gcm_type == AES_GCM_TEST_CRYPT_N_TAG) { + mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_AES_ENCRYPT, cfg->plaintext_length, iv_buf, cfg->iv_length, cfg->add_buf, cfg->add_length, cfg->plaintext, ciphertext, cfg->tag_len, tag_buf_encrypt); + } else if (aes_gcm_type == AES_GCM_TEST_START_UPDATE_FINISH) { + TEST_ASSERT(mbedtls_gcm_starts( &ctx, MBEDTLS_AES_ENCRYPT, iv_buf, cfg->iv_length) == 0 ); + TEST_ASSERT(mbedtls_gcm_update_ad( &ctx, cfg->add_buf, cfg->add_length) == 0 ); + TEST_ASSERT(mbedtls_gcm_update( &ctx, cfg->plaintext, cfg->plaintext_length, ciphertext, cfg->plaintext_length, &olen) == 0 ); + TEST_ASSERT(mbedtls_gcm_finish( &ctx, NULL, 0, &olen, tag_buf_encrypt, cfg->tag_len) == 0 ); + } + size_t offset = cfg->plaintext_length > 16 ? cfg->plaintext_length - 16 : 0; + /* Sanity check: make sure the last ciphertext block matches what we expect to see. */ + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->ciphertext_last_block, ciphertext + offset, MIN(16, cfg->plaintext_length)); + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->expected_tag, tag_buf_encrypt, cfg->tag_len); + + + /* Decrypt and authenticate */ + if (aes_gcm_type == AES_GCM_TEST_CRYPT_N_TAG) { + TEST_ASSERT(mbedtls_gcm_auth_decrypt(&ctx, cfg->plaintext_length, iv_buf, cfg->iv_length, cfg->add_buf, cfg->add_length, res->expected_tag, cfg->tag_len, ciphertext, output) == 0); + } else if (aes_gcm_type == AES_GCM_TEST_START_UPDATE_FINISH) { + TEST_ASSERT(mbedtls_gcm_starts( &ctx, MBEDTLS_AES_DECRYPT, iv_buf, cfg->iv_length) == 0 ); + TEST_ASSERT(mbedtls_gcm_update_ad( &ctx, cfg->add_buf, cfg->add_length) == 0 ); + TEST_ASSERT(mbedtls_gcm_update( &ctx, ciphertext, cfg->plaintext_length, output, cfg->plaintext_length, &olen) == 0 ); + TEST_ASSERT(mbedtls_gcm_finish( &ctx, NULL, 0, &olen, tag_buf_decrypt, cfg->tag_len) == 0 ); + + /* mbedtls_gcm_auth_decrypt already checks tag so only needed for AES_GCM_TEST_START_UPDATE_FINISH */ + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->expected_tag, tag_buf_decrypt, cfg->tag_len); + } + + TEST_ASSERT_EQUAL_HEX8_ARRAY(cfg->plaintext, output, cfg->plaintext_length); + mbedtls_gcm_free( &ctx ); + free(ciphertext); + free(output); +} + + + +TEST_CASE("mbedtls AES GCM", "[aes-gcm]") +{ + uint8_t iv[16]; + uint8_t key[16]; + uint8_t add[30]; + + memset(iv, 0xB1, sizeof(iv)); + memset(key, 0x27, sizeof(key)); + memset(add, 0x90, sizeof(add)); + + size_t length[] = {10, 16, 500, 5000, 12345}; + + const uint8_t expected_last_block[][16] = { + + { + 0x37, 0x99, 0x4b, 0x16, 0x5f, 0x8d, 0x27, 0xb1, + 0x60, 0x72 + }, + + { + 0x37, 0x99, 0x4b, 0x16, 0x5f, 0x8d, 0x27, 0xb1, + 0x60, 0x72, 0x9a, 0x81, 0x8d, 0x3c, 0x69, 0x66 + }, + + { + 0x9d, 0x7a, 0xac, 0x84, 0xe3, 0x70, 0x43, 0x0f, + 0xa7, 0x83, 0x43, 0xc9, 0x04, 0xf8, 0x7d, 0x48 + }, + + { + 0xee, 0xfd, 0xab, 0x2a, 0x09, 0x44, 0x41, 0x6a, + 0x91, 0xb0, 0x74, 0x24, 0xee, 0x35, 0xb1, 0x39 + }, + + { + 0x51, 0xf7, 0x1f, 0x67, 0x1a, 0x4a, 0x12, 0x37, + 0x60, 0x3b, 0x68, 0x01, 0x20, 0x4f, 0xf3, 0xd9 + }, + }; + + const uint8_t expected_tag[][16] = { + + { + 0x06, 0x4f, 0xb5, 0x91, 0x12, 0x24, 0xb4, 0x24, + 0x0b, 0xc2, 0x85, 0x59, 0x6a, 0x7c, 0x1f, 0xc9 + }, + + { + 0x45, 0xc2, 0xa8, 0xfe, 0xff, 0x49, 0x1f, 0x45, + 0x8e, 0x29, 0x74, 0x41, 0xed, 0x9b, 0x54, 0x28 + }, + + { + 0xe1, 0xf9, 0x40, 0xfa, 0x29, 0x6f, 0x30, 0xae, + 0xb6, 0x9b, 0x33, 0xdb, 0x8a, 0xf9, 0x70, 0xc4 + }, + + { + 0x22, 0xe1, 0x22, 0x34, 0x0c, 0x91, 0x0b, 0xcf, + 0xa3, 0x42, 0xe0, 0x48, 0xe6, 0xfe, 0x2e, 0x28 + }, + + { + 0xfb, 0xfe, 0x5a, 0xed, 0x26, 0x5c, 0x5e, 0x66, + 0x4e, 0xb2, 0x48, 0xce, 0xe9, 0x88, 0x1c, 0xe0 + }, + }; + + aes_gcm_test_cfg_t cfg = { + .output_caps = MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, + .iv = iv, + .iv_length = sizeof(iv), + .key = key, + .key_bits = 8 * sizeof(key), + .add_buf = add, + .add_length = sizeof(add), + .tag_len = 16 + }; + + aes_gcm_test_expected_res_t res = { + }; + + for (int i = 0; i < sizeof(length) / sizeof(length[0]); i++) { + printf("Test AES-GCM with plaintext length = %d\n", length[i]); + uint8_t *input = heap_caps_malloc(length[i], MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(input != NULL || length[i] == 0); + memset(input, 0x36, length[i]); + + cfg.plaintext = input; + cfg.plaintext_length = length[i]; + res.expected_tag = expected_tag[i]; + res.ciphertext_last_block = expected_last_block[i], + + aes_gcm_test(&cfg, &res, AES_GCM_TEST_CRYPT_N_TAG); + aes_gcm_test(&cfg, &res, AES_GCM_TEST_START_UPDATE_FINISH); + + free(input); + } +} + + +TEST_CASE("mbedtls AES GCM - Different add messages", "[aes-gcm]") +{ + const unsigned CALL_SZ = 160; + uint8_t iv[16]; + uint8_t key[16]; + uint8_t *input = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(input); + + memset(input, 0x67, CALL_SZ); + memset(iv, 0xA2, sizeof(iv)); + memset(key, 0x48, sizeof(key)); + + const uint8_t expected_last_block[] = { + 0xcd, 0xb9, 0xad, 0x6f, 0xc9, 0x35, 0x21, 0x0d, + 0xc9, 0x5d, 0xea, 0xd9, 0xf7, 0x1d, 0x43, 0xed + }; + + size_t add_len[] = {0, 10, 16, 500, 5000}; + + const uint8_t expected_tag[][16] = { + { + 0xe3, 0x91, 0xad, 0x40, 0x96, 0xb7, 0x8c, 0x53, + 0x4d, 0x15, 0x7d, 0x55, 0x15, 0xdf, 0x10, 0x69 + }, + + { + 0xc2, 0x38, 0x36, 0xe9, 0x12, 0x72, 0x5b, 0x31, + 0x0c, 0xde, 0xb5, 0xc9, 0x8c, 0xa3, 0xcb, 0xe7 + }, + + { + 0x57, 0x10, 0x22, 0x91, 0x65, 0xfa, 0x89, 0xba, + 0x0a, 0x3e, 0xc1, 0x7c, 0x93, 0x6e, 0x35, 0xac + }, + + { + 0x3c, 0x28, 0x03, 0xc2, 0x14, 0x40, 0xec, 0xb6, + 0x25, 0xfb, 0xdd, 0x55, 0xa0, 0xb2, 0x47, 0x7b + }, + + { + 0xfa, 0x66, 0x4a, 0x97, 0x2d, 0x02, 0x32, 0x5b, + 0x92, 0x94, 0xf1, 0x00, 0x1c, 0xfa, 0xe3, 0x07 + } + }; + + aes_gcm_test_cfg_t cfg = { + .plaintext = input, + .plaintext_length = CALL_SZ, + .output_caps = MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, + .iv = iv, + .iv_length = sizeof(iv), + .key = key, + .key_bits = 8 * sizeof(key), + .tag_len = 16 + }; + + aes_gcm_test_expected_res_t res = { + .ciphertext_last_block = expected_last_block, + }; + + for (int i = 0; i < sizeof(add_len) / sizeof(add_len[0]); i++) { + printf("Test AES-GCM with add length = %d\n", add_len[i]); + uint8_t *add = heap_caps_malloc(add_len[i], MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(add != NULL || add_len[i] == 0); + memset(add, 0x12, add_len[i]); + + cfg.add_buf = add; + cfg.add_length = add_len[i]; + res.expected_tag = expected_tag[i]; + + aes_gcm_test(&cfg, &res, AES_GCM_TEST_CRYPT_N_TAG); + aes_gcm_test(&cfg, &res, AES_GCM_TEST_START_UPDATE_FINISH); + + free(add); + } + free(input); +} + + + +TEST_CASE("mbedtls AES GCM performance, start, update, ret", "[aes-gcm]") +{ + const unsigned CALL_SZ = 16 * 3200; + mbedtls_gcm_context ctx; + float elapsed_usec; + unsigned char tag_buf[16]; + mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES; + uint8_t iv[16]; + uint8_t key[16]; + uint8_t aad[16]; + size_t olen; + memset(iv, 0xEE, 16); + memset(key, 0x44, 16); + memset(aad, 0x76, 16); + + // allocate internal memory + uint8_t *buf = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buf); + + mbedtls_gcm_init(&ctx); + mbedtls_gcm_setkey( &ctx, cipher, key, 128); + + ccomp_timer_start(); + + memset(buf, 0xAA, CALL_SZ); + + TEST_ASSERT(mbedtls_gcm_starts( &ctx, MBEDTLS_AES_ENCRYPT, iv, sizeof(iv) ) == 0 ); + TEST_ASSERT(mbedtls_gcm_update_ad( &ctx, aad, sizeof(aad)) == 0 ); + TEST_ASSERT(mbedtls_gcm_update( &ctx, buf, CALL_SZ, buf, CALL_SZ, &olen) == 0 ); + TEST_ASSERT(mbedtls_gcm_finish( &ctx, NULL, 0, &olen, tag_buf, 16 ) == 0 ); + + elapsed_usec = ccomp_timer_stop(); + + /* Sanity check: make sure the last ciphertext block matches + what we expect to see. + */ + const uint8_t expected_last_block[] = { + 0xd4, 0x25, 0x88, 0xd4, 0x32, 0x52, 0x3d, 0x6f, + 0xae, 0x49, 0x19, 0xb5, 0x95, 0x01, 0xde, 0x7d, + }; + + const uint8_t expected_tag[] = { + 0xf5, 0x10, 0x1f, 0x21, 0x5b, 0x07, 0x0d, 0x3f, + 0xac, 0xc9, 0xd0, 0x42, 0x45, 0xef, 0xc7, 0xfa, + }; + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_last_block, buf + CALL_SZ - 16, 16); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_tag, tag_buf, 16); + + free(buf); + + // bytes/usec = MB/sec + float mb_sec = CALL_SZ / elapsed_usec; + printf("GCM encryption rate %.3fMB/sec\n", mb_sec); + +#ifdef CONFIG_MBEDTLS_HARDWARE_GCM + // Don't put a hard limit on software AES performance + TEST_PERFORMANCE_GREATER_THAN(AES_GCM_UPDATE_THROUGHPUT_MBSEC, "%.3fMB/sec", mb_sec); +#endif +} + + +TEST_CASE("mbedtls AES GCM performance, crypt-and-tag", "[aes-gcm]") +{ + const unsigned CALL_SZ = 16 * 3200; + mbedtls_gcm_context ctx; + float elapsed_usec; + unsigned char tag_buf[16] = {}; + mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES; + uint8_t iv[16]; + uint8_t key[16]; + uint8_t aad[16]; + + memset(iv, 0xEE, 16); + memset(key, 0x44, 16); + memset(aad, 0x76, 16); + + // allocate internal memory + uint8_t *buf = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buf); + + mbedtls_gcm_init(&ctx); + mbedtls_gcm_setkey( &ctx, cipher, key, 128); + + memset(buf, 0xAA, CALL_SZ); + + ccomp_timer_start(); + mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_AES_ENCRYPT, CALL_SZ, iv, sizeof(iv), aad, sizeof(aad), buf, buf, 16, tag_buf); + + elapsed_usec = ccomp_timer_stop(); + + /* Sanity check: make sure the last ciphertext block matches + what we expect to see. + */ + + const uint8_t expected_last_block[] = { + 0xd4, 0x25, 0x88, 0xd4, 0x32, 0x52, 0x3d, 0x6f, + 0xae, 0x49, 0x19, 0xb5, 0x95, 0x01, 0xde, 0x7d, + }; + + const uint8_t expected_tag[] = { + 0xf5, 0x10, 0x1f, 0x21, 0x5b, 0x07, 0x0d, 0x3f, + 0xac, 0xc9, 0xd0, 0x42, 0x45, 0xef, 0xc7, 0xfa, + }; + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_last_block, buf + CALL_SZ - 16, 16); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_tag, tag_buf, 16); + + free(buf); + + // bytes/usec = MB/sec + float mb_sec = CALL_SZ / elapsed_usec; + printf("GCM encryption rate %.3fMB/sec\n", mb_sec); + +#ifdef CONFIG_MBEDTLS_HARDWARE_GCM + // Don't put a hard limit on software AES performance + TEST_PERFORMANCE_GREATER_THAN(AES_GCM_CRYPT_TAG_THROUGHPUT_MBSEC, "%.3fMB/sec", mb_sec); +#endif +} + +TEST_CASE("mbedtls AES GCM - Combine different IV/Key/Plaintext/AAD lengths", "[aes-gcm]") +{ + #define IV_BYTES_VALUE 0xA2 + #define KEY_BYTES_VALUE 0x48 + #define INPUT_BYTES_VALUE 0x36 + #define ADD_BYTES_VALUE 0x12 + + uint8_t iv[16]; + uint8_t key[32]; + + memset(iv, IV_BYTES_VALUE, sizeof(iv)); + memset(key, KEY_BYTES_VALUE, sizeof(key)); + + /* Key length is: 16 bytes, 32 bytes */ + size_t key_length[] = {16, 32}; + + /* IV length is: 12 bytes (standard), 16 bytes */ + size_t iv_length[] = {12, 16}; + + /* Plaintext length is: a multiple of 16 bytes, a non-multiple of 16 bytes */ + size_t length[] = {160, 321}; + + /* Add len is: 0, a multiple of 16 bytes, a non-multiple of 16 bytes */ + size_t add_len[] = {0, 160, 321}; + + /*indexes: Key - IV - Plaintext */ + const uint8_t expected_last_block[2][2][2][16] = { + { + /* 16 byte key */ + + { + { + 0xa2, 0x1e, 0x23, 0x3c, 0xfc, 0x7c, 0xec, 0x9a, + 0x91, 0xe5, 0xdb, 0x3a, 0xe5, 0x0c, 0x3f, 0xc2, + }, + + { + 0xa8, 0xeb, 0x40, 0x9b, 0x7b, 0x87, 0x07, + 0x68, 0x17, 0x5c, 0xc0, 0xb7, 0xb4, 0xb3, 0x81, + 0xbe, + } + }, + { + { + 0x9c, 0xe8, 0xfc, 0x3e, 0x98, 0x64, 0x70, 0x5c, + 0x98, 0x0c, 0xbb, 0x88, 0xa6, 0x4c, 0x12, 0xbc + }, + + { + 0x8b, 0x66, 0xf5, 0xbc, 0x56, 0x59, 0xae, + 0xf0, 0x9e, 0x5c, 0xdb, 0x6d, 0xfc, 0x1f, 0x2e, + 0x00 + } + }, + }, + { + /* 32 byte key */ + { + { + 0xde, 0xc2, 0xd3, 0xeb, 0x5e, 0x03, 0x53, 0x4b, + 0x04, 0x0d, 0x63, 0xf1, 0xd8, 0x5b, 0x1f, 0x85, + }, + + { + 0xb5, 0x53, 0x8e, 0xd3, 0xab, 0x10, 0xf1, + 0x77, 0x41, 0x92, 0xea, 0xdd, 0xdd, 0x9e, 0x5d, + 0x40, + } + }, + { + { + 0x3b, 0xc7, 0xf0, 0x3f, 0xba, 0x97, 0xbd, 0xa0, + 0xa5, 0x48, 0xf3, 0x7a, 0xde, 0x23, 0x19, 0x7a, + }, + + { + 0x57, 0xc7, 0x4d, 0xe3, 0x79, 0x5e, 0xbd, + 0x0d, 0xd7, 0x6a, 0xef, 0x1f, 0x54, 0x29, 0xa6, + 0xd7, + } + }, + }, + }; + + /*indexes: Key - IV - Plaintext - Add len*/ + const uint8_t expected_tag[2][2][2][3][16] = { + { + { + { + // Plaintext 160 bytes + { + 0x67, 0x92, 0xb1, 0x7f, 0x44, 0x1f, 0x95, 0xfb, + 0x33, 0x76, 0x66, 0xb7, 0x4f, 0x3e, 0xec, 0x4d, + }, + + { + 0xb1, 0x99, 0xed, 0x1b, 0x4e, 0x12, 0x87, 0x5e, + 0xf4, 0xe3, 0x81, 0xd8, 0x96, 0x07, 0xda, 0xff, + }, + + { + 0x73, 0x35, 0x0c, 0xf5, 0x70, 0x1e, 0xc0, 0x99, + 0x34, 0xba, 0x1a, 0x50, 0x23, 0xac, 0x21, 0x33, + }, + }, + { + // Plaintext 321 bytes + { + 0x2d, 0xf6, 0xd0, 0x7a, 0x75, 0x4d, 0x9d, + 0xb5, 0x9d, 0x43, 0xbf, 0x57, 0x10, 0xa3, 0xff, + 0x3d + }, + + { + 0x06, 0x91, 0xe4, 0x38, 0x3a, 0xe1, 0x6e, + 0x2d, 0x83, 0x68, 0x2e, 0xb0, 0x26, 0x2f, 0xe4, + 0x78 + }, + + { + 0x1b, 0x58, 0x2f, 0x9b, 0xe9, 0xe0, 0xe0, + 0x43, 0x83, 0x08, 0xec, 0x58, 0x3a, 0x78, 0xe9, + 0x69, + } + } + }, + { + { + // Plaintext 160 bytes + { + 0x77, 0xe5, 0x2e, 0x2d, 0x94, 0xb8, 0x03, 0x61, + 0x7a, 0xd5, 0x0c, 0x3c, 0x9c, 0x40, 0x92, 0x9b + }, + + { + 0xa1, 0xee, 0x72, 0x49, 0x9e, 0xb5, 0x11, 0xc4, + 0xbd, 0x40, 0xeb, 0x53, 0x45, 0x79, 0xa4, 0x29 + }, + + { + 0x63, 0x42, 0x93, 0xa7, 0xa0, 0xb9, 0x56, 0x03, + 0x7d, 0x19, 0x70, 0xdb, 0xf0, 0xd2, 0x5f, 0xe5 + }, + }, + { + // Plaintext 321 bytes + { + 0x50, 0xa3, 0x79, 0xfc, 0x17, 0xb8, 0xf4, + 0xf6, 0x14, 0xaa, 0x4a, 0xe7, 0xd4, 0xa0, 0xea, + 0xee + }, + + { + 0x7b, 0xc4, 0x4d, 0xbe, 0x58, 0x14, 0x07, + 0x6e, 0x0a, 0x81, 0xdb, 0x00, 0xe2, 0x2c, 0xf1, + 0xab + }, + + { + 0x66, 0x0d, 0x86, 0x1d, 0x8b, 0x15, 0x89, + 0x00, 0x0a, 0xe1, 0x19, 0xe8, 0xfe, 0x7b, 0xfc, + 0xba + } + } + }, + }, + { + { + { + // Plaintext 160 bytes + { + 0x04, 0x04, 0x15, 0xb1, 0xd3, 0x98, 0x15, 0x45, + 0xa2, 0x44, 0xba, 0x4a, 0xde, 0xc2, 0x8d, 0xd6, + }, + + { + 0x94, 0x3e, 0xc3, 0x5d, 0xdc, 0x42, 0xf6, 0x4c, + 0x80, 0x15, 0xe4, 0xb9, 0x0b, 0xc9, 0x87, 0x01, + }, + + { + 0x93, 0x6e, 0x26, 0x5b, 0x7e, 0x17, 0xc8, 0x73, + 0x9b, 0x71, 0x31, 0x7a, 0x8b, 0x0e, 0x19, 0x89, + } + }, + { + // Plaintext 321 bytes + { + 0x99, 0x5e, 0x77, 0x28, 0x8b, 0xa8, 0x9b, + 0xb3, 0x35, 0xc3, 0x99, 0x90, 0xd4, 0x5d, 0x63, + 0xa7, + }, + + { + 0xbc, 0xc2, 0x9f, 0xe6, 0x38, 0xef, 0xf5, + 0x11, 0x76, 0x09, 0x17, 0x3a, 0xd4, 0x91, 0xee, + 0xfe, + }, + + { + 0x9f, 0xa6, 0x23, 0x5a, 0x4d, 0x78, 0xae, + 0xce, 0x10, 0x35, 0xc1, 0x0c, 0x6e, 0xc2, 0x4e, + 0xe8, + } + } + }, + { + { + // Plaintext 160 bytes + { + 0xfb, 0x74, 0x7e, 0x21, 0xf2, 0xe7, 0xe3, 0xf5, + 0xfa, 0xc8, 0x23, 0xab, 0x54, 0x9a, 0xb9, 0xcf, + }, + + { + 0x6b, 0x4e, 0xa8, 0xcd, 0xfd, 0x3d, 0x00, 0xfc, + 0xd8, 0x99, 0x7d, 0x58, 0x81, 0x91, 0xb3, 0x18, + }, + + { + 0x6c, 0x1e, 0x4d, 0xcb, 0x5f, 0x68, 0x3e, 0xc3, + 0xc3, 0xfd, 0xa8, 0x9b, 0x01, 0x56, 0x2d, 0x90, + }, + }, + { + // Plaintext 321 bytes + { + 0xcd, 0x49, 0x75, 0x4c, 0x2a, 0x62, 0x65, + 0x6f, 0xfe, 0x14, 0xc2, 0x5d, 0x41, 0x07, 0x24, + 0x55 + }, + + { + 0xe8, 0xd5, 0x9d, 0x82, 0x99, 0x25, 0x0b, + 0xcd, 0xbd, 0xde, 0x4c, 0xf7, 0x41, 0xcb, 0xa9, + 0x0c, + }, + + { + 0xcb, 0xb1, 0x21, 0x3e, 0xec, 0xb2, 0x50, + 0x12, 0xdb, 0xe2, 0x9a, 0xc1, 0xfb, 0x98, 0x09, + 0x1a, + } + } + }, + }, + }; + + aes_gcm_test_cfg_t cfg = { + .output_caps = MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, + .tag_len = 16 + }; + + + for (int i_key = 0; i_key < sizeof(key_length) / sizeof(key_length[0]); i_key++) { + printf("Test AES-GCM with key length = %d\n", key_length[i_key]); + + cfg.key = key; + cfg.key_bits = 8 * key_length[i_key]; + + for (int i_iv = 0; i_iv < sizeof(iv_length) / sizeof(iv_length[0]); i_iv++) { + printf("Test AES-GCM with IV length = %d\n", iv_length[i_iv]); + + cfg.iv = iv; + cfg.iv_length = iv_length[i_iv]; + + for (int i_len = 0; i_len < sizeof(length) / sizeof(length[0]); i_len++) { + printf("Test AES-GCM with plaintext length = %d\n", length[i_len]); + uint8_t *input = heap_caps_malloc(length[i_len], MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(input != NULL || length[i_len] == 0); + memset(input, INPUT_BYTES_VALUE, length[i_len]); + cfg.plaintext = input; + cfg.plaintext_length = length[i_len]; + + aes_gcm_test_expected_res_t res = {0}; + + for (int i_add = 0; i_add < sizeof(add_len) / sizeof(add_len[0]); i_add++) { + + printf("Test AES-GCM with add length = %d\n", add_len[i_add]); + uint8_t *add = heap_caps_malloc(add_len[i_add], MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(add != NULL || add_len[i_add] == 0); + memset(add, ADD_BYTES_VALUE, add_len[i_add]); + + cfg.add_buf = add; + cfg.add_length = add_len[i_add]; + + res.expected_tag = expected_tag[i_key][i_iv][i_len][i_add]; + res.ciphertext_last_block = expected_last_block[i_key][i_iv][i_len], + + aes_gcm_test(&cfg, &res, AES_GCM_TEST_CRYPT_N_TAG); + + free(add); + } + free(input); + } + } + } +} + +TEST_CASE("mbedtls AES GCM - Different Authentication Tag lengths", "[aes-gcm]") +{ + const unsigned CALL_SZ = 160; + uint8_t iv[16]; + uint8_t key[16]; + uint8_t aad[16]; + uint8_t *input = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(input); + + memset(input, 0x67, CALL_SZ); + memset(iv, 0xA2, sizeof(iv)); + memset(key, 0x48, sizeof(key)); + memset(aad, 0x12, sizeof(aad)); + + size_t tag_len[] = {4, 8, 11, 16}; + + const uint8_t expected_last_block[] = { + 0xcd, 0xb9, 0xad, 0x6f, 0xc9, 0x35, 0x21, 0x0d, + 0xc9, 0x5d, 0xea, 0xd9, 0xf7, 0x1d, 0x43, 0xed + }; + + const uint8_t expected_tag[16] = { + 0x57, 0x10, 0x22, 0x91, 0x65, 0xfa, 0x89, 0xba, + 0x0a, 0x3e, 0xc1, 0x7c, 0x93, 0x6e, 0x35, 0xac + }; + + aes_gcm_test_cfg_t cfg = { + .plaintext = input, + .plaintext_length = CALL_SZ, + .output_caps = MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, + .add_buf = aad, + .add_length = sizeof(aad), + .iv = iv, + .iv_length = sizeof(iv), + .key = key, + .key_bits = 8 * sizeof(key), + }; + + aes_gcm_test_expected_res_t res = { + .expected_tag = expected_tag, + .ciphertext_last_block = expected_last_block, + }; + + for (int i = 0; i < sizeof(tag_len) / sizeof(tag_len[0]); i++) { + printf("Test AES-GCM with tag length = %d\n", tag_len[i]); + cfg.tag_len = tag_len[i]; + aes_gcm_test(&cfg, &res, AES_GCM_TEST_CRYPT_N_TAG); + aes_gcm_test(&cfg, &res, AES_GCM_TEST_START_UPDATE_FINISH); + } + free(input); +} + +#endif //CONFIG_MBEDTLS_HARDWARE_AES diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_perf.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_perf.c new file mode 100644 index 000000000..6ba8f15c1 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_perf.c @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* mbedTLS AES performance test +*/ +#include +#include +#include +#include +#include "mbedtls/aes.h" +#include "mbedtls/gcm.h" +#include "unity.h" +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "test_utils.h" +#include "ccomp_timer.h" + +TEST_CASE("mbedtls AES performance", "[aes][timeout=60]") +{ + const unsigned CALLS = 256; + const unsigned CALL_SZ = 32 * 1024; + mbedtls_aes_context ctx; + float elapsed_usec; + uint8_t iv[16]; + uint8_t key[16]; + + memset(iv, 0xEE, 16); + memset(key, 0x44, 16); + + // allocate internal memory + uint8_t *buf = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buf); + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key, 128); + + ccomp_timer_start(); + for (int c = 0; c < CALLS; c++) { + memset(buf, 0xAA, CALL_SZ); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, CALL_SZ, iv, buf, buf); + } + elapsed_usec = ccomp_timer_stop(); + + /* Sanity check: make sure the last ciphertext block matches + what we expect to see. + + Last block produced via this Python: + import os, binascii + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + key = b'\x44' * 16 + iv = b'\xee' * 16 + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ct = encryptor.update(b'\xaa' * 256 * 32 * 1024) + encryptor.finalize() + print(binascii.hexlify(ct[-16:])) + */ + const uint8_t expected_last_block[] = { + 0x50, 0x81, 0xe0, 0xe1, 0x15, 0x2f, 0x14, 0xe9, + 0x97, 0xa0, 0xc6, 0xe6, 0x36, 0xf3, 0x5c, 0x25, + }; + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_last_block, buf + CALL_SZ - 16, 16); + + mbedtls_aes_free(&ctx); + free(buf); + + // bytes/usec = MB/sec + float mb_sec = (CALL_SZ * CALLS) / elapsed_usec; + printf("Encryption rate %.3fMB/sec\n", mb_sec); +#ifdef CONFIG_MBEDTLS_HARDWARE_AES + // Don't put a hard limit on software AES performance + TEST_PERFORMANCE_CCOMP_GREATER_THAN(AES_CBC_THROUGHPUT_MBSEC, "%.3fMB/sec", mb_sec); +#endif +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_parallel.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_parallel.c new file mode 100644 index 000000000..ddbb5baff --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_parallel.c @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "mbedtls/aes.h" +#include "mbedtls/sha256.h" +#include "unity.h" +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "test_utils.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + + +static SemaphoreHandle_t done_sem; + +static const unsigned char *one_hundred_bs = (unsigned char *) + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + +static const uint8_t sha256_thousand_bs[32] = { + 0xf6, 0xf1, 0x18, 0xe1, 0x20, 0xe5, 0x2b, 0xe0, 0xbd, 0x0c, 0xfd, 0xf2, 0x79, 0x4c, 0xd1, 0x2c, 0x07, 0x68, 0x6c, 0xc8, 0x71, 0x23, 0x5a, 0xc2, 0xf1, 0x14, 0x59, 0x37, 0x8e, 0x6d, 0x23, 0x5b +}; + +static void tskRunSHA256Test(void *pvParameters) +{ + mbedtls_sha256_context sha256_ctx; + unsigned char sha256[32]; + + for (int i = 0; i < 1000; i++) { + + mbedtls_sha256_init(&sha256_ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + for (int j = 0; j < 10; j++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, (unsigned char *)one_hundred_bs, 100)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + mbedtls_sha256_free(&sha256_ctx); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_bs, sha256, 32, "SHA256 calculation"); + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + + +static void tskRunAES256Test(void *pvParameters) +{ + static const uint8_t iv[] = { + 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + static const uint8_t key_256[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + }; + + for (int i = 0; i <1000; i++) + { + const unsigned SZ = 1600; + mbedtls_aes_context ctx; + uint8_t nonce[16]; + + const uint8_t expected_cipher_end[] = { + 0x3e, 0x68, 0x8a, 0x02, 0xe6, 0xf2, 0x6a, 0x9e, + 0x9b, 0xb2, 0xc0, 0xc4, 0x63, 0x63, 0xd9, 0x25, + 0x51, 0xdc, 0xc2, 0x71, 0x96, 0xb3, 0xe5, 0xcd, + 0xbd, 0x0e, 0xf2, 0xef, 0xa9, 0xab, 0xab, 0x2d, + }; + + memcpy(nonce, iv, 16); + + // allocate internal memory + uint8_t *chipertext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *plaintext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + uint8_t *decryptedtext = heap_caps_malloc(SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + + TEST_ASSERT_NOT_NULL(chipertext); + TEST_ASSERT_NOT_NULL(plaintext); + TEST_ASSERT_NOT_NULL(decryptedtext); + + mbedtls_aes_init(&ctx); + mbedtls_aes_setkey_enc(&ctx, key_256, 256); + + memset(plaintext, 0x3A, SZ); + memset(decryptedtext, 0x0, SZ); + + // Encrypt + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, SZ, nonce, plaintext, chipertext); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_cipher_end, chipertext + SZ - 32, 32); + + // Decrypt + memcpy(nonce, iv, 16); + mbedtls_aes_setkey_dec(&ctx, key_256, 256); + mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, SZ, nonce, chipertext, decryptedtext); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(plaintext, decryptedtext, SZ); + + mbedtls_aes_free(&ctx); + free(plaintext); + free(chipertext); + free(decryptedtext); + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); + +} + +#include "esp_crypto_shared_gdma.h" + +#define TASK_STACK_SIZE (20*1024) + +TEST_CASE("mbedtls AES/SHA multithreading", "[mbedtls]") +{ + done_sem = xSemaphoreCreateCounting(2, 0); + + xTaskCreate(tskRunSHA256Test, "SHA256Task", TASK_STACK_SIZE, NULL, 3, NULL); + xTaskCreate(tskRunAES256Test, "AES256Task", TASK_STACK_SIZE, NULL, 3, NULL); + + for (int i = 0; i < 2; i++) { + if (!xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("done_sem not released by test task"); + } + } + + vSemaphoreDelete(done_sem); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_rsa.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_rsa.c new file mode 100644 index 000000000..497180fba --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_aes_sha_rsa.c @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include + +#if CONFIG_IDF_TARGET_ESP32 + +#include "esp_types.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_timer.h" +#include "unity.h" +#include "test_utils.h" +#include "esp32/rom/sha.h" +#include "soc/uart_periph.h" +#include "soc/dport_reg.h" +#include "soc/rtc.h" +#include "esp_log.h" +#include "sha/sha_parallel_engine.h" +#include "aes/esp_aes.h" +#include "mbedtls/rsa.h" +#include "mbedtls/sha256.h" + +static const char *TAG = "test"; +static volatile bool exit_flag = false; +#define TASK_STACK_SIZE (8*1024) + +static void aes_task(void *pvParameters) +{ + SemaphoreHandle_t *sema = (SemaphoreHandle_t *) pvParameters; + ESP_LOGI(TAG, "aes_task is started"); + esp_aes_context ctx = { + .key_bytes = 16, + .key = {101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116} + }; + const unsigned char input[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + unsigned char output[16]; + unsigned char output2[16]; + while (exit_flag == false) { + memset(output, 0, sizeof(output)); + memset(output, 0, sizeof(output2)); + esp_internal_aes_encrypt(&ctx, input, output); + esp_internal_aes_decrypt(&ctx, output, output2); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(input, output2, sizeof(input), "AES must match"); + } + xSemaphoreGive(*sema); + vTaskDelete(NULL); +} + +static void sha_task(void *pvParameters) +{ + SemaphoreHandle_t *sema = (SemaphoreHandle_t *) pvParameters; + ESP_LOGI(TAG, "sha_task is started"); + const char *input = "Space!#$%&()*+,-.0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz~DEL0123456789"; + unsigned char output[64]; + unsigned char output_origin[64]; + esp_sha(SHA2_512, (const unsigned char *)input, sizeof(input), output); + memcpy(output_origin, output, sizeof(output)); + while (exit_flag == false) { + memset(output, 0, sizeof(output)); + esp_sha(SHA2_512, (const unsigned char *)input, sizeof(input), output); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(output, output_origin, sizeof(output), "SHA256 must match"); + } + xSemaphoreGive(*sema); + vTaskDelete(NULL); +} + +static void mbedtls_sha256_task(void *pvParameters) +{ + SemaphoreHandle_t *sema = (SemaphoreHandle_t *) pvParameters; + ESP_LOGI(TAG, "mbedtls_sha256_task is started"); + const char *input = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz~DEL0123456789Space!#$%&()*+,-.0123456789:;<=>?"; + mbedtls_sha256_context sha256_ctx; + unsigned char output[32]; + unsigned char output_origin[32]; + + mbedtls_sha256_init(&sha256_ctx); + memset(output, 0, sizeof(output)); + mbedtls_sha256_starts(&sha256_ctx, false); + for (int i = 0; i < 3; ++i) { + mbedtls_sha256_update(&sha256_ctx, (unsigned char *)input, 100); + } + mbedtls_sha256_finish(&sha256_ctx, output); + memcpy(output_origin, output, sizeof(output)); + + while (exit_flag == false) { + mbedtls_sha256_init(&sha256_ctx); + memset(output, 0, sizeof(output)); + mbedtls_sha256_starts(&sha256_ctx, false); + for (int i = 0; i < 3; ++i) { + mbedtls_sha256_update(&sha256_ctx, (unsigned char *)input, 100); + } + mbedtls_sha256_finish(&sha256_ctx, output); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(output, output_origin, sizeof(output), "MBEDTLS SHA256 must match"); + } + xSemaphoreGive(*sema); + vTaskDelete(NULL); +} + +TEST_CASE("Test shared using AES SHA512 SHA256", "[hw_crypto]") +{ +#ifndef CONFIG_FREERTOS_UNICORE + const int max_tasks = 6; +#else + const int max_tasks = 3; +#endif + SemaphoreHandle_t exit_sema[max_tasks]; + + for (int i = 0; i < max_tasks; ++i) { + exit_sema[i] = xSemaphoreCreateBinary(); + } + exit_flag = false; +#ifndef CONFIG_FREERTOS_UNICORE + xTaskCreatePinnedToCore(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); + xTaskCreatePinnedToCore(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); + xTaskCreatePinnedToCore(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[2], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); + xTaskCreatePinnedToCore(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[3], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); + xTaskCreatePinnedToCore(&mbedtls_sha256_task, "mbedtls_sha256_task", TASK_STACK_SIZE, &exit_sema[4], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); + xTaskCreatePinnedToCore(&mbedtls_sha256_task, "mbedtls_sha256_task", TASK_STACK_SIZE, &exit_sema[5], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); +#else + xTaskCreate(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&mbedtls_sha256_task, "mbedtls_sha256_task", TASK_STACK_SIZE, &exit_sema[2], UNITY_FREERTOS_PRIORITY - 1, NULL); +#endif + + ESP_LOGI(TAG, "Waiting for 10s ..."); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + // set exit flag to let thread exit + exit_flag = true; + for (int i = 0; i < max_tasks; ++i) { + if (!xSemaphoreTake(exit_sema[i], 2000/portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("exit_sema not released by test task"); + } + vSemaphoreDelete(exit_sema[i]); + } +} + +static void rsa_task(void *pvParameters) +{ + SemaphoreHandle_t *sema = (SemaphoreHandle_t *) pvParameters; + ESP_LOGI(TAG, "rsa_task is started"); + while (exit_flag == false) { + mbedtls_rsa_self_test(0); + } + xSemaphoreGive(*sema); + vTaskDelete(NULL); +} + +TEST_CASE("Test shared using AES RSA", "[hw_crypto]") +{ +#ifndef CONFIG_FREERTOS_UNICORE + const int max_tasks = 2; +#else + const int max_tasks = 2; +#endif + SemaphoreHandle_t exit_sema[max_tasks]; + + for (int i = 0; i < max_tasks; ++i) { + exit_sema[i] = xSemaphoreCreateBinary(); + } + exit_flag = false; +#ifndef CONFIG_FREERTOS_UNICORE + xTaskCreatePinnedToCore(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); + xTaskCreatePinnedToCore(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); +#else + xTaskCreate(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL); +#endif + + ESP_LOGI(TAG, "Waiting for 10s ..."); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + // set exit flag to let thread exit + exit_flag = true; + for (int i = 0; i < max_tasks; ++i) { + if (!xSemaphoreTake(exit_sema[i], 2000/portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("exit_sema not released by test task"); + } + vSemaphoreDelete(exit_sema[i]); + } +} + +TEST_CASE("Test shared using SHA512 RSA", "[hw_crypto]") +{ +#ifndef CONFIG_FREERTOS_UNICORE + const int max_tasks = 2; +#else + const int max_tasks = 2; +#endif + SemaphoreHandle_t exit_sema[max_tasks]; + + for (int i = 0; i < max_tasks; ++i) { + exit_sema[i] = xSemaphoreCreateBinary(); + } + exit_flag = false; +#ifndef CONFIG_FREERTOS_UNICORE + xTaskCreatePinnedToCore(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 2, NULL, 1); + xTaskCreatePinnedToCore(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); +#else + xTaskCreate(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL); +#endif + + ESP_LOGI(TAG, "Waiting for 10s ..."); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + // set exit flag to let thread exit + exit_flag = true; + for (int i = 0; i < max_tasks; ++i) { + if (!xSemaphoreTake(exit_sema[i], 2000/portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("exit_sema not released by test task"); + } + vSemaphoreDelete(exit_sema[i]); + } +} + +TEST_CASE("Test shared using SHA256 RSA", "[hw_crypto]") +{ +#ifndef CONFIG_FREERTOS_UNICORE + const int max_tasks = 2; +#else + const int max_tasks = 2; +#endif + SemaphoreHandle_t exit_sema[max_tasks]; + + for (int i = 0; i < max_tasks; ++i) { + exit_sema[i] = xSemaphoreCreateBinary(); + } + exit_flag = false; +#ifndef CONFIG_FREERTOS_UNICORE + xTaskCreatePinnedToCore(&mbedtls_sha256_task, "mbedtls_sha256_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); + xTaskCreatePinnedToCore(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); +#else + xTaskCreate(&mbedtls_sha256_task, "mbedtls_sha256_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL); +#endif + + ESP_LOGI(TAG, "Waiting for 10s ..."); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + // set exit flag to let thread exit + exit_flag = true; + for (int i = 0; i < max_tasks; ++i) { + if (!xSemaphoreTake(exit_sema[i], 2000/portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("exit_sema not released by test task"); + } + vSemaphoreDelete(exit_sema[i]); + } +} + +TEST_CASE("Test shared using AES SHA RSA", "[hw_crypto]") +{ +#ifndef CONFIG_FREERTOS_UNICORE + const int max_tasks = 3; +#else + const int max_tasks = 3; +#endif + SemaphoreHandle_t exit_sema[max_tasks]; + + for (int i = 0; i < max_tasks; ++i) { + exit_sema[i] = xSemaphoreCreateBinary(); + } + exit_flag = false; +#ifndef CONFIG_FREERTOS_UNICORE + xTaskCreatePinnedToCore(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); + xTaskCreatePinnedToCore(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL, 0); + xTaskCreatePinnedToCore(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[2], UNITY_FREERTOS_PRIORITY - 1, NULL, 1); +#else + xTaskCreate(&aes_task, "aes_task", TASK_STACK_SIZE, &exit_sema[0], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&sha_task, "sha_task", TASK_STACK_SIZE, &exit_sema[1], UNITY_FREERTOS_PRIORITY - 1, NULL); + xTaskCreate(&rsa_task, "rsa_task", TASK_STACK_SIZE, &exit_sema[2], UNITY_FREERTOS_PRIORITY - 1, NULL); +#endif + + ESP_LOGI(TAG, "Waiting for 10s ..."); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + // set exit flag to let thread exit + exit_flag = true; + for (int i = 0; i < max_tasks; ++i) { + if (!xSemaphoreTake(exit_sema[i], 2000/portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("exit_sema not released by test task"); + } + vSemaphoreDelete(exit_sema[i]); + } +} + +#endif // CONFIG_IDF_TARGET_ESP32 diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.c new file mode 100644 index 000000000..34cb95687 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.c @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Implementation of utility functions to verify + unit tests aren't performing SMP-unsafe DPORT reads. +*/ + +#include "unity.h" +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/uart_periph.h" +#include "test_apb_dport_access.h" +#include "test_utils.h" + +#ifndef CONFIG_FREERTOS_UNICORE + +static void apb_access_loop_task(void *ignore); + +static volatile bool apb_access_corrupt; +static TaskHandle_t apb_task_handle; + +void start_apb_access_loop(void) +{ + apb_access_corrupt = false; + xTaskCreatePinnedToCore(apb_access_loop_task, "accessAPB", 2048, NULL, + UNITY_FREERTOS_PRIORITY - 1, + &apb_task_handle, !UNITY_FREERTOS_CPU); +} + +void verify_apb_access_loop(void) +{ + vTaskDelete(apb_task_handle); + apb_task_handle = NULL; + TEST_ASSERT_FALSE(apb_access_corrupt); + printf("Verified no APB corruption from operations\n"); +} + +static void apb_access_loop_task(void *ignore) +{ + uint32_t initial = REG_READ(UART_DATE_REG(0)); + while(1) { + if (REG_READ(UART_DATE_REG(0)) != initial) { + apb_access_corrupt = true; + } + } +} + +#else /*CONFIG_FREERTOS_UNICORE */ + +void start_apb_access_loop(void) +{ +} + +void verify_apb_access_loop(void) +{ +} + +#endif diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.h b/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.h new file mode 100644 index 000000000..3231d745e --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_apb_dport_access.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Utility functions to test that APB access is still safe + while the other CPU performs some set of DPORT accesses + + (see ECO 3.10 and the standalone esp32 test_dport.c for more). +*/ + +/* start_apb_access_loop() starts a task reading from APB in a loop on the non-Unity-test CPU. + + Call this before doing something which involes DPORT reads. + + Does nothing in unicore mode. +*/ +void start_apb_access_loop(void); + +/* verify_apb_access_loop() kills the task started by start_apb_access_loop() + and verifies that none of the APB reads were corrupted by unsafe DPORT reads. +*/ +void verify_apb_access_loop(void); diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_ecp.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_ecp.c new file mode 100644 index 000000000..db4258703 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_ecp.c @@ -0,0 +1,333 @@ +/* mbedTLS Elliptic Curve functionality tests + * + * Focus on testing functionality where we use ESP32 hardware + * accelerated crypto features. + * + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "test_utils.h" +#include "ccomp_timer.h" +#include "unity.h" + +/* Note: negative value here so that assert message prints a grep-able + error hex value (mbedTLS uses -N for error codes) */ +#define TEST_ASSERT_MBEDTLS_OK(X) TEST_ASSERT_EQUAL_HEX32(0, -(X)) + +/* TODO: Currently MBEDTLS_ECDH_LEGACY_CONTEXT is enabled by default + * when MBEDTLS_ECP_RESTARTABLE is enabled. + * This is a temporary workaround to allow that. + * + * The legacy option is soon going to be removed in future mbedtls + * versions and this workaround will be removed once the appropriate + * solution is available. + */ +#ifdef CONFIG_MBEDTLS_ECDH_LEGACY_CONTEXT +#define ACCESS_ECDH(S, var) S.MBEDTLS_PRIVATE(var) +#else +#define ACCESS_ECDH(S, var) S.MBEDTLS_PRIVATE(ctx).MBEDTLS_PRIVATE(mbed_ecdh).MBEDTLS_PRIVATE(var) +#endif + +#if CONFIG_NEWLIB_NANO_FORMAT +#define NEWLIB_NANO_COMPAT_FORMAT PRIu32 +#define NEWLIB_NANO_COMPAT_CAST(int64_t_var) (uint32_t)int64_t_var +#else +#define NEWLIB_NANO_COMPAT_FORMAT PRId64 +#define NEWLIB_NANO_COMPAT_CAST(int64_t_var) int64_t_var +#endif + +TEST_CASE("mbedtls ECDH Generate Key", "[mbedtls]") +{ + mbedtls_ecdh_context ctx; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_ecdh_init(&ctx); + mbedtls_ctr_drbg_init(&ctr_drbg); + + mbedtls_entropy_init(&entropy); + TEST_ASSERT_MBEDTLS_OK( mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0) ); + + TEST_ASSERT_MBEDTLS_OK( mbedtls_ecp_group_load(ACCESS_ECDH(&ctx, grp), MBEDTLS_ECP_DP_CURVE25519) ); + + TEST_ASSERT_MBEDTLS_OK( mbedtls_ecdh_gen_public(ACCESS_ECDH(&ctx, grp), ACCESS_ECDH(&ctx, d), ACCESS_ECDH(&ctx, Q), + mbedtls_ctr_drbg_random, &ctr_drbg ) ); + + mbedtls_ecdh_free(&ctx); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); +} + +TEST_CASE("mbedtls ECP self-tests", "[mbedtls]") +{ + TEST_ASSERT_EQUAL(0, mbedtls_ecp_self_test(1)); +} + +TEST_CASE("mbedtls ECP mul w/ koblitz", "[mbedtls]") +{ + /* Test case code via https://github.com/espressif/esp-idf/issues/1556 */ + mbedtls_entropy_context ctxEntropy; + mbedtls_ctr_drbg_context ctxRandom; + mbedtls_ecdsa_context ctxECDSA; + const char* pers = "myecdsa"; + + mbedtls_entropy_init(&ctxEntropy); + mbedtls_ctr_drbg_init(&ctxRandom); + TEST_ASSERT_MBEDTLS_OK( mbedtls_ctr_drbg_seed(&ctxRandom, mbedtls_entropy_func, &ctxEntropy, + (const unsigned char*) pers, strlen(pers)) ); + + mbedtls_ecdsa_init(&ctxECDSA); + + TEST_ASSERT_MBEDTLS_OK( mbedtls_ecdsa_genkey(&ctxECDSA, MBEDTLS_ECP_DP_SECP256K1, + mbedtls_ctr_drbg_random, &ctxRandom) ); + + + TEST_ASSERT_MBEDTLS_OK(mbedtls_ecp_mul(&ctxECDSA.MBEDTLS_PRIVATE(grp), &ctxECDSA.MBEDTLS_PRIVATE(Q), + &ctxECDSA.MBEDTLS_PRIVATE(d), &ctxECDSA.MBEDTLS_PRIVATE(grp).G, + mbedtls_ctr_drbg_random, &ctxRandom) ); + + mbedtls_ecdsa_free(&ctxECDSA); + mbedtls_ctr_drbg_free(&ctxRandom); + mbedtls_entropy_free(&ctxEntropy); +} + +#if CONFIG_MBEDTLS_HARDWARE_ECC + +#define SMALL_SCALAR 127 + +/* + * Coordinates and integers stored in big endian format + */ +const uint8_t ecc_p192_point_x[] = { + 0x18, 0x8D, 0xA8, 0x0E, 0xB0, 0x30, 0x90, 0xF6, + 0x7C, 0xBF, 0x20, 0xEB, 0x43, 0xA1, 0x88, 0x00, + 0xF4, 0xFF, 0x0A, 0xFD, 0x82, 0xFF, 0x10, 0x12 +}; + +const uint8_t ecc_p192_point_y[] = { + 0x07, 0x19, 0x2B, 0x95, 0xFF, 0xC8, 0xDA, 0x78, + 0x63, 0x10, 0x11, 0xED, 0x6B, 0x24, 0xCD, 0xD5, + 0x73, 0xF9, 0x77, 0xA1, 0x1E, 0x79, 0x48, 0x11 +}; + +const uint8_t ecc_p192_scalar[] = { + 0x6f, 0x18, 0x34, 0xeb, 0x16, 0xb7, 0xac, 0x9f, + 0x3c, 0x77, 0x71, 0xb3, 0x02, 0x30, 0x70, 0x48, + 0x75, 0x87, 0xbb, 0x6f, 0x80, 0x34, 0x8d, 0x5e +}; + +const uint8_t ecc_p192_mul_res_x[] = { + 0x3F, 0xEE, 0x6F, 0x1F, 0x99, 0xDC, 0xCB, 0x78, + 0xB7, 0x47, 0x1C, 0x2A, 0xF5, 0xA0, 0xAC, 0xE6, + 0xEC, 0x24, 0x82, 0x37, 0x6C, 0xC0, 0x27, 0xC5, +}; + +const uint8_t ecc_p192_mul_res_y[] = { + 0xDF, 0xF3, 0x9E, 0x76, 0x24, 0xF4, 0xF6, 0xB4, + 0xF0, 0x0A, 0x18, 0xE1, 0x0B, 0xD2, 0xD9, 0x83, + 0xE8, 0x29, 0x5E, 0xD9, 0x46, 0x54, 0xC3, 0xE1 +}; + +const uint8_t ecc_p192_small_mul_res_x[] = { + 0x62, 0xBF, 0x33, 0xC1, 0x75, 0xB5, 0xEB, 0x1D, + 0xBE, 0xC7, 0x15, 0x04, 0x03, 0xA7, 0xDD, 0x9D, + 0x0B, 0x17, 0x9D, 0x3B, 0x06, 0x63, 0xFE, 0xD3 +}; + +const uint8_t ecc_p192_small_mul_res_y[] = { + 0xD4, 0xE9, 0x4E, 0x4D, 0x89, 0x4D, 0xB5, 0x99, + 0x8A, 0xE1, 0x85, 0x81, 0x27, 0x38, 0x23, 0x32, + 0x92, 0xCF, 0xE8, 0x38, 0xCA, 0x39, 0xF2, 0xE1 +}; + +const uint8_t ecc_p256_point_x[] = { + 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, + 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, + 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, + 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96 +}; + +const uint8_t ecc_p256_point_y[] = { + 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, + 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, + 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, + 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5 +}; + +const uint8_t ecc_p256_scalar[] = { + 0xB2, 0xC5, 0x9E, 0x92, 0x64, 0xCD, 0x5F, 0x66, + 0x9E, 0xC8, 0x83, 0x6D, 0x99, 0x61, 0x18, 0x72, + 0xC8, 0x60, 0x83, 0x1E, 0xE5, 0x79, 0xCC, 0x73, + 0xA9, 0xB4, 0x74, 0x85, 0x70, 0x11, 0x2D, 0xA2, +}; + +const uint8_t ecc_p256_mul_res_x[] = { + 0x26, 0x1A, 0x0F, 0xBD, 0xA5, 0xE5, 0x1E, 0xE7, + 0xB3, 0xC3, 0xB7, 0x09, 0xD1, 0x4A, 0x7A, 0x2A, + 0x16, 0x69, 0x4B, 0xAF, 0x76, 0x5C, 0xD4, 0x0E, + 0x93, 0x57, 0xB8, 0x67, 0xF9, 0xA1, 0xE5, 0xE8 +}; + +const uint8_t ecc_p256_mul_res_y[] = { + 0xA0, 0xF4, 0x2E, 0x62, 0x36, 0x25, 0x9F, 0xE0, + 0xF2, 0xA0, 0x41, 0x42, 0xD2, 0x95, 0x89, 0x41, + 0x38, 0xF0, 0xEB, 0x6E, 0xA7, 0x96, 0x29, 0x24, + 0xC7, 0xD4, 0x0C, 0x90, 0xA1, 0xC9, 0xD3, 0x3A +}; + +const uint8_t ecc_p256_small_mul_res_x[] = { + 0x53, 0x4D, 0x45, 0xDB, 0x6B, 0xAC, 0xA8, 0xE2, + 0xD2, 0xA5, 0xD0, 0xA7, 0x65, 0xF1, 0x60, 0x13, + 0xA8, 0xD4, 0xEB, 0x58, 0xC6, 0xAA, 0xAD, 0x35, + 0x67, 0xCE, 0xBD, 0xFA, 0xC4, 0x2D, 0x62, 0x3C +}; + +const uint8_t ecc_p256_small_mul_res_y[] = { + 0xFA, 0xD6, 0x69, 0xC8, 0x9A, 0x2A, 0x54, 0xE4, + 0x41, 0x54, 0x35, 0x7F, 0x99, 0x2C, 0xCE, 0xC8, + 0xEE, 0xF0, 0x93, 0xE0, 0xF2, 0x3A, 0x63, 0x1D, + 0x17, 0xFD, 0xF6, 0x64, 0x41, 0x9E, 0x50, 0x0C +}; + + +static int rng_wrapper(void *ctx, unsigned char *buf, size_t len) +{ + esp_fill_random(buf, len); + return 0; +} + +static void test_ecp_mul(mbedtls_ecp_group_id id, const uint8_t *x_coord, const uint8_t *y_coord, const uint8_t *scalar, + const uint8_t *result_x_coord, const uint8_t *result_y_coord) +{ + int64_t elapsed_time; + uint8_t x[32]; + uint8_t y[32]; + int size; + int ret; + + mbedtls_ecp_group grp; + mbedtls_ecp_point R; + mbedtls_ecp_point P; + mbedtls_mpi m; + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&R); + mbedtls_ecp_point_init(&P); + mbedtls_mpi_init(&m); + + mbedtls_ecp_group_load(&grp, id); + + size = grp.pbits / 8; + + if (!scalar) { + mbedtls_mpi_lset(&m, SMALL_SCALAR); + } else { + mbedtls_mpi_read_binary(&m, scalar, size); + } + + mbedtls_mpi_read_binary(&P.MBEDTLS_PRIVATE(X), x_coord, size); + mbedtls_mpi_read_binary(&P.MBEDTLS_PRIVATE(Y), y_coord, size); + + mbedtls_mpi_lset(&P.MBEDTLS_PRIVATE(Z), 1); + + ccomp_timer_start(); + ret = mbedtls_ecp_mul(&grp, &R, &m, &P, rng_wrapper, NULL); + elapsed_time = ccomp_timer_stop(); + + TEST_ASSERT_EQUAL(0, ret); + + mbedtls_mpi_write_binary(&R.MBEDTLS_PRIVATE(X), x, mbedtls_mpi_size(&R.MBEDTLS_PRIVATE(X))); + mbedtls_mpi_write_binary(&R.MBEDTLS_PRIVATE(Y), y, mbedtls_mpi_size(&R.MBEDTLS_PRIVATE(Y))); + + TEST_ASSERT_EQUAL(0, memcmp(x, result_x_coord, mbedtls_mpi_size(&R.MBEDTLS_PRIVATE(X)))); + TEST_ASSERT_EQUAL(0, memcmp(y, result_y_coord, mbedtls_mpi_size(&R.MBEDTLS_PRIVATE(Y)))); + + if (id == MBEDTLS_ECP_DP_SECP192R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECP_P192_POINT_MULTIPLY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } else if (id == MBEDTLS_ECP_DP_SECP256R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECP_P256_POINT_MULTIPLY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } + + mbedtls_ecp_point_free(&R); + mbedtls_ecp_point_free(&P); + mbedtls_mpi_free(&m); + mbedtls_ecp_group_free(&grp); +} + +TEST_CASE("mbedtls ECP point multiply with SECP192R1", "[mbedtls]") +{ + test_ecp_mul(MBEDTLS_ECP_DP_SECP192R1, ecc_p192_point_x, ecc_p192_point_y, ecc_p192_scalar, + ecc_p192_mul_res_x, ecc_p192_mul_res_y); + + test_ecp_mul(MBEDTLS_ECP_DP_SECP192R1, ecc_p192_point_x, ecc_p192_point_y, NULL, + ecc_p192_small_mul_res_x, ecc_p192_small_mul_res_y); +} + +TEST_CASE("mbedtls ECP point multiply with SECP256R1", "[mbedtls]") +{ + test_ecp_mul(MBEDTLS_ECP_DP_SECP256R1, ecc_p256_point_x, ecc_p256_point_y, ecc_p256_scalar, + ecc_p256_mul_res_x, ecc_p256_mul_res_y); + + test_ecp_mul(MBEDTLS_ECP_DP_SECP256R1, ecc_p256_point_x, ecc_p256_point_y, NULL, + ecc_p256_small_mul_res_x, ecc_p256_small_mul_res_y); +} + +static void test_ecp_verify(mbedtls_ecp_group_id id, const uint8_t *x_coord, const uint8_t *y_coord) +{ + int64_t elapsed_time; + int size; + int ret; + + mbedtls_ecp_group grp; + mbedtls_ecp_point P; + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&P); + + mbedtls_ecp_group_load(&grp, id); + + size = grp.pbits / 8; + + mbedtls_mpi_read_binary(&P.MBEDTLS_PRIVATE(X), x_coord, size); + mbedtls_mpi_read_binary(&P.MBEDTLS_PRIVATE(Y), y_coord, size); + mbedtls_mpi_lset(&P.MBEDTLS_PRIVATE(Z), 1); + + ccomp_timer_start(); + ret = mbedtls_ecp_check_pubkey(&grp, &P); + elapsed_time = ccomp_timer_stop(); + + TEST_ASSERT_EQUAL(0, ret); + + if (id == MBEDTLS_ECP_DP_SECP192R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECP_P192_POINT_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } else if (id == MBEDTLS_ECP_DP_SECP256R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECP_P256_POINT_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } + + mbedtls_ecp_point_free(&P); + mbedtls_ecp_group_free(&grp); +} + +TEST_CASE("mbedtls ECP point verify with SECP192R1", "[mbedtls]") +{ + test_ecp_verify(MBEDTLS_ECP_DP_SECP192R1, ecc_p192_mul_res_x, ecc_p192_mul_res_y); +} + +TEST_CASE("mbedtls ECP point verify with SECP256R1", "[mbedtls]") +{ + test_ecp_verify(MBEDTLS_ECP_DP_SECP256R1, ecc_p256_mul_res_x, ecc_p256_mul_res_y); +} +#endif /* CONFIG_MBEDTLS_HARDWARE_ECC */ diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_esp_crt_bundle.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_esp_crt_bundle.c new file mode 100644 index 000000000..a4b0af46a --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_esp_crt_bundle.c @@ -0,0 +1,487 @@ +/* SSL server using plain mbedTLS sockets + * + * Adapted from the ssl_server example in mbedtls. + * + * SPDX-FileCopyrightText: The Mbed TLS Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-FileContributor: 2019-2022 Espressif Systems (Shanghai) CO LTD + */ +#include "esp_err.h" +#include "esp_log.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/x509.h" +#include "mbedtls/ssl.h" +#include "entropy_poll.h" +#include "mbedtls/net_sockets.h" +#include "mbedtls/error.h" +#include "mbedtls/debug.h" +#include "mbedtls/esp_debug.h" + +#include "esp_crt_bundle.h" +#include "esp_random.h" + +#include "unity.h" +#include "test_utils.h" +#include "unity_test_utils.h" + +#define SERVER_ADDRESS "localhost" +#define SERVER_PORT "4433" + +extern const uint8_t server_cert_chain_pem_start[] asm("_binary_server_cert_chain_pem_start"); +extern const uint8_t server_cert_chain_pem_end[] asm("_binary_server_cert_chain_pem_end"); + +extern const uint8_t server_pk_start[] asm("_binary_prvtkey_pem_start"); +extern const uint8_t server_pk_end[] asm("_binary_prvtkey_pem_end"); + +extern const uint8_t server_cert_bundle_start[] asm("_binary_server_cert_bundle_start"); +extern const uint8_t server_cert_bundle_end[] asm("_binary_server_cert_bundle_end"); + +extern const uint8_t bad_md_crt_pem_start[] asm("_binary_bad_md_crt_pem_start"); +extern const uint8_t bad_md_crt_pem_end[] asm("_binary_bad_md_crt_pem_end"); + +extern const uint8_t wrong_sig_crt_pem_start[] asm("_binary_wrong_sig_crt_esp32_com_pem_start"); +extern const uint8_t wrong_sig_crt_pem_end[] asm("_binary_wrong_sig_crt_esp32_com_pem_end"); + +extern const uint8_t correct_sig_crt_pem_start[] asm("_binary_correct_sig_crt_esp32_com_pem_start"); +extern const uint8_t correct_sig_crt_pem_end[] asm("_binary_correct_sig_crt_esp32_com_pem_end"); + +#define SEM_TIMEOUT 10000 +typedef struct { + mbedtls_ssl_context ssl; + mbedtls_net_context listen_fd; + mbedtls_net_context client_fd; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_ssl_config conf; + mbedtls_x509_crt cert; + mbedtls_pk_context pkey; + +} mbedtls_endpoint_t; + +typedef enum { + ESP_CRT_VALIDATE_UNKNOWN, + ESP_CRT_VALIDATE_OK, + ESP_CRT_VALIDATE_FAIL, +}esp_crt_validate_res_t; + +int esp_crt_verify_callback(void *buf, mbedtls_x509_crt *crt, int data, uint32_t *flags); + +static const char *TAG = "cert_bundle_test"; + +static volatile bool exit_flag; + +esp_err_t endpoint_teardown(mbedtls_endpoint_t *endpoint); + +static int myrand(void *rng_state, unsigned char *output, size_t len) +{ + size_t olen; + return mbedtls_hardware_poll(rng_state, output, len, &olen); +} + +esp_err_t server_setup(mbedtls_endpoint_t *server) +{ + int ret; + mbedtls_ssl_config_init( &server->conf ); +#if CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log( &server->conf, CONFIG_MBEDTLS_DEBUG_LEVEL ); +#endif + mbedtls_net_init( &server->listen_fd ); + mbedtls_net_init( &server->client_fd ); + mbedtls_ssl_init( &server->ssl ); + mbedtls_x509_crt_init( &server->cert ); + mbedtls_pk_init( &server->pkey ); + mbedtls_entropy_init( &server->entropy ); + mbedtls_ctr_drbg_init( &server->ctr_drbg ); + + ESP_LOGI(TAG, "Loading the server cert and key"); + ret = mbedtls_x509_crt_parse( &server->cert, server_cert_chain_pem_start, + server_cert_chain_pem_end - server_cert_chain_pem_start); + + if ( ret != 0 ) { + ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned %d", ret ); + return ESP_FAIL; + } + + ret = mbedtls_pk_parse_key( &server->pkey, (const unsigned char *)server_pk_start, + server_pk_end - server_pk_start, NULL, 0, myrand, NULL ); + if ( ret != 0 ) { + ESP_LOGE(TAG, "mbedtls_pk_parse_key returned %d", ret ); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Bind on https://%s:%s/", SERVER_ADDRESS, SERVER_PORT ); + if ( ( ret = mbedtls_net_bind( &server->listen_fd, NULL, SERVER_PORT, MBEDTLS_NET_PROTO_TCP ) ) != 0 ) { + ESP_LOGE(TAG, "mbedtls_net_bind returned %d", ret ); + return ESP_FAIL; + } + mbedtls_net_set_nonblock(&server->listen_fd); + + ESP_LOGI(TAG, "Seeding the random number generator"); + if ( ( ret = mbedtls_ctr_drbg_seed( &server->ctr_drbg, mbedtls_entropy_func, &server->entropy, + NULL, 0) ) != 0 ) { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret ); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Setting up the SSL data"); + if ( ( ret = mbedtls_ssl_config_defaults( &server->conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 ) { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret ); + return ESP_FAIL; + } + + mbedtls_ssl_conf_rng( &server->conf, mbedtls_ctr_drbg_random, &server->ctr_drbg ); + + if (( ret = mbedtls_ssl_conf_own_cert( &server->conf, &server->cert, &server->pkey ) ) != 0 ) { + ESP_LOGE(TAG, "mbedtls_ssl_conf_own_cert returned %d", ret ); + return ESP_FAIL; + } + + if (( ret = mbedtls_ssl_setup( &server->ssl, &server->conf ) ) != 0 ) { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned %d", ret ); + return ESP_FAIL; + } + return ESP_OK; +} + +void server_task(void *pvParameters) +{ + int ret; + mbedtls_endpoint_t server; + SemaphoreHandle_t *sema = (SemaphoreHandle_t *) pvParameters; + + + if (server_setup(&server) != ESP_OK) { + ESP_LOGE(TAG, "SSL server setup failed"); + goto exit; + } + + /* Signal that server is up and hence client task can start now */ + xSemaphoreGive(*sema); + + bool connected = false; + while (!exit_flag) { + + ret = mbedtls_net_accept( &server.listen_fd, &server.client_fd, NULL, 0, NULL ); + + if (ret == 0) { + connected = true; + } + + if (connected) { + mbedtls_ssl_set_bio( &server.ssl, &server.client_fd, mbedtls_net_send, mbedtls_net_recv, NULL ); + ret = mbedtls_ssl_handshake( &server.ssl ); + mbedtls_ssl_session_reset(&server.ssl); + connected = false; + } + + vTaskDelay(20 / portTICK_PERIOD_MS); + } + ESP_LOGE(TAG, "Server shutdown"); +exit: + endpoint_teardown(&server); + xSemaphoreGive(*sema); + vTaskSuspend(NULL); +} + + +esp_err_t endpoint_teardown(mbedtls_endpoint_t *endpoint) +{ + mbedtls_net_free( &endpoint->client_fd ); + mbedtls_net_free( &endpoint->listen_fd ); + + mbedtls_x509_crt_free( &endpoint->cert ); + mbedtls_pk_free( &endpoint->pkey ); + mbedtls_ssl_free( &endpoint->ssl ); + mbedtls_ssl_config_free( &endpoint->conf ); + + mbedtls_ctr_drbg_free( &endpoint->ctr_drbg ); + mbedtls_entropy_free( &endpoint->entropy ); + + return ESP_OK; +} + +esp_err_t client_setup(mbedtls_endpoint_t *client) +{ + int ret; + mbedtls_ssl_config_init( &client->conf ); +#if CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log( &client->conf, CONFIG_MBEDTLS_DEBUG_LEVEL ); +#endif + mbedtls_net_init( &client->client_fd ); + mbedtls_ssl_init( &client->ssl ); + mbedtls_x509_crt_init( &client->cert ); + mbedtls_pk_init( &client->pkey ); + mbedtls_entropy_init( &client->entropy ); + mbedtls_ctr_drbg_init( &client->ctr_drbg ); + + ESP_LOGI(TAG, "Seeding the random number generator"); + if ((ret = mbedtls_ctr_drbg_seed(&client->ctr_drbg, mbedtls_entropy_func, &client->entropy, + NULL, 0)) != 0) { + ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Setting hostname for TLS session..."); + /* Hostname set here should match CN in server certificate */ + if ((ret = mbedtls_ssl_set_hostname(&client->ssl, SERVER_ADDRESS)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Setting up the SSL/TLS structure..."); + if ((ret = mbedtls_ssl_config_defaults(&client->conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret); + return ESP_FAIL; + } + mbedtls_ssl_conf_rng(&client->conf, mbedtls_ctr_drbg_random, &client->ctr_drbg); + + if ((ret = mbedtls_ssl_setup(&client->ssl, &client->conf)) != 0) { + ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret); + return ESP_FAIL; + } + + return ESP_OK; +} + +void client_task(void *pvParameters) +{ + SemaphoreHandle_t *client_signal_sem = (SemaphoreHandle_t *) pvParameters; + int ret = ESP_FAIL; + + mbedtls_endpoint_t client; + esp_crt_validate_res_t res = ESP_CRT_VALIDATE_UNKNOWN; + + if (client_setup(&client) != ESP_OK) { + ESP_LOGE(TAG, "SSL client setup failed"); + goto exit; + } + + /* Test with default crt bundle that doesnt contain the ca crt */ + ESP_LOGI(TAG, "Connecting to %s:%s...", SERVER_ADDRESS, SERVER_PORT); + if ((ret = mbedtls_net_connect(&client.client_fd, SERVER_ADDRESS, SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Connected."); + mbedtls_ssl_set_bio(&client.ssl, &client.client_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that is missing the server root certificate"); + while ( ( ret = mbedtls_ssl_handshake( &client.ssl ) ) != 0 ) { + if ( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { + printf( "mbedtls_ssl_handshake failed with -0x%x\n", -ret ); + break; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate for bundle ..."); + ret = mbedtls_ssl_get_verify_result(&client.ssl); + + res = (ret == 0) ? ESP_CRT_VALIDATE_OK : ESP_CRT_VALIDATE_FAIL; + + if (res == ESP_CRT_VALIDATE_OK) { + ESP_LOGI(TAG, "Certificate verification passed!"); + } else { + ESP_LOGE(TAG, "Certificate verification failed!"); + } + TEST_ASSERT(res == ESP_CRT_VALIDATE_FAIL); + + // Reset session before new connection + mbedtls_ssl_close_notify(&client.ssl); + mbedtls_ssl_session_reset(&client.ssl); + mbedtls_net_free( &client.client_fd); + + /* Test with bundle that does contain the CA crt */ + esp_crt_bundle_attach(&client.conf); + esp_crt_bundle_set(server_cert_bundle_start, server_cert_bundle_end - server_cert_bundle_start); + + ESP_LOGI(TAG, "Connecting to %s:%s...", SERVER_ADDRESS, SERVER_PORT); + if ((ret = mbedtls_net_connect(&client.client_fd, SERVER_ADDRESS, SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + ESP_LOGI(TAG, "Connected."); + mbedtls_ssl_set_bio(&client.ssl, &client.client_fd, mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that is missing the server root certificate"); + while ( ( ret = mbedtls_ssl_handshake( &client.ssl ) ) != 0 ) { + if ( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { + printf( "mbedtls_ssl_handshake failed with -0x%x\n", -ret ); + break; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate for bundle ..."); + ret = mbedtls_ssl_get_verify_result(&client.ssl); + + res = (ret == 0) ? ESP_CRT_VALIDATE_OK : ESP_CRT_VALIDATE_FAIL; + + if (res == ESP_CRT_VALIDATE_OK) { + ESP_LOGI(TAG, "Certificate verification passed!"); + } else { + ESP_LOGE(TAG, "Certificate verification failed!"); + } + TEST_ASSERT(res == ESP_CRT_VALIDATE_OK); + + // Reset session before new connection + mbedtls_ssl_close_notify(&client.ssl); + mbedtls_ssl_session_reset(&client.ssl); + mbedtls_net_free( &client.client_fd); + + +exit: + mbedtls_ssl_close_notify(&client.ssl); + mbedtls_ssl_session_reset(&client.ssl); + esp_crt_bundle_detach(&client.conf); + endpoint_teardown(&client); + xSemaphoreGive(*client_signal_sem); + vTaskSuspend(NULL); +} + + +TEST_CASE("custom certificate bundle", "[mbedtls]") +{ + test_case_uses_tcpip(); + + SemaphoreHandle_t signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(signal_sem); + + exit_flag = false; + TaskHandle_t server_task_handle; + xTaskCreate(server_task, "server task", 8192, &signal_sem, 10, &server_task_handle); + + // Wait for the server to start up + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server start failed"); + } + + SemaphoreHandle_t client_signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(client_signal_sem); + + TaskHandle_t client_task_handle; + xTaskCreate(client_task, "client task", 8192, &client_signal_sem, 10, &client_task_handle); + + if (!xSemaphoreTake(client_signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("client_signal_sem not released, client exit failed"); + } + unity_utils_task_delete(client_task_handle); + + exit_flag = true; + + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server exit failed"); + } + unity_utils_task_delete(server_task_handle); + vSemaphoreDelete(client_signal_sem); + vSemaphoreDelete(signal_sem); +} + +TEST_CASE("custom certificate bundle - weak hash", "[mbedtls]") +{ + /* A weak signature hash on the trusted certificate should not stop + us from verifying the chain, since we already trust it a weak signature hash is + not a security issue */ + + mbedtls_x509_crt crt; + uint32_t flags = 0; + + esp_crt_bundle_attach(NULL); + + mbedtls_x509_crt_init( &crt ); + mbedtls_x509_crt_parse(&crt, bad_md_crt_pem_start, bad_md_crt_pem_end - bad_md_crt_pem_start); + TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) == 0); + + mbedtls_x509_crt_free(&crt); + + esp_crt_bundle_detach(NULL); +} + +TEST_CASE("custom certificate bundle - wrong signature", "[mbedtls]") +{ + /* Check that the bundle will not verify a valid certificate from trusted root where the signature is wrong */ + + mbedtls_x509_crt crt; + uint32_t flags = 0; + + esp_crt_bundle_attach(NULL); + + mbedtls_x509_crt_init( &crt ); + /* esp32.com cert chain where 1 byte in the signature is changed */ + printf("Testing certificate with wrong signature\n"); + mbedtls_x509_crt_parse(&crt, wrong_sig_crt_pem_start, wrong_sig_crt_pem_end - wrong_sig_crt_pem_start); + TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) != 0); + mbedtls_x509_crt_free(&crt); + + mbedtls_x509_crt_init( &crt ); + /* the correct esp32.com cert chain*/ + printf("Testing certificate with correct signature\n"); + mbedtls_x509_crt_parse(&crt, correct_sig_crt_pem_start, correct_sig_crt_pem_end - correct_sig_crt_pem_start); + TEST_ASSERT(mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, esp_crt_verify_callback, NULL) == 0); + mbedtls_x509_crt_free(&crt); + + esp_crt_bundle_detach(NULL); +} + +TEST_CASE("custom certificate bundle init API - bound checking", "[mbedtls]") +{ + + uint8_t test_bundle[256] = {0}; + esp_err_t esp_ret; + /* The API should fail with bundle size given as 1 */ + esp_ret = esp_crt_bundle_set(test_bundle, 1); + TEST_ASSERT( esp_ret == ESP_ERR_INVALID_ARG); + + /* Check that the esp_crt_bundle_set API will not accept a bundle + * which has more no. of certs than configured in + * CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS */ + + uint8_t rand; + esp_fill_random(&rand, 1); + test_bundle[0] = rand; + + /* Make sure that the number of certs will always be greater than + * CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS */ + test_bundle[1] = rand + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS; + + esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); + TEST_ASSERT( esp_ret == ESP_ERR_INVALID_ARG); + + /* The API should fail with bundle_size < BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) */ + test_bundle[0] = 0; + test_bundle[1] = 1; /* set num_certs = 1 */ + esp_ret = esp_crt_bundle_set(test_bundle, 5); + TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); + + /* Cert number is greater than actual certs present, The API should fail */ + /* Actual No. of certs present in bundle = 1, setting num_certs to 5 */ + test_bundle[1] = 5; /* num_certs */ + test_bundle[3] = 5; /* cert_1_name_len */ + test_bundle[5] = 10; /* cert_1_pub_key_len */ + /* Actual bundle size becomes BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) + cert_1_name_len(5) + cert_1_pub_key_len(10) + * i.e. 21 bytes */ + esp_ret = esp_crt_bundle_set(test_bundle, 21); + TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); + + /* The API should fail if bundle_size < BUNDLE_HEADER_OFFSET (2) + CRT_HEADER_OFFSET (4) + cert_1_name_len(5) + cert_1_pub_key_len(10) */ + esp_ret = esp_crt_bundle_set(test_bundle, 20); + TEST_ASSERT(esp_ret == ESP_ERR_INVALID_ARG); + + esp_crt_bundle_detach(NULL); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_gcm.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_gcm.c new file mode 100644 index 000000000..07af880d9 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_gcm.c @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include "sys/param.h" +#include "esp_heap_caps.h" +#include "mbedtls/gcm.h" +#include "sdkconfig.h" +#include "unity.h" + + +#if CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER + +typedef struct { + uint8_t *plaintext; + size_t plaintext_length; + uint8_t *aad_buf; + size_t aad_length; + uint8_t *iv; + size_t iv_length; + uint8_t *key; + size_t key_bits; + size_t tag_len; +} gcm_test_cfg_t; + +typedef struct { + const uint8_t *expected_tag; + const uint8_t *ciphertext_last_block; // Last block of the ciphertext +} gcm_test_expected_res_t; + +typedef enum { + GCM_TEST_CRYPT_N_TAG, + GCM_TEST_START_UPDATE_FINISH, +} gcm_test_type_t; + +static void gcm_test(gcm_test_cfg_t *cfg, gcm_test_expected_res_t *res, gcm_test_type_t gcm_type) +{ + mbedtls_gcm_context ctx; + mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_ARIA; + + uint8_t tag_buf_encrypt[16] = {}; + uint8_t tag_buf_decrypt[16] = {}; + uint8_t iv_buf[16] = {}; + uint8_t *ciphertext = malloc(cfg->plaintext_length); + uint8_t *output = malloc(cfg->plaintext_length); + size_t olen; + + if (cfg->plaintext_length != 0) { + TEST_ASSERT_NOT_NULL(ciphertext); + TEST_ASSERT_NOT_NULL(output); + } + + memset(ciphertext, 0, cfg->plaintext_length); + memset(output, 0, cfg->plaintext_length); + memcpy(iv_buf, cfg->iv, cfg->iv_length); + + mbedtls_gcm_init(&ctx); + TEST_ASSERT(mbedtls_gcm_setkey(&ctx, cipher, cfg->key, cfg->key_bits) == 0); + + if (gcm_type == GCM_TEST_CRYPT_N_TAG) { + mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_GCM_ENCRYPT, cfg->plaintext_length, iv_buf, cfg->iv_length, cfg->aad_buf, cfg->aad_length, cfg->plaintext, ciphertext, cfg->tag_len, tag_buf_encrypt); + } else if (gcm_type == GCM_TEST_START_UPDATE_FINISH) { + TEST_ASSERT(mbedtls_gcm_starts(&ctx, MBEDTLS_GCM_ENCRYPT, iv_buf, cfg->iv_length) == 0); + TEST_ASSERT(mbedtls_gcm_update_ad(&ctx, cfg->aad_buf, cfg->aad_length) == 0); + TEST_ASSERT(mbedtls_gcm_update(&ctx, cfg->plaintext, cfg->plaintext_length, ciphertext, cfg->plaintext_length, &olen) == 0); + TEST_ASSERT(mbedtls_gcm_finish(&ctx, ciphertext, cfg->plaintext_length, &olen, tag_buf_encrypt, cfg->tag_len) == 0); + } + + size_t offset = cfg->plaintext_length > 16 ? cfg->plaintext_length - 16 : 0; + /* Sanity check: make sure the last ciphertext block matches what we expect to see. */ + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->ciphertext_last_block, ciphertext + offset, MIN(16, cfg->plaintext_length)); + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->expected_tag, tag_buf_encrypt, cfg->tag_len); + + + if (gcm_type == GCM_TEST_CRYPT_N_TAG) { + TEST_ASSERT(mbedtls_gcm_auth_decrypt(&ctx, cfg->plaintext_length, iv_buf, cfg->iv_length, cfg->aad_buf, cfg->aad_length, res->expected_tag, cfg->tag_len, ciphertext, output) == 0); + } else if (gcm_type == GCM_TEST_START_UPDATE_FINISH) { + TEST_ASSERT(mbedtls_gcm_starts(&ctx, MBEDTLS_GCM_DECRYPT, iv_buf, cfg->iv_length) == 0); + TEST_ASSERT(mbedtls_gcm_update_ad(&ctx, cfg->aad_buf, cfg->aad_length) == 0); + TEST_ASSERT(mbedtls_gcm_update(&ctx, ciphertext, cfg->plaintext_length, output, cfg->plaintext_length, &olen) == 0); + TEST_ASSERT(mbedtls_gcm_finish(&ctx, output, cfg->plaintext_length, &olen, tag_buf_decrypt, cfg->tag_len) == 0); + + /* mbedtls_gcm_auth_decrypt already checks tag so only needed for GCM_TEST_START_UPDATE_FINISH */ + TEST_ASSERT_EQUAL_HEX8_ARRAY(res->expected_tag, tag_buf_decrypt, cfg->tag_len); + } + + TEST_ASSERT_EQUAL_HEX8_ARRAY(cfg->plaintext, output, cfg->plaintext_length); + + mbedtls_gcm_free(&ctx); + free(ciphertext); + free(output); +} + + +TEST_CASE("mbedtls ARIA GCM test", "[gcm]") +{ + const unsigned SZ = 1600; + uint8_t aad[16]; + uint8_t iv[16]; + uint8_t key[16]; + + const uint8_t expected_last_block[] = { + 0xbe, 0x96, 0xf1, 0x57, 0x34, 0x07, 0x3f, 0x9d, + 0x87, 0x6b, 0x39, 0x22, 0xe4, 0xef, 0xff, 0xf0, + }; + const uint8_t expected_tag[] = { + 0xef, 0x4e, 0xa8, 0x24, 0x07, 0x65, 0x36, 0x12, + 0xb1, 0xde, 0x7e, 0x23, 0xda, 0xea, 0x7c, 0x6b, + }; + + uint8_t *plaintext = malloc(SZ); + TEST_ASSERT_NOT_NULL(plaintext); + + memset(plaintext, 0xAA, SZ); + memset(iv, 0xEE, 16); + memset(key, 0x44, 16); + memset(aad, 0x76, 16); + + gcm_test_cfg_t cfg = { + .plaintext = plaintext, + .plaintext_length = SZ, + .iv = iv, + .iv_length = sizeof(iv), + .key = key, + .key_bits = 8 * sizeof(key), + .aad_buf = aad, + .aad_length = sizeof(aad), + .tag_len = 16 + }; + + gcm_test_expected_res_t res = { + .expected_tag = expected_tag, + .ciphertext_last_block = expected_last_block, + }; + + gcm_test(&cfg, &res, GCM_TEST_CRYPT_N_TAG); + gcm_test(&cfg, &res, GCM_TEST_START_UPDATE_FINISH); + free(plaintext); +} + +#endif /* CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER */ diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls.c new file mode 100644 index 000000000..a35ba2140 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls.c @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* mbedTLS self-tests as unit tests + + Focus on testing functionality where we use ESP32 hardware + accelerated crypto features. + + See also test_hwcrypto.c in esp32 component, which tests hardware crypto without mbedTLS. +*/ +#include +#include +#include +#include +#include "mbedtls/sha1.h" +#include "mbedtls/sha256.h" +#include "mbedtls/sha512.h" +#include "mbedtls/aes.h" +#include "mbedtls/bignum.h" +#include "mbedtls/rsa.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "sdkconfig.h" +#include "test_apb_dport_access.h" +#include "test_utils.h" + +TEST_CASE("mbedtls AES self-tests", "[aes]") +{ + start_apb_access_loop(); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_aes_self_test(1), "AES self-tests should pass."); + verify_apb_access_loop(); +} + +TEST_CASE("mbedtls MPI self-tests", "[bignum]") +{ + start_apb_access_loop(); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_mpi_self_test(1), "MPI self-tests should pass."); + verify_apb_access_loop(); +} + +TEST_CASE("mbedtls RSA self-tests", "[bignum]") +{ + start_apb_access_loop(); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_rsa_self_test(1), "RSA self-tests should pass."); + verify_apb_access_loop(); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_ecdsa.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_ecdsa.c new file mode 100644 index 000000000..aeb830f0b --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_ecdsa.c @@ -0,0 +1,237 @@ +/* mbedTLS Elliptic Curve Digital Signature performance tests + * + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "test_utils.h" +#include "ccomp_timer.h" +#include "unity.h" + +#include "ecdsa/ecdsa_alt.h" + +#define TEST_ASSERT_MBEDTLS_OK(X) TEST_ASSERT_EQUAL_HEX32(0, -(X)) + +#if CONFIG_NEWLIB_NANO_FORMAT +#define NEWLIB_NANO_COMPAT_FORMAT PRIu32 +#define NEWLIB_NANO_COMPAT_CAST(int64_t_var) (uint32_t)int64_t_var +#else +#define NEWLIB_NANO_COMPAT_FORMAT PRId64 +#define NEWLIB_NANO_COMPAT_CAST(int64_t_var) int64_t_var +#endif + +/* + * All the following values are in big endian format, as required by the mbedTLS APIs + */ + +const uint8_t sha[] = { + 0x0c, 0xaa, 0x08, 0xb4, 0xf0, 0x89, 0xd3, 0x45, + 0xbb, 0x55, 0x98, 0xd9, 0xc2, 0xe9, 0x65, 0x5d, + 0x7e, 0xa3, 0xa9, 0xc3, 0xcd, 0x69, 0xb1, 0xcf, + 0x91, 0xbe, 0x58, 0x10, 0xfe, 0x80, 0x65, 0x6e +}; + +#if CONFIG_MBEDTLS_HARDWARE_ECC || CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY + +const uint8_t ecdsa256_r[] = { + 0x26, 0x1a, 0x0f, 0xbd, 0xa5, 0xe5, 0x1e, 0xe7, + 0xb3, 0xc3, 0xb7, 0x09, 0xd1, 0x4a, 0x7a, 0x2a, + 0x16, 0x69, 0x4b, 0xaf, 0x76, 0x5c, 0xd4, 0x0e, + 0x93, 0x57, 0xb8, 0x67, 0xf9, 0xa1, 0xe5, 0xe8 +}; + +const uint8_t ecdsa256_s[] = { + 0x63, 0x59, 0xc0, 0x3b, 0x6a, 0xc2, 0xc4, 0xc4, + 0xaf, 0x47, 0x5c, 0xe6, 0x6d, 0x43, 0x3b, 0xa7, + 0x91, 0x51, 0x15, 0x62, 0x7e, 0x46, 0x0e, 0x68, + 0x84, 0xce, 0x72, 0xa0, 0xd8, 0x8b, 0x69, 0xd5 +}; + +const uint8_t ecdsa256_pub_x[] = { + 0xcb, 0x59, 0xde, 0x9c, 0xbb, 0x28, 0xaa, 0xac, + 0x72, 0x06, 0xc3, 0x43, 0x2a, 0x65, 0x82, 0xcc, + 0x68, 0x01, 0x76, 0x68, 0xfc, 0xec, 0xf5, 0x91, + 0xd1, 0x9e, 0xbf, 0xcf, 0x67, 0x7d, 0x7d, 0xbe +}; + +const uint8_t ecdsa256_pub_y[] = { + 0x00, 0x66, 0x14, 0x74, 0xe0, 0x06, 0x44, 0x66, + 0x6f, 0x3b, 0x8c, 0x3b, 0x2d, 0x05, 0xf6, 0xd5, + 0xb2, 0x5d, 0xe4, 0x85, 0x6c, 0x61, 0x38, 0xc5, + 0xb1, 0x21, 0xde, 0x2b, 0x44, 0xf5, 0x13, 0x62 +}; + +const uint8_t ecdsa192_r[] = { + 0x2b, 0x8a, 0x18, 0x2f, 0xb2, 0x75, 0x26, 0xb7, + 0x1c, 0xe1, 0xe2, 0x6d, 0xaa, 0xe7, 0x74, 0x2c, + 0x42, 0xc8, 0xd5, 0x09, 0x4f, 0xb7, 0xee, 0x9f +}; + +const uint8_t ecdsa192_s[] = { + 0x1a, 0x74, 0xb4, 0x5, 0xf4, 0x28, 0xa5, 0xb6, + 0xce, 0xed, 0xa5, 0xff, 0xa8, 0x60, 0x06, 0x2f, + 0xf6, 0xeb, 0x24, 0x59, 0x24, 0x30, 0x5b, 0x12 +}; + +const uint8_t ecdsa192_pub_x[] = { + 0xd0, 0x3f, 0x6f, 0xe7, 0x5d, 0xaa, 0xf4, 0xc0, + 0x1e, 0x63, 0x7b, 0x82, 0xab, 0x23, 0x33, 0x34, + 0x74, 0x59, 0x56, 0x5d, 0x21, 0x10, 0x9c, 0xb1 +}; + +const uint8_t ecdsa192_pub_y[] = { + 0x85, 0xfc, 0x76, 0xcb, 0x65, 0xbc, 0xc4, 0xbe, + 0x74, 0x09, 0xfd, 0xf3, 0x74, 0xdc, 0xc2, 0xde, + 0x7e, 0x4b, 0x23, 0xad, 0x46, 0x5c, 0x87, 0xc2 +}; + +void test_ecdsa_verify(mbedtls_ecp_group_id id, const uint8_t *hash, const uint8_t *r_comp, const uint8_t *s_comp, + const uint8_t *pub_x, const uint8_t *pub_y) +{ + int64_t elapsed_time; + mbedtls_mpi r, s; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + mbedtls_ecdsa_context ecdsa_context; + mbedtls_ecdsa_init(&ecdsa_context); + + mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), id); + size_t plen = mbedtls_mpi_size(&ecdsa_context.MBEDTLS_PRIVATE(grp).P); + + TEST_ASSERT_MBEDTLS_OK(mbedtls_mpi_read_binary(&r, r_comp, plen)); + + TEST_ASSERT_MBEDTLS_OK(mbedtls_mpi_read_binary(&s, s_comp, plen)); + + TEST_ASSERT_MBEDTLS_OK(mbedtls_mpi_read_binary(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), pub_x, plen)); + TEST_ASSERT_MBEDTLS_OK(mbedtls_mpi_read_binary(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), pub_y, plen)); + TEST_ASSERT_MBEDTLS_OK(mbedtls_mpi_lset(&ecdsa_context.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), 1)); + + ccomp_timer_start(); + TEST_ASSERT_MBEDTLS_OK(mbedtls_ecdsa_verify(&ecdsa_context.MBEDTLS_PRIVATE(grp), hash, 32, &ecdsa_context.MBEDTLS_PRIVATE(Q), &r, &s)); + elapsed_time = ccomp_timer_stop(); + + if (id == MBEDTLS_ECP_DP_SECP192R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P192_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } else if (id == MBEDTLS_ECP_DP_SECP256R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P256_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } + + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_ecdsa_free(&ecdsa_context); +} + +TEST_CASE("mbedtls ECDSA signature verification performance on SECP192R1", "[mbedtls]") +{ + test_ecdsa_verify(MBEDTLS_ECP_DP_SECP192R1, sha, ecdsa192_r, ecdsa192_s, + ecdsa192_pub_x, ecdsa192_pub_y); +} + +TEST_CASE("mbedtls ECDSA signature verification performance on SECP256R1", "[mbedtls]") +{ + test_ecdsa_verify(MBEDTLS_ECP_DP_SECP256R1, sha, ecdsa256_r, ecdsa256_s, + ecdsa256_pub_x, ecdsa256_pub_y); +} + +#endif /* CONFIG_MBEDTLS_HARDWARE_ECC */ + +#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN + +/* + * This test assumes that ECDSA private key has been burnt in efuse. + * + * ecdsa_key_p192.pem must be burnt in efuse block 4 + * ecdsa_key_p256.pem must be burnt in efuse block 5 + */ +#define SECP192R1_EFUSE_BLOCK 4 // EFUSE_BLK_KEY0 +#define SECP256R1_EFUSE_BLOCK 5 // EFUSE_BLK_KEY1 + +#define MAX_ECDSA_COMPONENT_LEN 32 +#define HASH_LEN 32 + +const uint8_t ecdsa256_sign_pub_x[] = { + 0xa2, 0x8f, 0x52, 0x60, 0x20, 0x9b, 0x54, 0x3c, + 0x13, 0x2f, 0x51, 0xb1, 0x89, 0xbf, 0xc7, 0xfa, + 0x84, 0x5c, 0x56, 0x96, 0x2a, 0x00, 0x67, 0xdd, + 0x7c, 0x8c, 0x0f, 0x63, 0x8b, 0x76, 0x7f, 0xb9, +}; + +const uint8_t ecdsa256_sign_pub_y[] = { + 0xf6, 0x4c, 0x87, 0x5b, 0x5a, 0x9b, 0x59, 0x0a, + 0xc4, 0x53, 0x04, 0x72, 0x0d, 0x7c, 0xde, 0xac, + 0x7e, 0xad, 0x49, 0x8c, 0xf7, 0x5c, 0xc3, 0x1c, + 0x1e, 0x81, 0xf2, 0x47, 0x01, 0x74, 0x05, 0xd5 +}; + +const uint8_t ecdsa192_sign_pub_x[] = { + 0x88, 0x47, 0x25, 0x3c, 0xb4, 0xb7, 0x87, 0x24, + 0x5e, 0x07, 0xe1, 0xc7, 0xfc, 0x76, 0x0f, 0x6b, + 0x83, 0xf6, 0x81, 0x7d, 0x9b, 0x5f, 0xc4, 0xb9, +}; + +const uint8_t ecdsa192_sign_pub_y[] = { + 0x9c, 0xfc, 0xaa, 0xed, 0xef, 0xba, 0x02, 0xc3, + 0x1c, 0x0a, 0x55, 0x17, 0xe0, 0x9d, 0x10, 0xcb, + 0x23, 0xae, 0x7e, 0x0f, 0x1f, 0x4d, 0x69, 0xd5 +}; + +void test_ecdsa_sign(mbedtls_ecp_group_id id, const uint8_t *hash, const uint8_t *pub_x, const uint8_t *pub_y) +{ + uint8_t r_be[MAX_ECDSA_COMPONENT_LEN] = {0}; + uint8_t s_be[MAX_ECDSA_COMPONENT_LEN] = {0}; + + mbedtls_mpi r, s; + mbedtls_mpi key_mpi; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + mbedtls_ecdsa_context ecdsa_context; + mbedtls_ecdsa_init(&ecdsa_context); + + if (id == MBEDTLS_ECP_DP_SECP192R1) { + mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP192R1); + esp_ecdsa_privkey_load_mpi(&key_mpi, SECP192R1_EFUSE_BLOCK); + } else if (id == MBEDTLS_ECP_DP_SECP256R1) { + mbedtls_ecp_group_load(&ecdsa_context.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1); + esp_ecdsa_privkey_load_mpi(&key_mpi, SECP256R1_EFUSE_BLOCK); + } + + mbedtls_ecdsa_sign(&ecdsa_context.MBEDTLS_PRIVATE(grp), &r, &s, &key_mpi, sha, HASH_LEN, NULL, NULL); + + mbedtls_mpi_write_binary(&r, r_be, MAX_ECDSA_COMPONENT_LEN); + mbedtls_mpi_write_binary(&s, s_be, MAX_ECDSA_COMPONENT_LEN); + + if (id == MBEDTLS_ECP_DP_SECP192R1) { + // Skip the initial zeroes + test_ecdsa_verify(id, sha, &r_be[8], &s_be[8], pub_x, pub_y); + } else if (id == MBEDTLS_ECP_DP_SECP256R1) { + test_ecdsa_verify(id, sha, r_be, s_be, pub_x, pub_y); + } +} + +TEST_CASE("mbedtls ECDSA signature generation on SECP192R1", "[mbedtls][efuse_key]") +{ + test_ecdsa_sign(MBEDTLS_ECP_DP_SECP192R1, sha, ecdsa192_sign_pub_x, ecdsa192_sign_pub_y); +} + +TEST_CASE("mbedtls ECDSA signature generation on SECP256R1", "[mbedtls][efuse_key]") +{ + test_ecdsa_sign(MBEDTLS_ECP_DP_SECP256R1, sha, ecdsa256_sign_pub_x, ecdsa256_sign_pub_y); +} + +#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN */ diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_mpi.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_mpi.c new file mode 100644 index 000000000..14bdc8497 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_mpi.c @@ -0,0 +1,282 @@ +/* mbedTLS bignum (MPI) self-tests as unit tests + * + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include "mbedtls/bignum.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "sdkconfig.h" +#include "test_utils.h" + +#define MBEDTLS_OK 0 + +/* Debugging function to print an MPI number to stdout. Happens to + print output that can be copy-pasted directly into a Python shell. +*/ +void mbedtls_mpi_printf(const char *name, const mbedtls_mpi *X) +{ + static char buf[2048]; + size_t n; + memset(buf, 0, sizeof(buf)); + mbedtls_mpi_write_string(X, 16, buf, sizeof(buf)-1, &n); + if(n) { + printf("%s = (s=%d) 0x%s\n", name, X->MBEDTLS_PRIVATE(s), buf); + } else { + printf("%s = TOOLONG\n", name); + } +} + +/* + Assert E == X, X=A*B if res_operands_overlap==0 + Assert E == A, A=A*B if res_operands_overlap==1 + Assert E == B, B=A*B if res_operands_overlap==2 +*/ +static void test_bignum_mult_variant(const char *a_str, const char *b_str, const char *e_str, size_t mod_bits, int res_operands_overlap) +{ + mbedtls_mpi A, B, X, E; + char x_buf[2048] = {0}; + size_t x_buf_len = 0; + + mbedtls_mpi_init(&A); + mbedtls_mpi_init(&B); + mbedtls_mpi_init(&X); + mbedtls_mpi_init(&E); + + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&A, 16, a_str)); + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&B, 16, b_str)); + + /* calulate X = A * B variant */ + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&E, 16, e_str)); + if (res_operands_overlap == 0) { + TEST_ASSERT_FALSE(mbedtls_mpi_mul_mpi(&X, &A, &B)); + } else if (res_operands_overlap == 1) { + mbedtls_mpi_copy( &X, &A ); + TEST_ASSERT_FALSE(mbedtls_mpi_mul_mpi(&X, &X, &B)); + } else if (res_operands_overlap == 2) { + mbedtls_mpi_copy( &X, &B ); + TEST_ASSERT_FALSE(mbedtls_mpi_mul_mpi(&X, &A, &X)); + } + + mbedtls_mpi_write_string(&X, 16, x_buf, sizeof(x_buf)-1, &x_buf_len); + TEST_ASSERT_EQUAL_STRING_MESSAGE(e_str, x_buf, "mbedtls_mpi_mul_mpi result wrong"); + +#ifdef CONFIG_MBEDTLS_HARDWARE_MPI + mbedtls_mpi M; + /* if mod_bits arg is set, also do a esp_mpi_mul_mod() call */ + if (mod_bits > 0 && mod_bits <= SOC_RSA_MAX_BIT_LEN) { + mbedtls_mpi_init(&M); + for(int i = 0; i < mod_bits; i++) { + mbedtls_mpi_set_bit(&M, i, 1); + } + TEST_ASSERT_FALSE(esp_mpi_mul_mpi_mod(&X, &A, &B, &M)); + + mbedtls_mpi_write_string(&X, 16, x_buf, sizeof(x_buf)-1, &x_buf_len); + TEST_ASSERT_EQUAL_STRING_MESSAGE(e_str, x_buf, "esp_mpi_mul_mpi_mod result wrong"); + + mbedtls_mpi_free(&M); + } +#endif + + mbedtls_mpi_free(&A); + mbedtls_mpi_free(&B); + mbedtls_mpi_free(&X); + mbedtls_mpi_free(&E); +} + +/* Assert E = A * B, including 3 variants: X=A*B A*=B, B*=A */ +static void test_bignum_mult(const char *a_str, const char *b_str, const char *e_str, size_t mod_bits) +{ + for (int overlap_operands=0; overlap_operands < 3; ++overlap_operands) { + test_bignum_mult_variant(a_str, b_str, e_str, mod_bits, overlap_operands); + } +} + + +TEST_CASE("test MPI multiplication", "[bignum]") +{ + /* Run some trivial numbers tests w/ various high modulo bit counts, + should make no difference to the result + */ + for(int i = 512; i <= SOC_RSA_MAX_BIT_LEN; i+= 512) { + test_bignum_mult("10", "100", "1000", + i); + } + + test_bignum_mult("60006FA8D3E3BD746BE39B860FFAADB4F108E15CF2ED8F685FB0E86CC4CB107A488720B41C3F1E18550F00619CD3CA8442296ECB54D2F52ECEE5346D310195700000000", + "BF474CA7", + "047BB102CAF58A48D3D97E4231BC0B753051D8232B9B939A2A4E310F88E65FEFD7762FC2DE0E2BAD6AA51A391DFFABD120653A312E4998F42E2C03AA404EE63B67275BC100000000", + 1024); + + test_bignum_mult("49493AC229831EC01EEB01EAF3BBEBC44768EADF9ABC30C87D1791F5E04245756ED4965361EC0599626884DF079B6B5738985CE76BD66FAA67E3AAAD60775D5C9D44C09FDF9E27C033696C007BE1C540D718CA148BA01FFA4A358541E9E9F02F72BE37AFAB037DAEA5E3669A770400D2F4A5DBBD83A83919D05E3DD64787BC80000000", + "B878CC29", + "34CF37013066D5BDA2C86CF1FE7BDA66604E0D55DAFF9864B6E727BFF5871012B0AB73D28D4E100BA1E4607AA2A247C912FDBC435C6BF7C5F8E00278AE1381B1E5F6E3D52D2CBE819F0D65CB37370666D156E7A7B1FD4698D8C9D3165FC8A83F9293C839521993619CCF8180E521300C4306206C9121D629754F1FCC7839BF6DFAF33080000000", + 3072); + + test_bignum_mult("24BF6185468786FDD303083D25E64EFC66CA472BC44D253102F8B4A9D3BFA75091386C0077937FE33FA3252D28855837AE1B484A8A9A45F7EE8C0C634F9E8CDDF79C5CE07EE72C7F123142198164234CABB724CF78B8173B9F880FC86322407AF1FEDFDDE2BEB674CA15F3E81A1521E071513A1E85B5DFA031F21ECAE9A34D", + "010001", + "24BF8644A80CCD855A00DB402E2374E2B5C6ADF60B78E97E2829B7A288697B103888FD38E393F776BF8664D04DB280BD0652F665D2E4D0923483FAEF5C01DC7C847A547CDBC7AB663EB0544AC37DA4B0CF03D0869D878FF3B6C3AF5072EAA39D3279D1DCC29C9933808ABDFE0DFD3BF59331AB6FBFD46556119250BD086E36A34D", + 1536); + + test_bignum_mult("-5D88B669C417EDD02213723546A906B7E9DA7683780E9B54856A2147467ADA316F8819D69486FC8056FE1E8EA7DEC5D5EF12340B95C4FC966F4B348D35893620", + "9AE7FBC99546432DF71896FC239EADAEF38D18D2B2F0E2DD275AA977E2BF4411F5A3B2A5D33605AEBBCCBA7FEB9F2D2FA74206CEC169D74BF5A8C50D6F48EA08", + "-38990016EB21810E3B5E6AEE339AEE72BB7CD629C4C9270A3D832701A2949BC82B2BE5A7F900C0C9937464699862821976095187D646884E8FBF01DE8C3442F3BC97B670AF573EFB74A9BBEBE4432EE74B0A83BBCDF59485D332B1FF49EB461A3A8B12C38FD72C7772D75EC6EBA5633199540C47678BD2F4ADEEA40830C2F100", + 2048); + + + /* 1 << 2050 * 0X1234 */ + test_bignum_mult("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "1234", + "48D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + 3072); + + /* multiply a 1178 bit number by a 2050 bit number */ + test_bignum_mult("AAAAAAAAAA75124938ABBECD0EEEEE333333333333333333333FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAAAAAAABBBBBBBBBBBBBBBBBBBB000000000000000000000000000000000004988A5293848932948872398400000000000FFFFFFFFFFF0000000000000EDFABC0204048975876873487387478327482374871327482347328742837483247283748234723874238", + "390587293875124938ABBECD0EEEEE3333333333333333333333333333333399999888000AAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBB00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000EDFABC0204048975876873487387478327482374871327482347328742837483247283748234723874238478327400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003012111111111111111100000000000000000000000111111111111111111111111", + "02603AF70D0421C1AD82CE623F28F70B128118D06D00C27D433EC25BA86E6105C3890A0B1973B8BE068CA68E159A21078785DDB37F94216FBF4AEC939958AF4B8CEA2A48895CECA87562FC846EAAE0C866AF9D41EEABFB1D579F5828E9666A15E2AF946F16A189B5C645872FDCA247D309AB0BCAFB0D112881186FCFFEDC87061B4AE4A375E9BBCF579A7BC87A8EAC8C6F66E107986FC603F920F5E1A0FD8C619D88D90066FFFC8F4DB77437EBD7E3BD7E398C4C01F93426E347E039DCA7B0A73C0C90A9C4271BB761ADFF88971D190CE5DA98EFC5D7390D33BC034908AF81D784A4D7F32D0902E0C5DABC706635D5A28FC0E3A364EDEB21E8E117041D0E4B51CA6F9684F434057E7FCF2AF6BD050334B1D11E043B0967154E57354B681161D3C618974D5A7E0385755B80B931AE9B59DD4402BAEC206F04B8440741B3C4CA6D9F7DAF0AE6B3BF1B24B76C2F12B9E9A7C50D32E2093608FC9A30CBD852329E64A9AE0BC3F513899EBFA28629C1DF38081FB8C6630408F70D7B9A37701ABA4176C8B7DCB8CC78BD7783B861A7FC50862E75191DB8", + 4096); + + + /* multiply two very large numbers (4080 bits x 4088 bits) with and without overlapping multipliers/multiplicant */ + test_bignum_mult("B96BF707C8CD3CA5AE8247C5CA2AF98140EFAE60179BE3F5BEAD7DA3C6D17404C529239DD1EFE6CADAE1AAFB4FE936B0107839C28A7861E4364EB093CB4698E4BBF6BD8BEF85D9B35781D14AEE1BE86E57B49DF98896CF037CCBD8C622603D84891FD6AC48BE4728E564E64FB715C149C243BAA289569D0FF2E0C9E183D38C8A669CEFF542737E35F3E484D39FF7A3727EF8DB733DAB3E359E1456C0AE33C358EFEC8079EDDD5D58E09B37744EE1DBDF567742CFC0CE98BCC9AD90242ECCF7F6FA696C8C1B32A4D7285C56AB3658DB1AD89A7331F69DEFE212DE8EEEE5B377EC7A4112A27A0FD02EFABB9D3025F6563B65DC214A38A6E7BF8C78B6A3D2A8BA12D75BFBF26ACA655EF13A145AC18D2A6C9B535AAF8290314A2512451B3BD6DA19C42F1FD1B958E1F49303EDEC0392A8CD8450FBC177B26FD2D6CC23F051655565B42FEDE9685A9E708CFC8EA766B94D7B9B627BFA98945BB8EF88E9E7FB696BC4729240F1C25F7085E8C8A9DE2241BBC388FFC65E0058B4327D554FD2D8AA872614052C38BE177F9EC0E705DFDD5F82DD5ED49DAF3582CA64E7F14CE97FD6F25B53FD888D1593450EDC5E79A947F18D0917E01F66ACE99FF4A249C14957A9860B839CEE5096F78FE02C7610E558FC0FCA803A6EF0FBA64AB94893E61080BC5D2AC5DA548E9E0D8E2B63BAB6B82247DF22007D925711E0FE45EB14B92665B6", + "C15B96BF707C8CD3CA5AE8247C5CA2AF98140EFAE60179BE3F5BEAD7DA3C6D17404C529239DD1EFE6CADAE1AAFB4FE936B0107839C28A7861E4364EB093CB4698E4BBF6BD8BEF85D9B35781D14AEE1BE86E57B49DF98896CF037CCBD8C622603D84891FD6AC48BE4728E564E64FB715C149C243BAA289569D0FF2E0C9E183D38C8A669CEFF542737E35F3E484D39FF7A3727EF8DB733DAB3E359E1456C0AE33C358EFEC8079EDDD5D58E09B37744EE1DBDF567742CFC0CE98BCC9AD90242ECCF7F6FA696C8C1B32A4D7285C56AB3658DB1AD89A7331F69DEFE212DE8EEEE5B377EC7A4112A27A0FD02EFABB9D3025F6563B65DC214A38A6E7BF8C78B6A3D2A8BA12D75BFBF26ACA655EF13A145AC18D2A6C9B535AAF8290314A2512451B3BD6DA19C42F1FD1B958E1F49303EDEC0392A8CD8450FBC177B26FD2D6CC23F051655565B42FEDE9685A9E708CFC8EA766B94D7B9B627BFA98945BB8EF88E9E7FB696BC4729240F1C25F7085E8C8A9DE2241BBC388FFC65E0058B4327D554FD2D8AA872614052C38BE177F9EC0E705DFDD5F82DD5ED49DAF3582CA64E7F14CE97FD6F25B53FD888D1593450EDC5E79A947F18D0917E01F66ACE99FF4A249C14957A9860B839CEE5096F78FE02C7610E558FC0FCA803A6EF0FBA64AB94893E61080BC5D2AC5DA548E9E0D8E2B63BAB6B82247DF22007D925711E0FE45EB14B92665B6", + "08C0CBBCD99EC6C840B63887B07378C45DF268C118061B2AFFD9D0C854EE0F7DE5F548FF5BA7F155CCA81EF086F9760FCD86722167AC88E504723CB19A772D9EFF4EC75DD29F6187D34535B2A801C82DF9B1D0F08B64373A5AE5BD31346EE06EDB032B0A306A871E4FDB576F3E8D8F32ED4E054D9292719E77A1C5500FBEC23C59F5CC49A4E0B49D15F92D426FEF36376CB674AACDABF68A731746CA74440C71534D30CBF0252DE4EC38EA53E5821C9868F636239923C460CB813C4F05DD5EC36987A390FDBB7EE345F9D2687D1EF9A26A1FF84BF38049E995E1A5F2D5318A7BEFC9AA15528F7A2253299D30718459CF7958694AA58D91CA28F22718D07105C3FBFA4FEA970A810DD52BFC6AB4D44E3253347A3C5F42C1E723588343B210581F3B8A97C616A26C9C99C1376E169B8C8ED5DE6FA2272D24FB05BA6351B687A27C5CA1CF3FC1BA2BF06DD7598DED3F89080A2AB6DD694000698A7488274396E55EA78104584A5A0523C4744D018DCBFD3E411DA8CA679199FE818C59E08B126356028E3B6AAEA61ACE679D6EF2587CCAE513AB99EB161A405A8AA6FB36BB308BB7CBC21E2117B7CE4B4D1A1FE7EE0386DD229220BFD892A293FD7B8F6617743612736CC2F6200C736FD49C6A4AC9565E4B4EFD841C5FC3F6883621FD4319A11319EF462B549A12DA699612F2E8CE08525559C3487AAD57DFDBF1CAB847174C2FF8BA8D5232E33E5D8F60E5BE4CD8BE5857A860B615EFCFFF38DC0EE6A40F725D5275892419D3915EDB534166AC402B5849EB16AF1E8ADA68EF8C2E6AC8BE254DAEDAC135DA0ECA0E5A3CCF5332CFAB4C2249A92EF96A7DD6F69B4B0F9379CDA164FB1CB1934F27F42C0CEF9AFBA6A0B1716A4A1092E6CC7081AC74503A22F3C2071BB4D3A6842772C02CE78BD1FB4E0D39EB19E2425D3DC77777A8E8254F86A69950C75C1D8CF98C512ECEAF2FF15DC8A6D373A20045F63166BFDB8899C6095D91FC282D49DB9154E74DC64C41A98EFABDB207FF31A88718FF5410EA5A15E76F9591E5BE3B26035FD8567117588D9B94708B98FD764529B43B09EC6A2CDF79E2C05D3A799484516369795E103C7FCB78F1B1CADA47C3092695220FF2DB05136A9401897DE182EDB89022E4E7419B43172808C0A9F3ED80A8DF0A9E5F8E59E57994053050E709E63A4809AAECC3DEABDF5E2B9ED3F8FDA6297811A666E81BB0914B1F9D5D558EE40DBD89BE8B9D7F58575CE66C5A5EC2939463D1CECDD760B2C0584535FEEB2125CB675A1AB09CEDC81F27FA6830423B1F8D426E361EB1B9AD203C33176ACAB28C618714E068DA294C9338EF92FF4ED9F67F438E33E53797C1C31F8FF8D5466887E5610EA41C0CABC07ED90894BA73ECF84F5F3C5443EFAC61F9826C54D176D482CB5174D08A7EA3C3933FEE4986DB38A1EEC08D3366711D64", + 0); + + /* Multiply two specific MPI values to reproduce issue mentioned in https://github.com/espressif/esp-idf/issues/11850 */ + test_bignum_mult("454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545", + "03", + "CFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCF", + 0); +} + +static bool test_bignum_modexp(const char *z_str, const char *x_str, const char *y_str, const char *m_str, int ret_error) +{ + mbedtls_mpi Z = {0}; // Z is non-initialized (the sign Z.s=0) + mbedtls_mpi X, Y, M; + char z_buf[400] = { 0 }; + size_t z_buf_len = 0; + bool fail = false; + + printf("%s = (%s ^ %s) mod %s ret=%d ... ", z_str, x_str, y_str, m_str, ret_error); + + mbedtls_mpi_init(&X); + mbedtls_mpi_init(&Y); + mbedtls_mpi_init(&M); + + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&X, 16, x_str)); + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&Y, 16, y_str)); + TEST_ASSERT_FALSE(mbedtls_mpi_read_string(&M, 16, m_str)); + + /* Z = (X ^ Y) mod M */ + // (Z is passed to mbedtls_mpi_exp_mod() as a non-initialized with the sign s=0) + int err = mbedtls_mpi_exp_mod(&Z, &X, &Y, &M, NULL); + if (ret_error != err) { + printf("\nExpected ret_error %d, Was %d \n", ret_error, err); + fail = true; + } + + if (ret_error == MBEDTLS_OK) { + mbedtls_mpi_write_string(&Z, 16, z_buf, sizeof(z_buf)-1, &z_buf_len); + if (strlen(z_str) != z_buf_len - 1 || memcmp(z_str, z_buf, strlen(z_str)) != 0) { + printf("\n"); + mbedtls_mpi_printf("Z", &Z); + mbedtls_mpi_printf("X", &X); + mbedtls_mpi_printf("Y", &Y); + mbedtls_mpi_printf("M", &M); + printf("\nsize: Expected %d, Was %d \n", strlen(z_str), z_buf_len - 1); + printf("Expected '%s' Was '%s' \n", z_str, z_buf); + fail = true; + } + } + + mbedtls_mpi_free(&Z); + mbedtls_mpi_free(&X); + mbedtls_mpi_free(&Y); + mbedtls_mpi_free(&M); + + if (fail == true) { + printf(" FAIL\n\n"); + } else { + printf(" PASS\n"); + } + return fail; +} + +TEST_CASE("test MPI modexp", "[bignum]") +{ + bool test_error = false; + printf("Z = (X ^ Y) mod M \n"); + // test_bignum_modexp(Z, X, Y, M, ret_error); + test_error |= test_bignum_modexp("01000000", "1000", "2", "FFFFFFFF", MBEDTLS_OK); + test_error |= test_bignum_modexp("014B5A90", "1234", "2", "FFFFFFF", MBEDTLS_OK); + test_error |= test_bignum_modexp("01234321", "1111", "2", "FFFFFFFF", MBEDTLS_OK); + test_error |= test_bignum_modexp("02", "5", "1", "3", MBEDTLS_OK); + test_error |= test_bignum_modexp("22", "55", "1", "33", MBEDTLS_OK); + test_error |= test_bignum_modexp("0222", "555", "1", "333", MBEDTLS_OK); + test_error |= test_bignum_modexp("2222", "5555", "1", "3333", MBEDTLS_OK); + test_error |= test_bignum_modexp("11", "5555", "1", "33", MBEDTLS_OK); + test_error |= test_bignum_modexp("55", "1111", "1", "77", MBEDTLS_OK); + test_error |= test_bignum_modexp("88", "1111", "2", "BB", MBEDTLS_OK); + test_error |= test_bignum_modexp("01000000", "2", "128", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", MBEDTLS_OK); + test_error |= test_bignum_modexp("0ABCDEF12345", "ABCDEF12345", "1", "FFFFFFFFFFFF", MBEDTLS_OK); + test_error |= test_bignum_modexp("0ABCDE", "ABCDE", "1", "FFFFF", MBEDTLS_OK); + + test_error |= test_bignum_modexp("04", "2", "2", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("04", "2", "-2", "9", MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + test_error |= test_bignum_modexp("04", "2", "2", "-9", MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + test_error |= test_bignum_modexp("04", "2", "-2", "-9", MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + + test_error |= test_bignum_modexp("01", "2", "0", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("04", "2", "0", "0", MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + test_error |= test_bignum_modexp("04", "2", "2", "0", MBEDTLS_ERR_MPI_BAD_INPUT_DATA); + test_error |= test_bignum_modexp("00", "0", "2", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "0", "0", "9", MBEDTLS_OK); + + test_error |= test_bignum_modexp("04", "-2", "2", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "-2", "0", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("07", "-2", "7", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("07", "-2", "1", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("02", "2", "1", "9", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "2", "0", "9", MBEDTLS_OK); + + test_error |= test_bignum_modexp("05", "5", "7", "7", MBEDTLS_OK); + test_error |= test_bignum_modexp("02", "-5", "7", "7", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "-5", "7", "3", MBEDTLS_OK); + + test_error |= test_bignum_modexp("00", "123456789", "123456789", "123456789", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "123456788", "123456788", "123456789", MBEDTLS_OK); + test_error |= test_bignum_modexp("01", "12345678A", "12345678A", "123456789", MBEDTLS_OK); + test_error |= test_bignum_modexp("06", "-32", "03E9", "07", MBEDTLS_OK); + + test_error |= test_bignum_modexp( + "5FA6CB1F76A157EC6CB02835DAD05B207DF883AB90CE4180277AB525801C9B5AF0C89B5C5E9DB2BB20CD0B86A308F006D19D4B1FBF355F2A8024B012A49ED483F5DF3FD77EFB40C221B6D67F28B5313F18728EFDF204C903A247A4A2CEE9542A15AD27F9EFB6AC0940D71BBCC4CB31B4D0372FAC26937A8CBA541503E0B8C80B", + "02", + "471CC5F6A82CCAB3B4FED79ADA3DEAF532166B1F0A5F2DA4CF364770CB883A85884C0E32029A256A3617251A1ACF4CB1E9FFD47A62699C3454444D193E2B0C1D3E6071E0D15C29A736E4EE76F6C63D3D157F8BE2CDFF05BD11C88A9691537869BF8DE4ED103E8EB9DB42B4E733F2D8FB1684435DC529B67305AB4A0FA9E42102", + "CF5CF5C38419A724957FF5DD323B9C45C3CDD261EB740F69AA94B8BB1A5C96409153BD76B24222D03274E4725A5406092E9E82E9135C643CAE98132B0D95F7D65347C68AFC1E677DA90E51BBAB5F5CF429C291B4BA39C6B2DC5E8C7231E46AA7728E87664532CDF547BE20C9A3FA8342BE6E34371A27C06F7DC0EDDDD2F86373", + MBEDTLS_OK); + + test_error |= test_bignum_modexp( + "368A32291EA9B22B11BE9D23636C4AB69C1936527AE70A33B8240556F886BD3E242A381FB752845287ED74EF2AE01E9736C374897A6DD910FC607769A66745368E683E34222DA242DD4C5B68A5B817C10F3BC207F9354188E70A90F64DF3A6156B7569069D459304EB85C9FEFA1D0AEDB1279B89633131980A05C37472837FBB", + "02", + "31FBCFDDC81A8696DA8A33798AF97F041D4C659AC82C0D5C608386013E84EBC3229E03B509F7FC81B2A2DE08659AC8699D109034B22D47A0C5D34DC6B90D3299AEE0F18E321E02FE53793857F73A0E3DC1D9A7CE3E0FC930185D67C02C08454EA5CB53285EB67C32C94C1E56DC34FBA796BC54898B4CDBF76A920E46C0AC270D", + "CF5CF5C38419A724957FF5DD323B9C45C3CDD261EB740F69AA94B8BB1A5C96409153BD76B24222D03274E4725A5406092E9E82E9135C643CAE98132B0D95F7D65347C68AFC1E677DA90E51BBAB5F5CF429C291B4BA39C6B2DC5E8C7231E46AA7728E87664532CDF547BE20C9A3FA8342BE6E34371A27C06F7DC0EDDDD2F86373", + MBEDTLS_OK); + + test_error |= test_bignum_modexp( + "631B2A9124600E91C030E6650284AE120CBAEFCB24083905F17B5B758A32CA071CA4BB109CDBA411F3136A009186E5E4F59A8DD481EDFD23FC95437749E971EB4C28E076FA3EC8E3E81A5A33E2B04598B987D7B11D74077E7B6F5C965A52558F5495D3FC518193132844E745D6C67C287C3239083A34DCB9F77F5348E0FC973D", + "BDAD66C8632D9E50A7EFDF9EC1465FFF445D45F1F2B57C8A18084E6743658BC45DCFD1F9F10ED6C2DDC0B14F1546D7DDE6624AE89722DB6B7BBE3AFBB26C5B2406E583834034C1F2162DD5BA5165B72C876EB7EE834268F1400F8FD8E88187B5264A452A5D795C6090CA02284E2E5CA136BED5AA05DAF5689136FA85F17D532D", + "471CC5F6A82CCAB3B4FED79ADA3DEAF532166B1F0A5F2DA4CF364770CB883A85884C0E32029A256A3617251A1ACF4CB1E9FFD47A62699C3454444D193E2B0C1D3E6071E0D15C29A736E4EE76F6C63D3D157F8BE2CDFF05BD11C88A9691537869BF8DE4ED103E8EB9DB42B4E733F2D8FB1684435DC529B67305AB4A0FA9E42102", + "CF5CF5C38419A724957FF5DD323B9C45C3CDD261EB740F69AA94B8BB1A5C96409153BD76B24222D03274E4725A5406092E9E82E9135C643CAE98132B0D95F7D65347C68AFC1E677DA90E51BBAB5F5CF429C291B4BA39C6B2DC5E8C7231E46AA7728E87664532CDF547BE20C9A3FA8342BE6E34371A27C06F7DC0EDDDD2F86373", + MBEDTLS_OK); + + TEST_ASSERT_FALSE_MESSAGE(test_error, "mbedtls_mpi_exp_mod incorrect for some tests\n"); +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_sha.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_sha.c new file mode 100644 index 000000000..68bc6a367 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_sha.c @@ -0,0 +1,612 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * mbedTLS SHA unit tests + */ +#include +#include +#include +#include +#include "mbedtls/sha1.h" +#include "mbedtls/sha256.h" +#include "mbedtls/sha512.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "sdkconfig.h" +#include "test_apb_dport_access.h" +#include "soc/soc_caps.h" +#include "test_utils.h" +#include "esp_memory_utils.h" + +TEST_CASE("mbedtls SHA self-tests", "[mbedtls]") +{ + start_apb_access_loop(); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_sha1_self_test(1), "SHA1 self-tests should pass."); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_sha256_self_test(1), "SHA256 self-tests should pass."); + TEST_ASSERT_FALSE_MESSAGE(mbedtls_sha512_self_test(1), "SHA512 self-tests should pass."); + verify_apb_access_loop(); +} + +static const unsigned char *one_hundred_as = (unsigned char *) + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + +static const unsigned char *one_hundred_bs = (unsigned char *) + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + +static const uint8_t sha256_thousand_as[32] = { + 0x41, 0xed, 0xec, 0xe4, 0x2d, 0x63, 0xe8, 0xd9, 0xbf, 0x51, 0x5a, 0x9b, 0xa6, 0x93, 0x2e, 0x1c, + 0x20, 0xcb, 0xc9, 0xf5, 0xa5, 0xd1, 0x34, 0x64, 0x5a, 0xdb, 0x5d, 0xb1, 0xb9, 0x73, 0x7e, 0xa3 +}; + + +static const uint8_t sha256_thousand_bs[32] = { + 0xf6, 0xf1, 0x18, 0xe1, 0x20, 0xe5, 0x2b, 0xe0, 0xbd, 0x0c, 0xfd, 0xf2, 0x79, 0x4c, 0xd1, 0x2c, 0x07, 0x68, 0x6c, 0xc8, 0x71, 0x23, 0x5a, 0xc2, 0xf1, 0x14, 0x59, 0x37, 0x8e, 0x6d, 0x23, 0x5b +}; + +static const uint8_t sha512_thousand_bs[64] = { + 0xa6, 0x68, 0x68, 0xa3, 0x73, 0x53, 0x2a, 0x5c, 0xc3, 0x3f, 0xbf, 0x43, 0x4e, 0xba, 0x10, 0x86, 0xb3, 0x87, 0x09, 0xe9, 0x14, 0x3f, 0xbf, 0x37, 0x67, 0x8d, 0x43, 0xd9, 0x9b, 0x95, 0x08, 0xd5, 0x80, 0x2d, 0xbe, 0x9d, 0xe9, 0x1a, 0x54, 0xab, 0x9e, 0xbc, 0x8a, 0x08, 0xa0, 0x1a, 0x89, 0xd8, 0x72, 0x68, 0xdf, 0x52, 0x69, 0x7f, 0x1c, 0x70, 0xda, 0xe8, 0x3f, 0xe5, 0xae, 0x5a, 0xfc, 0x9d +}; + +static const uint8_t sha384_thousand_bs[48] = { + 0x6d, 0xe5, 0xf5, 0x88, 0x57, 0x60, 0x83, 0xff, 0x7c, 0x94, 0x61, 0x5f, 0x8d, 0x96, 0xf2, 0x76, 0xd5, 0x3f, 0x77, 0x0c, 0x8e, 0xc1, 0xbf, 0xb6, 0x04, 0x27, 0xa4, 0xba, 0xea, 0x6c, 0x68, 0x44, 0xbd, 0xb0, 0x9c, 0xef, 0x6a, 0x09, 0x28, 0xe8, 0x1f, 0xfc, 0x95, 0x03, 0x69, 0x99, 0xab, 0x1a +}; + +static const uint8_t sha1_thousand_as[20] = { + 0x29, 0x1e, 0x9a, 0x6c, 0x66, 0x99, 0x49, 0x49, 0xb5, 0x7b, 0xa5, + 0xe6, 0x50, 0x36, 0x1e, 0x98, 0xfc, 0x36, 0xb1, 0xba +}; + + +TEST_CASE("mbedtls SHA interleaving", "[mbedtls]") +{ + mbedtls_sha1_context sha1_ctx; + mbedtls_sha256_context sha256_ctx; + mbedtls_sha512_context sha512_ctx; + unsigned char sha1[20], sha256[32], sha512[64]; + + mbedtls_sha1_init(&sha1_ctx); + mbedtls_sha256_init(&sha256_ctx); + mbedtls_sha512_init(&sha512_ctx); + + TEST_ASSERT_EQUAL(0, mbedtls_sha1_starts(&sha1_ctx)); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_starts(&sha512_ctx, false)); + + for (int i = 0; i < 10; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha1_update(&sha1_ctx, one_hundred_as, 100)); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, one_hundred_as, 100)); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&sha512_ctx, one_hundred_bs, 100)); + } + + TEST_ASSERT_EQUAL(0, mbedtls_sha1_finish(&sha1_ctx, sha1)); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&sha512_ctx, sha512)); + + mbedtls_sha1_free(&sha1_ctx); + mbedtls_sha256_free(&sha256_ctx); + mbedtls_sha512_free(&sha512_ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha512_thousand_bs, sha512, 64, "SHA512 calculation"); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_as, sha256, 32, "SHA256 calculation"); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha1_thousand_as, sha1, 20, "SHA1 calculation"); +} + +#define SHA_TASK_STACK_SIZE (10*1024) +static SemaphoreHandle_t done_sem; + +static void tskRunSHA1Test(void *pvParameters) +{ + mbedtls_sha1_context sha1_ctx; + unsigned char sha1[20]; + + for (int i = 0; i < 1000; i++) { + mbedtls_sha1_init(&sha1_ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha1_starts(&sha1_ctx)); + for (int j = 0; j < 10; j++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha1_update(&sha1_ctx, (unsigned char *)one_hundred_as, 100)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha1_finish(&sha1_ctx, sha1)); + mbedtls_sha1_free(&sha1_ctx); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha1_thousand_as, sha1, 20, "SHA1 calculation"); + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +static void tskRunSHA256Test(void *pvParameters) +{ + mbedtls_sha256_context sha256_ctx; + unsigned char sha256[32]; + + for (int i = 0; i < 1000; i++) { + mbedtls_sha256_init(&sha256_ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + for (int j = 0; j < 10; j++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, (unsigned char *)one_hundred_bs, 100)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + mbedtls_sha256_free(&sha256_ctx); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_bs, sha256, 32, "SHA256 calculation"); + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + + +TEST_CASE("mbedtls SHA multithreading", "[mbedtls]") +{ + done_sem = xSemaphoreCreateCounting(4, 0); + xTaskCreate(tskRunSHA1Test, "SHA1Task1", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + xTaskCreate(tskRunSHA1Test, "SHA1Task2", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + xTaskCreate(tskRunSHA256Test, "SHA256Task1", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + xTaskCreate(tskRunSHA256Test, "SHA256Task2", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + + for (int i = 0; i < 4; i++) { + if (!xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("done_sem not released by test task"); + } + } + vSemaphoreDelete(done_sem); +} + +void tskRunSHASelftests(void *param) +{ + for (int i = 0; i < 5; i++) { + if (mbedtls_sha1_self_test(1)) { + printf("SHA1 self-tests failed.\n"); + while (1) {} + } + + if (mbedtls_sha256_self_test(1)) { + printf("SHA256 self-tests failed.\n"); + while (1) {} + } + +#if SOC_SHA_SUPPORT_SHA512 + if (mbedtls_sha512_self_test(1)) { + printf("SHA512 self-tests failed.\n"); + while (1) {} + } + + if (mbedtls_sha512_self_test(1)) { + printf("SHA512 self-tests failed.\n"); + while (1) {} + } +#endif //SOC_SHA_SUPPORT_SHA512 + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +TEST_CASE("mbedtls SHA self-tests multithreaded", "[mbedtls]") +{ + done_sem = xSemaphoreCreateCounting(2, 0); + xTaskCreate(tskRunSHASelftests, "SHASelftests1", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + xTaskCreate(tskRunSHASelftests, "SHASelftests2", SHA_TASK_STACK_SIZE, NULL, 3, NULL); + + const int TIMEOUT_MS = 40000; + + for (int i = 0; i < 2; i++) { + if (!xSemaphoreTake(done_sem, TIMEOUT_MS / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("done_sem not released by test task"); + } + } + vSemaphoreDelete(done_sem); +} + +TEST_CASE("mbedtls SHA512 clone", "[mbedtls]") +{ + mbedtls_sha512_context ctx; + mbedtls_sha512_context clone; + unsigned char sha512[64]; + + mbedtls_sha512_init(&ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_starts(&ctx, false)); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&ctx, one_hundred_bs, 100)); + } + + mbedtls_sha512_init(&clone); + mbedtls_sha512_clone(&clone, &ctx); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&ctx, one_hundred_bs, 100)); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&clone, one_hundred_bs, 100)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&ctx, sha512)); + mbedtls_sha512_free(&ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha512_thousand_bs, sha512, 64, "SHA512 original calculation"); + + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&clone, sha512)); + mbedtls_sha512_free(&clone); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha512_thousand_bs, sha512, 64, "SHA512 cloned calculation"); +} + +TEST_CASE("mbedtls SHA384 clone", "[mbedtls][") +{ + mbedtls_sha512_context ctx; + mbedtls_sha512_context clone; + + unsigned char sha384[48]; + + mbedtls_sha512_init(&ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_starts(&ctx, true)); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&ctx, one_hundred_bs, 100)); + } + + mbedtls_sha512_init(&clone); + mbedtls_sha512_clone(&clone, &ctx); + + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&ctx, one_hundred_bs, 100)); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&clone, one_hundred_bs, 100)); + } +/* intended warning supression: is384 == true */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&ctx, sha384)); +#pragma GCC diagnostic pop + mbedtls_sha512_free(&ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha384_thousand_bs, sha384, 48, "SHA512 original calculation"); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&clone, sha384)); +#pragma GCC diagnostic pop + mbedtls_sha512_free(&clone); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha384_thousand_bs, sha384, 48, "SHA512 cloned calculation"); +} + + +TEST_CASE("mbedtls SHA256 clone", "[mbedtls]") +{ + mbedtls_sha256_context ctx; + mbedtls_sha256_context clone; + unsigned char sha256[64]; + + mbedtls_sha256_init(&ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&ctx, false)); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&ctx, one_hundred_as, 100)); + } + + mbedtls_sha256_init(&clone); + mbedtls_sha256_clone(&clone, &ctx); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&ctx, one_hundred_as, 100)); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&clone, one_hundred_as, 100)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&ctx, sha256)); + mbedtls_sha256_free(&ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_as, sha256, 32, "SHA256 original calculation"); + + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&clone, sha256)); + mbedtls_sha256_free(&clone); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_as, sha256, 32, "SHA256 cloned calculation"); +} + +typedef struct { + mbedtls_sha256_context ctx; + uint8_t result[32]; + int ret; + bool done; +} finalise_sha_param_t; + +static void tskFinaliseSha(void *v_param) +{ + finalise_sha_param_t *param = (finalise_sha_param_t *)v_param; + + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(¶m->ctx, one_hundred_as, 100)); + } + + param->ret = mbedtls_sha256_finish(¶m->ctx, param->result); + mbedtls_sha256_free(¶m->ctx); + + param->done = true; + vTaskDelete(NULL); +} + + +TEST_CASE("mbedtls SHA session passed between tasks", "[mbedtls]") +{ + finalise_sha_param_t param = { 0 }; + + mbedtls_sha256_init(¶m.ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(¶m.ctx, false)); + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(¶m.ctx, one_hundred_as, 100)); + } + + // pass the SHA context off to a different task + // + // note: at the moment this doesn't crash even if a mutex semaphore is used as the + // engine lock, but it can crash... + xTaskCreate(tskFinaliseSha, "SHAFinalise", SHA_TASK_STACK_SIZE, ¶m, 3, NULL); + + while (!param.done) { + vTaskDelay(1); + } + + TEST_ASSERT_EQUAL(0, param.ret); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_thousand_as, param.result, 32, "SHA256 result from other task"); +} + + + +/* Random input generated and hashed using python: + + import hashlib + import os, binascii + + input = bytearray(os.urandom(150)) + arr = '' + for idx, b in enumerate(input): + if idx % 8 == 0: + arr += '\n' + arr += "{}, ".format(hex(b)) + digest = hashlib.sha256(input).hexdigest() + +*/ +const uint8_t test_vector[] = { + 0xe4, 0x1a, 0x1a, 0x30, 0x71, 0xd3, 0x94, 0xb0, + 0xc3, 0x7e, 0x99, 0x9f, 0x1a, 0xde, 0x4a, 0x36, + 0xb1, 0x1, 0x81, 0x2b, 0x41, 0x91, 0x11, 0x7f, + 0xd8, 0xe1, 0xd5, 0xe5, 0x52, 0x6d, 0x92, 0xee, + 0x6c, 0xf7, 0x70, 0xea, 0x3a, 0xb, 0xc9, 0x97, + 0xc0, 0x12, 0x6f, 0x10, 0x5b, 0x90, 0xd8, 0x52, + 0x91, 0x69, 0xea, 0xc4, 0x1f, 0xc, 0xcf, 0xc6, + 0xf0, 0x43, 0xc6, 0xa3, 0x1f, 0x46, 0x3c, 0x3d, + 0x25, 0xe5, 0xa8, 0x27, 0x86, 0x85, 0x32, 0x3f, + 0x33, 0xd8, 0x40, 0xc4, 0x41, 0xf6, 0x4b, 0x12, + 0xd8, 0x5e, 0x4, 0x27, 0x42, 0x90, 0x73, 0x4, + 0x8, 0x42, 0xd1, 0x64, 0xd, 0x84, 0x3, 0x1, + 0x76, 0x88, 0xe4, 0x95, 0xdf, 0xe7, 0x62, 0xb4, + 0xb3, 0xb2, 0x7e, 0x6d, 0x78, 0xca, 0x79, 0x82, + 0xcc, 0xba, 0x22, 0xd2, 0x90, 0x2e, 0xe3, 0xa8, + 0x2a, 0x53, 0x3a, 0xb1, 0x9a, 0x7f, 0xb7, 0x8b, + 0xfa, 0x32, 0x47, 0xc1, 0x5c, 0x6, 0x4f, 0x7b, + 0xcd, 0xb3, 0xf4, 0xf1, 0xd0, 0xb5, 0xbf, 0xfb, + 0x7c, 0xc3, 0xa5, 0xb2, 0xc4, 0xd4, +}; + +const uint8_t test_vector_digest[] = { + 0xff, 0x1c, 0x60, 0xcb, 0x21, 0xf0, 0x63, 0x68, + 0xb9, 0xfc, 0xfe, 0xad, 0x3e, 0xb0, 0x2e, 0xd1, + 0xf9, 0x08, 0x82, 0x82, 0x83, 0x06, 0xc1, 0x8a, + 0x98, 0x5d, 0x36, 0xc0, 0xb7, 0xeb, 0x35, 0xe0, +}; + + +TEST_CASE("mbedtls SHA, input in flash", "[mbedtls]") +{ + mbedtls_sha256_context sha256_ctx; + unsigned char sha256[32]; + + mbedtls_sha256_init(&sha256_ctx); + + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, test_vector, sizeof(test_vector))); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + mbedtls_sha256_free(&sha256_ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(test_vector_digest, sha256, 32, "SHA256 calculation"); +} + +/* Function are not implemented in SW */ +#if CONFIG_MBEDTLS_HARDWARE_SHA && SOC_SHA_SUPPORT_SHA512_T + +/* + * FIPS-180-2 test vectors + */ +static unsigned char sha512T_test_buf[2][113] = { + { "abc" }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" + } +}; + +static const size_t sha512T_test_buflen[2] = { + 3, 112 +}; + +static const esp_sha_type sha512T_algo[4] = { + SHA2_512224, SHA2_512256, SHA2_512T, SHA2_512T +}; + +static const size_t sha512T_t_len[4] = { 224, 256, 224, 256 }; + +static const unsigned char sha512_test_sum[4][32] = { + /* SHA512-224 */ + { + 0x46, 0x34, 0x27, 0x0f, 0x70, 0x7b, 0x6a, 0x54, + 0xda, 0xae, 0x75, 0x30, 0x46, 0x08, 0x42, 0xe2, + 0x0e, 0x37, 0xed, 0x26, 0x5c, 0xee, 0xe9, 0xa4, + 0x3e, 0x89, 0x24, 0xaa + }, + { + 0x23, 0xfe, 0xc5, 0xbb, 0x94, 0xd6, 0x0b, 0x23, + 0x30, 0x81, 0x92, 0x64, 0x0b, 0x0c, 0x45, 0x33, + 0x35, 0xd6, 0x64, 0x73, 0x4f, 0xe4, 0x0e, 0x72, + 0x68, 0x67, 0x4a, 0xf9 + }, + + /* SHA512-256 */ + { + 0x53, 0x04, 0x8e, 0x26, 0x81, 0x94, 0x1e, 0xf9, + 0x9b, 0x2e, 0x29, 0xb7, 0x6b, 0x4c, 0x7d, 0xab, + 0xe4, 0xc2, 0xd0, 0xc6, 0x34, 0xfc, 0x6d, 0x46, + 0xe0, 0xe2, 0xf1, 0x31, 0x07, 0xe7, 0xaf, 0x23 + }, + { + 0x39, 0x28, 0xe1, 0x84, 0xfb, 0x86, 0x90, 0xf8, + 0x40, 0xda, 0x39, 0x88, 0x12, 0x1d, 0x31, 0xbe, + 0x65, 0xcb, 0x9d, 0x3e, 0xf8, 0x3e, 0xe6, 0x14, + 0x6f, 0xea, 0xc8, 0x61, 0xe1, 0x9b, 0x56, 0x3a + } + + /* For SHA512_T testing we use t=224 & t=256 + * so the hash digest should be same as above + */ +}; + +/* This will run total of 8 test cases, 2 for each of the below MODE + * SHA512/224, SHA512/256, SHA512/t with t=224 & SHA512/t with t=256 + * + * Test is disabled for ESP32 as there is no hardware for SHA512/t + */ +TEST_CASE("mbedtls SHA512/t", "[mbedtls]") +{ + mbedtls_sha512_context sha512_ctx; + unsigned char sha512[64], k; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 2; j++) { + k = i * 2 + j; + mbedtls_sha512_init(&sha512_ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_starts(&sha512_ctx, false)); + esp_sha512_set_mode(&sha512_ctx, sha512T_algo[i]); + if (i > 1) { + k = (i - 2) * 2 + j; + esp_sha512_set_t(&sha512_ctx, sha512T_t_len[i]); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha512_update(&sha512_ctx, sha512T_test_buf[j], sha512T_test_buflen[j])); + TEST_ASSERT_EQUAL(0, mbedtls_sha512_finish(&sha512_ctx, sha512)); + mbedtls_sha512_free(&sha512_ctx); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha512_test_sum[k], sha512, sha512T_t_len[i] / 8, "SHA512t calculation"); + } + } +} +#endif //CONFIG_MBEDTLS_HARDWARE_SHA + +#ifdef CONFIG_SPIRAM_USE_MALLOC +#include "test_mbedtls_utils.h" +TEST_CASE("mbedtls SHA256 PSRAM DMA", "[mbedtls]") +{ + const unsigned CALLS = 256; + const unsigned CALL_SZ = 16 * 1024; + mbedtls_sha256_context sha256_ctx; + unsigned char sha256[32]; + + // allocate external memory + uint8_t *buf = heap_caps_malloc(CALL_SZ, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + TEST_ASSERT(esp_ptr_external_ram(buf)); + memset(buf, 0x54, CALL_SZ); + + mbedtls_sha256_init(&sha256_ctx); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + for (int c = 0; c < CALLS; c++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, buf, CALL_SZ)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + + free(buf); + mbedtls_sha256_free(&sha256_ctx); + + /* Check the result. Reference value can be calculated using: + * dd if=/dev/zero bs=$((16*1024)) count=256 | tr '\000' '\124' | sha256sum + */ + const char *expected_hash = "8d031167bd706ac337e07aa9129c34ae4ae792d0a79a2c70e7f012102e8adc3d"; + char hash_str[sizeof(sha256) * 2 + 1]; + utils_bin2hex(hash_str, sizeof(hash_str), sha256, sizeof(sha256)); + + TEST_ASSERT_EQUAL_STRING(expected_hash, hash_str); + +} + +#if SOC_SHA_SUPPORT_DMA +TEST_CASE("mbedtls SHA256 PSRAM DMA large buffer", "[hw_crypto]") +{ + mbedtls_sha256_context sha256_ctx; + unsigned char sha256[32]; + + const size_t SZ = 257984; // specific size to cover issue in https://github.com/espressif/esp-idf/issues/11915 + void *buffer = heap_caps_malloc(SZ, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(buffer); + memset(buffer, 0x55, SZ); + + mbedtls_sha256_init(&sha256_ctx); + int r = mbedtls_sha256_starts(&sha256_ctx, false); + TEST_ASSERT_EQUAL(0, r); + r = mbedtls_sha256_update(&sha256_ctx, buffer, SZ); + TEST_ASSERT_EQUAL(0, r); + r = mbedtls_sha256_finish(&sha256_ctx, sha256); + TEST_ASSERT_EQUAL(0, r); + mbedtls_sha256_free(&sha256_ctx); + free(buffer); + + /* Check the result. Reference value can be calculated using: + * dd if=/dev/zero bs=257984 count=1 | tr '\000' '\125' | sha256sum + */ + const char *expected_hash = "f2330c9f81ff1c8f0515247faa82be8b6f9685601de6f5dae79172766f136c33"; + + char hash_str[sizeof(sha256) * 2 + 1]; + utils_bin2hex(hash_str, sizeof(hash_str), sha256, sizeof(sha256)); + + TEST_ASSERT_EQUAL_STRING(expected_hash, hash_str); +} +#endif // SOC_SHA_SUPPORT_DMA + +#endif //CONFIG_SPIRAM_USE_MALLOC + +#if CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK + +TEST_CASE("mbedtls SHA stack in RTC RAM", "[mbedtls]") +{ + done_sem = xSemaphoreCreateBinary(); + static StaticTask_t rtc_task; + size_t STACK_SIZE = 3072; + uint8_t *rtc_stack = heap_caps_calloc(STACK_SIZE, 1, MALLOC_CAP_RTCRAM); + + TEST_ASSERT(esp_ptr_in_rtc_dram_fast(rtc_stack)); + + TEST_ASSERT_NOT_NULL(xTaskCreateStatic(tskRunSHA256Test, "tskRunSHA256Test_task", STACK_SIZE, NULL, + 3, rtc_stack, &rtc_task)); + TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)); + + /* Give task time to cleanup before freeing stack */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + free(rtc_stack); + + vSemaphoreDelete(done_sem); +} + +#endif //CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK + +#if CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY && CONFIG_SPIRAM_USE_MALLOC + +TEST_CASE("mbedtls SHA stack in PSRAM", "[mbedtls]") +{ + done_sem = xSemaphoreCreateBinary(); + static StaticTask_t psram_task; + size_t STACK_SIZE = 3072; + uint8_t *psram_stack = heap_caps_calloc(STACK_SIZE, 1, MALLOC_CAP_SPIRAM); + + TEST_ASSERT(esp_ptr_external_ram(psram_stack)); + + TEST_ASSERT_NOT_NULL(xTaskCreateStatic(tskRunSHA256Test, "tskRunSHA256Test_task", STACK_SIZE, NULL, + 3, psram_stack, &psram_task)); + TEST_ASSERT_TRUE(xSemaphoreTake(done_sem, 10000 / portTICK_PERIOD_MS)); + + /* Give task time to cleanup before freeing stack */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + free(psram_stack); + + vSemaphoreDelete(done_sem); +} + +#endif //CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY && CONFIG_SPIRAM_USE_MALLOC diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.c new file mode 100644 index 000000000..a2db38918 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.c @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "test_mbedtls_utils.h" + +static inline char to_hex_digit(unsigned val) +{ + return (val < 10) ? ('0' + val) : ('a' + val - 10); +} + + +void utils_bin2hex(char *const hex, const size_t hex_maxlen, const unsigned char *const bin, const size_t bin_len) +{ + assert(bin_len < SIZE_MAX / 2); + assert(hex_maxlen > bin_len * 2U); + assert(hex); + assert(bin); + + int i; + for (i = 0; i < bin_len; i++) { + hex[2*i] = to_hex_digit(bin[i] >> 4); + hex[2*i + 1] = to_hex_digit(bin[i] & 0xf); + } + hex[i * 2U] = 0U; +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.h b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.h new file mode 100644 index 000000000..8d2b73c08 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_mbedtls_utils.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +void utils_bin2hex(char *const hex, const size_t hex_maxlen, const unsigned char *const bin, const size_t bin_len); diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_rsa.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_rsa.c new file mode 100644 index 000000000..f69a0238f --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_rsa.c @@ -0,0 +1,603 @@ +/* mbedTLS RSA functionality tests + * + * Focus on testing functionality where we use ESP32 hardware + * accelerated crypto features + * + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_system.h" +#include "esp_task_wdt.h" +#include "mbedtls/rsa.h" +#include "mbedtls/pk.h" +#include "mbedtls/x509_crt.h" +#include +#include +#include "entropy_poll.h" +#include "freertos/FreeRTOS.h" +#include "unity.h" +#include "test_utils.h" +#include "memory_checks.h" +#include "ccomp_timer.h" + +#define PRINT_DEBUG_INFO + +/* Taken from openssl s_client -connect api.gigafive.com:443 -showcerts + */ +static const char *rsa4096_cert = "-----BEGIN CERTIFICATE-----\n"\ + "MIIExzCCA6+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCVVMx\n"\ + "CzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtTYW50YSBDbGFyYTElMCMGA1UECgwcR2ln\n"\ + "YWZpdmUgVGVjaG5vbG9neSBQYXJ0bmVyczEZMBcGA1UEAwwQR2lnYWZpdmUgUm9v\n"\ + "dCBDQTEeMBwGCSqGSIb3DQEJARYPY2FAZ2lnYWZpdmUuY29tMB4XDTE2MDgyNzE2\n"\ + "NDYyM1oXDTI2MDgyNTE2NDYyM1owgZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD\n"\ + "QTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExKTAnBgNVBAoMIEdpZ2FmaXZlIFRlY2hu\n"\ + "b2xvZ3kgUGFydG5lcnMgTExDMRkwFwYDVQQDDBBhcGkuZ2lnYWZpdmUuY29tMR8w\n"\ + "HQYJKoZIhvcNAQkBFhBjcmxAZ2lnYWZpdmUuY29tMIICIjANBgkqhkiG9w0BAQEF\n"\ + "AAOCAg8AMIICCgKCAgEAof82VrEpXMpsI/ddW6RLeTeSYtxiXZZkRbDKN6otYgEk\n"\ + "vA8yRbzei2cO2A/8+Erhe9beYLAMXWF+bjoUAFwnuIcbmufgHprOYzX/7CYXCsrH\n"\ + "LrJfVF6kvjCXy2W3xSvgh8ZgHNWnBGzl13tq19Fz8x0AhK5GQ9608oJCbnQjpVSI\n"\ + "lZDl3JVOifCeXf2c7nMhVOC/reTeto0Gbchs8Ox50WyojmfYbVjOQcA7f8p1eI+D\n"\ + "XUJK01cUGVu6/KarVArGHh5LsiyXOadbyeyOXPmjyrgarG3IIBeQSNECfJZPc/OW\n"\ + "lFszjU4YLDckI4x+tReiuFQbQPN5sDplcEldmZZm/8XD36ddvAaDds+SYlPXxDK7\n"\ + "7L8RBVUG2Ylc9YZf7RE6IMDmdQmsCZDX0VxySYEmzv5lnAx4mzzaXcgS+kHMOLyK\n"\ + "n9UxmpzwQoqqC9tMZqwRaeKW1njR1dSwQLqirBPfGCWKkpkpm7C3HEfeeLrasral\n"\ + "aPf6LAwN3A4ZKHa5Jmne7W+1eYS1aTXOAOLIPcXRAh1B80H+SusIdM9d6vk2YTIg\n"\ + "khwGQV3sgM6nIO5+T/8z141UEjWbtP7pb/u0+G9Cg7TwvRoO2UukxdvOwNto1G2e\n"\ + "J3rKB/JSYsYWnPHvvh9XR+55PZ4iCf9Rqw/IP82uyGipR9gxlHqN8WhMTj9tNEkC\n"\ + "AwEAAaMhMB8wHQYDVR0OBBYEFISCemcSriz1HFhRXluw9H+Bv9lEMA0GCSqGSIb3\n"\ + "DQEBCwUAA4IBAQCMetK0xe6Y/uZpb1ARh+hHYcHI3xI+IG4opWJeoB1gDh/xpNAW\n"\ + "j6t5MGbLoqNMBXbqL26hnKVspyvCxw7ebI5ZJgjtbrD1t+0D8yrgIZzr7AWGA9Hj\n"\ + "WIHqDHGDxwkmfjVVPmuO3l5RtJmL6KV6kVL2bOvVI6gECpFLddmOTtg+iXDfSw3x\n"\ + "0+ueMYKr8QLF+TCxfzQTHvTHvOJtcZHecc1n7PYbRmI2p7tV6RoBpV69oM6NAVUV\n"\ + "i2QoSxm0pYzDzavOaxwhEPHT34Tpg6fwXy1QokFD9OtxRFtdpTjL3bMWpatZE+ba\n"\ + "cjvvf0utMW5fNjTTxu1nnpuxZM3ifTCqZJ+9\n"\ + "-----END CERTIFICATE-----\n"; + +static const char *rsa3072_cert = "-----BEGIN CERTIFICATE-----\n"\ + "MIIEszCCAxugAwIBAgIUNTBsyv59/rRarOVm3KBA29zqEtUwDQYJKoZIhvcNAQEL\n"\ + "BQAwaTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYDVQQHDAhT\n"\ + "aGFuZ2hhaTESMBAGA1UECgwJRXNwcmVzc2lmMQwwCgYDVQQLDANJREYxEjAQBgNV\n"\ + "BAMMCWVzcHJlc3NpZjAeFw0yMDA3MTQwODQ5NDdaFw0yMTA3MTQwODQ5NDdaMGkx\n"\ + "CzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdo\n"\ + "YWkxEjAQBgNVBAoMCUVzcHJlc3NpZjEMMAoGA1UECwwDSURGMRIwEAYDVQQDDAll\n"\ + "c3ByZXNzaWYwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDMj3ZwPd2y\n"\ + "+UxzmMUdZC5I5JQIzvUmHRNJWUe99Vht/rIEQuNSGg7xjyvuZoyeFo+Yg+QYUICa\n"\ + "Ipe4y2bZS12QsTxUmeoEhYORDSeQXFEo4aUmWuKIs6Y41dBOL7eDYDL3FRmIgmcn\n"\ + "qMonyCrSzXlcgHOVtMd8U8ifkX5u+nTigQLSIHVeAFz8CvC0tIiPm9YFurtMN15p\n"\ + "P1K/AH17ljtwVqacrI/asZgX+ECY5rauNJLigEYgfr7+xV6GofaXp6rUpGgWbVxM\n"\ + "hqKe/dbDuIzte3VK+zRDNDCeE5gPQjgoSDblOVmPemrq7KKjZ/PKmP47ct5a/0Ov\n"\ + "zWcdCgaXDRoPiwbpmz3Z6uh3JdvsDf214svLK+z4EDIRzpvggM0pfDvOADatiPkr\n"\ + "KmnFD1ZZx3R29/7IZ5OVvQL1hgWbm3cL4JADOc8PQKcqCzBE9JDdAVoa228ESaJ/\n"\ + "n4b63qaqfgBnoaFzCEruEcXj5nuXBxlk19WWtgY1tZtAgoA8hTWxxH0CAwEAAaNT\n"\ + "MFEwHQYDVR0OBBYEFPlwrvgkde/r+F8VRMMtpDUIxAtgMB8GA1UdIwQYMBaAFPlw\n"\ + "rvgkde/r+F8VRMMtpDUIxAtgMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\n"\ + "BQADggGBAH9nBaEP+FWyaZnmxCblKhs8eIEYXzjxbnRUPo5b3uL/PAv1XD1kEUwY\n"\ + "GWnJ7Z5HOSCdVMgo1opmKGLWuiVP6Vlt9QuA/tWh0bGScL4QfriPXuA7aXAcLbW/\n"\ + "BqHNJ9Z+H2Fq09XktkZE4Nfnv3iTMMqfNCchM3t3iWZRf2sRVYIdd5OjhM+CLLUK\n"\ + "kYNiseAgbcBX0/kqTdHlC6OS8Mcu9btJ/663DZy8tndf+PH+EB6fexQd9T31jWoj\n"\ + "OkEkJ4vDRZP+0LceK7kNcMOcLx8DnF9LwUyHQitW7NMFServoTfxy8A0yep7nIOH\n"\ + "M/ndECzirQ6WkR9jMG3cw0Jm5mZvA9IAvnLhUO45AyZGC8mShJ0AaXtqejqPg9ng\n"\ + "//5VIpzoqwVkrMYlMA7ZrccQiRsd2nlBHr+64PRwRCp7y5FOxIzhGzsJibXUpO/V\n"\ + "FNwuPz+VcnPvJE7r4gB1oRViiGYojMDQV3G+jbgvpTHKUKP6zzavSAKs+FlfEAmh\n"\ + "EtmuT/beDA==\n"\ + "-----END CERTIFICATE-----\n"; + +/* Root cert from openssl s_client -connect google.com:443 -showcerts + */ +static const char *rsa2048_cert = "-----BEGIN CERTIFICATE-----\n"\ + "MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\n"\ + "MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\n"\ + "aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw\n"\ + "WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE\n"\ + "AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\ + "CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m\n"\ + "OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu\n"\ + "T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c\n"\ + "JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR\n"\ + "Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz\n"\ + "PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm\n"\ + "aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM\n"\ + "TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g\n"\ + "LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO\n"\ + "BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv\n"\ + "dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB\n"\ + "AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL\n"\ + "NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W\n"\ + "b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S\n"\ + "-----END CERTIFICATE-----\n"; + + +/* Some random input bytes to public key encrypt */ +static const uint8_t pki_input[4096/8] = { + 0, 1, 4, 6, 7, 9, 33, 103, 49, 11, 56, 211, 67, 92 }; + +/* Result of an RSA4096 operation using cert's public key + (raw PKI, no padding/etc) */ +static const uint8_t pki_rsa4096_output[] = { + 0x91, 0x87, 0xcd, 0x04, 0x80, 0x7c, 0x8b, 0x0b, + 0x0c, 0xc0, 0x38, 0x37, 0x7a, 0xe3, 0x2c, 0x94, + 0xea, 0xc4, 0xcb, 0x83, 0x2c, 0x77, 0x71, 0x14, + 0x11, 0x85, 0x16, 0x61, 0xd3, 0x64, 0x2a, 0x0f, + 0xf9, 0x6b, 0x45, 0x04, 0x66, 0x5d, 0x15, 0xf1, + 0xcf, 0x69, 0x77, 0x90, 0xb9, 0x41, 0x68, 0xa9, + 0xa6, 0xfd, 0x94, 0xdc, 0x6a, 0xce, 0xc7, 0xb6, + 0x41, 0xd9, 0x44, 0x3c, 0x02, 0xb6, 0xc7, 0x26, + 0xce, 0xec, 0x66, 0x21, 0xa8, 0xe8, 0xf4, 0xa9, + 0x33, 0x4a, 0x6c, 0x28, 0x0f, 0x50, 0x30, 0x32, + 0x28, 0x00, 0xbb, 0x2c, 0xc3, 0x44, 0x72, 0x31, + 0x93, 0xd4, 0xde, 0x29, 0x6b, 0xfa, 0x31, 0xfd, + 0x3a, 0x05, 0xc6, 0xb1, 0x28, 0x43, 0x57, 0x20, + 0xf7, 0xf8, 0x13, 0x0c, 0x4a, 0x80, 0x00, 0xab, + 0x1f, 0xe8, 0x88, 0xad, 0x56, 0xf2, 0xda, 0x5a, + 0x50, 0xe9, 0x02, 0x09, 0x21, 0x2a, 0xfc, 0x82, + 0x68, 0x34, 0xf9, 0x04, 0xa3, 0x25, 0xe1, 0x0f, + 0xa8, 0x77, 0x29, 0x94, 0xb6, 0x9d, 0x5a, 0x08, + 0x33, 0x8d, 0x27, 0x6a, 0xc0, 0x3b, 0xad, 0x91, + 0x8a, 0x83, 0xa9, 0x2e, 0x48, 0xcd, 0x67, 0xa3, + 0x3a, 0x35, 0x41, 0x85, 0xfa, 0x3f, 0x61, 0x1f, + 0x80, 0xeb, 0xcd, 0x5a, 0xc5, 0x14, 0x7b, 0xab, + 0x9c, 0x45, 0x11, 0xd2, 0x25, 0x9a, 0x16, 0xeb, + 0x9c, 0xfa, 0xbe, 0x73, 0x18, 0xbd, 0x25, 0x8e, + 0x99, 0x6d, 0xb3, 0xbc, 0xac, 0x2d, 0xa2, 0x53, + 0xe8, 0x7c, 0x38, 0x1b, 0x7a, 0x75, 0xff, 0x76, + 0x4f, 0x48, 0x5b, 0x39, 0x20, 0x5a, 0x7b, 0x82, + 0xd3, 0x33, 0x33, 0x2a, 0xab, 0x6a, 0x7a, 0x42, + 0x1d, 0x1f, 0xd1, 0x61, 0x58, 0xd7, 0x38, 0x52, + 0xdf, 0xb0, 0x61, 0x98, 0x63, 0xb7, 0xa1, 0x4e, + 0xdb, 0x9b, 0xcb, 0xb7, 0x85, 0xc4, 0x3e, 0x03, + 0xe5, 0x59, 0x50, 0x28, 0x5a, 0x4d, 0x7f, 0x53, + 0x2e, 0x99, 0x1d, 0x6d, 0x85, 0x27, 0x78, 0x34, + 0x5e, 0xae, 0xc9, 0x1b, 0x37, 0x96, 0xde, 0x40, + 0x87, 0x35, 0x3c, 0x1f, 0xe0, 0x8f, 0xfb, 0x3a, + 0x58, 0x0e, 0x60, 0xe9, 0x06, 0xbd, 0x83, 0x03, + 0x92, 0xde, 0x5e, 0x69, 0x28, 0xb1, 0x00, 0xeb, + 0x44, 0xca, 0x3c, 0x49, 0x03, 0x10, 0xa8, 0x84, + 0xa6, 0xbb, 0xd5, 0xda, 0x98, 0x8c, 0x6f, 0xa3, + 0x0f, 0x39, 0xf3, 0xa7, 0x7d, 0xd5, 0x3b, 0xe2, + 0x85, 0x12, 0xda, 0xa4, 0x4d, 0x80, 0x97, 0xcb, + 0x11, 0xe0, 0x89, 0x90, 0xff, 0x5b, 0x72, 0x19, + 0x59, 0xd1, 0x39, 0x23, 0x9f, 0xb0, 0x00, 0xe2, + 0x45, 0x72, 0xc6, 0x9a, 0xbc, 0xe1, 0xd1, 0x51, + 0x6b, 0x35, 0xd2, 0x49, 0xbf, 0xb6, 0xfe, 0xab, + 0x09, 0xf7, 0x9d, 0xa4, 0x6e, 0x69, 0xb6, 0xf9, + 0xde, 0xe3, 0x57, 0x0c, 0x1a, 0x96, 0xf1, 0xcc, + 0x1c, 0x92, 0xdb, 0x44, 0xf4, 0x45, 0xfa, 0x8f, + 0x87, 0xcf, 0xf4, 0xd2, 0xa1, 0xf8, 0x69, 0x18, + 0xcf, 0xdc, 0xa0, 0x1f, 0xb0, 0x26, 0xad, 0x81, + 0xab, 0xdf, 0x78, 0x18, 0xa2, 0x74, 0xba, 0x2f, + 0xec, 0x70, 0xa2, 0x1f, 0x56, 0xee, 0xff, 0xc9, + 0xfe, 0xb1, 0xe1, 0x9b, 0xea, 0x0e, 0x33, 0x14, + 0x5f, 0x6e, 0xca, 0xee, 0x02, 0x56, 0x5a, 0x67, + 0x42, 0x9a, 0xbf, 0x55, 0xc0, 0x0f, 0x8e, 0x01, + 0x67, 0x63, 0x6e, 0xd1, 0x57, 0xf7, 0xf1, 0xc6, + 0x92, 0x9e, 0xb5, 0x45, 0xe1, 0x50, 0x58, 0x94, + 0x20, 0x90, 0x6a, 0x29, 0x2d, 0x4b, 0xd1, 0xb5, + 0x68, 0x63, 0xb5, 0xe6, 0xd8, 0x6e, 0x84, 0x80, + 0xad, 0xe6, 0x03, 0x1e, 0x51, 0xc2, 0xa8, 0x6d, + 0x84, 0xec, 0x2d, 0x7c, 0x61, 0x02, 0xd1, 0xda, + 0xf5, 0x94, 0xfa, 0x2d, 0xa6, 0xed, 0x89, 0x6a, + 0x6a, 0xda, 0x07, 0x5d, 0x83, 0xfc, 0x43, 0x76, + 0x7c, 0xca, 0x8c, 0x00, 0xfc, 0xb9, 0x2c, 0x23, +}; + +static const uint8_t pki_rsa3072_output[] = { + 0x86, 0xc0, 0xe4, 0xa5, 0x4b, 0x45, 0xe4, 0xd4, 0x0f, 0xb7, 0xe3, 0x10, 0x4f, 0xea, 0x88, 0x91, + 0x3d, 0xad, 0x43, 0x86, 0x90, 0xf0, 0xd8, 0xf0, 0x29, 0x21, 0xc7, 0x5c, 0x75, 0x49, 0x91, 0xce, + 0xf8, 0x34, 0x91, 0xbd, 0x89, 0x61, 0xcf, 0x47, 0x0e, 0x4d, 0x3f, 0x29, 0xd1, 0x02, 0xa7, 0xa8, + 0x8f, 0x6a, 0xda, 0x1a, 0xf2, 0xf1, 0x18, 0x92, 0x35, 0xf6, 0x0c, 0x07, 0x5a, 0x84, 0xfa, 0x65, + 0xd3, 0x02, 0xe0, 0x53, 0x17, 0x5d, 0xf7, 0x45, 0x26, 0xcc, 0xf9, 0x26, 0xf5, 0x6a, 0x66, 0xbb, + 0xef, 0x33, 0xcb, 0x03, 0x6e, 0x6a, 0x93, 0x6c, 0x2a, 0x27, 0xa7, 0xf7, 0x2c, 0xdc, 0x00, 0xdd, + 0x98, 0x52, 0xfb, 0xce, 0x31, 0xe2, 0x96, 0x20, 0x98, 0x0a, 0xf4, 0x19, 0x0f, 0xbf, 0x22, 0xed, + 0x37, 0xb2, 0x14, 0x10, 0x88, 0xa3, 0x6a, 0x43, 0x26, 0xb8, 0x54, 0xf1, 0xb8, 0xc6, 0x56, 0xb7, + 0x89, 0x34, 0xc0, 0xba, 0xae, 0x38, 0x35, 0x2c, 0x13, 0x57, 0x7a, 0xa4, 0x4b, 0xf2, 0x21, 0x82, + 0xf4, 0xea, 0x1a, 0x2c, 0xd8, 0x32, 0xe8, 0x5f, 0x37, 0x04, 0x52, 0x3d, 0xff, 0xc2, 0x85, 0x00, + 0xd2, 0x8d, 0x84, 0x36, 0x61, 0x61, 0x7b, 0xea, 0x7c, 0x3d, 0xeb, 0x51, 0xea, 0xf2, 0x67, 0xc9, + 0xb8, 0xa6, 0x98, 0x54, 0x3f, 0x5b, 0x8f, 0x1a, 0x8a, 0x93, 0x81, 0x05, 0xa3, 0x15, 0xf8, 0x54, + 0x8f, 0x75, 0xe2, 0x01, 0xc3, 0x47, 0xc3, 0x8f, 0xc7, 0x6d, 0x04, 0xbc, 0x05, 0x88, 0xd9, 0x62, + 0xcc, 0x14, 0xea, 0x30, 0x68, 0x73, 0xd5, 0xe5, 0x53, 0x7c, 0xb1, 0xa0, 0xe5, 0x6c, 0xd0, 0xa3, + 0x07, 0x2a, 0x5e, 0x2a, 0x0f, 0x89, 0x39, 0xea, 0xf9, 0xf5, 0xfb, 0x3b, 0xee, 0x66, 0xd9, 0xd4, + 0x04, 0x2d, 0x1b, 0xc9, 0xc2, 0x37, 0xc8, 0xa8, 0x71, 0xea, 0xa8, 0xf6, 0xe6, 0xc1, 0xdc, 0x5b, + 0x70, 0x68, 0x89, 0xa5, 0x69, 0xc0, 0x7f, 0x15, 0x8b, 0x6d, 0xc6, 0x88, 0x41, 0x8b, 0x25, 0x8f, + 0x2f, 0x5c, 0x81, 0x94, 0x1b, 0x8c, 0x52, 0x3f, 0xe5, 0x97, 0x6d, 0x4a, 0xc6, 0x42, 0x35, 0x0e, + 0x59, 0xce, 0x00, 0x3c, 0x2b, 0x0f, 0x5a, 0xc5, 0x1b, 0x01, 0xf3, 0x02, 0x70, 0xb1, 0x88, 0xda, + 0x7b, 0x5b, 0x4d, 0x3e, 0xd1, 0x15, 0x57, 0xc8, 0x39, 0x14, 0xff, 0x8d, 0x2b, 0x12, 0xf5, 0x5b, + 0xaf, 0x78, 0x2e, 0x0b, 0xcd, 0x27, 0x83, 0xdb, 0x4e, 0xe1, 0x5d, 0xa5, 0xbd, 0xfe, 0x2b, 0x6e, + 0x8b, 0x54, 0x7d, 0x14, 0x6f, 0x4d, 0xe1, 0x14, 0xc8, 0x30, 0x0e, 0x10, 0x23, 0x2a, 0xe1, 0xe5, + 0xee, 0xa3, 0x69, 0x8d, 0xe2, 0x9a, 0xed, 0x0c, 0x23, 0x16, 0x8e, 0x95, 0xae, 0x1a, 0xa2, 0x28, + 0x61, 0x25, 0xa2, 0x15, 0x74, 0xc4, 0xec, 0x6b, 0x73, 0xb2, 0x8c, 0xd2, 0x64, 0xfd, 0x2b, 0x92, +}; + +static const uint8_t pki_rsa2048_output[] = { + 0x47, 0x0b, 0xe5, 0x8a, 0xcd, 0x2f, 0x78, 0x07, + 0x69, 0x69, 0x70, 0xff, 0x81, 0xdf, 0x96, 0xf0, + 0xed, 0x82, 0x3a, 0x3d, 0x46, 0xab, 0xe9, 0xc3, + 0xb5, 0xd9, 0xca, 0xa2, 0x05, 0xa9, 0xf6, 0x6e, + 0xad, 0x6c, 0xe0, 0xd1, 0xa2, 0xb4, 0xf2, 0x78, + 0x4a, 0x93, 0xfc, 0x45, 0xe1, 0x9b, 0xdd, 0x62, + 0xf9, 0x66, 0x2a, 0x14, 0x38, 0x12, 0xb6, 0x50, + 0x0b, 0xe3, 0x53, 0x9c, 0x12, 0x56, 0xf1, 0xb7, + 0x83, 0xd5, 0xf3, 0x24, 0x81, 0xcc, 0x5a, 0xeb, + 0xec, 0xac, 0x68, 0xa8, 0x0c, 0xd7, 0x84, 0x7a, + 0xbb, 0x77, 0x7b, 0xd5, 0x5b, 0xcf, 0x7b, 0x25, + 0xd0, 0x75, 0x80, 0x21, 0x12, 0x97, 0x6b, 0xe1, + 0xb6, 0x51, 0x12, 0x52, 0x6e, 0x01, 0x92, 0xb7, + 0xcc, 0x70, 0x4b, 0x46, 0x11, 0x98, 0x5a, 0x84, + 0x1c, 0x90, 0x45, 0x0f, 0x15, 0x77, 0xdb, 0x79, + 0xe8, 0xff, 0x1f, 0xaa, 0x58, 0x95, 0xce, 0x3c, + 0x65, 0x0c, 0x66, 0x29, 0xe1, 0x9c, 0x41, 0xbb, + 0xde, 0x65, 0xb8, 0x29, 0x36, 0x94, 0xbd, 0x87, + 0x93, 0x39, 0xc5, 0xeb, 0x49, 0x21, 0xc1, 0xeb, + 0x48, 0xbd, 0x19, 0x13, 0x4d, 0x40, 0x90, 0x88, + 0xc6, 0x12, 0xd9, 0xf7, 0xdd, 0xc8, 0x4f, 0x89, + 0xc0, 0x91, 0xf8, 0xeb, 0xcf, 0xe3, 0x12, 0x17, + 0x88, 0x9c, 0x88, 0xf4, 0xf5, 0xae, 0xf4, 0x15, + 0xfe, 0x17, 0xf6, 0xa4, 0x74, 0x49, 0x02, 0x05, + 0x11, 0x3b, 0x92, 0x25, 0x39, 0x2c, 0x4b, 0x08, + 0x19, 0x76, 0x13, 0x8d, 0xf9, 0xda, 0xae, 0xdf, + 0x30, 0xda, 0xcc, 0xbb, 0x3f, 0xb9, 0xb0, 0xd6, + 0x5c, 0x78, 0x4b, 0x2b, 0x35, 0x51, 0x17, 0x48, + 0xf5, 0xd4, 0x39, 0x7e, 0x05, 0x83, 0x68, 0x86, + 0x44, 0x5f, 0x56, 0x1d, 0x2c, 0x53, 0xd3, 0x64, + 0x3a, 0xb2, 0x0c, 0x4a, 0x85, 0xd6, 0x5b, 0x7e, + 0xf9, 0xe9, 0x50, 0x29, 0x5d, 0x4f, 0xcc, 0xc9, +}; + +#ifdef CONFIG_MBEDTLS_HARDWARE_MPI +/* Pregenerated RSA 4096 size keys using openssl */ +static const char privkey_4096_buf[] = "-----BEGIN RSA PRIVATE KEY-----\n" + "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\n" + "-----END RSA PRIVATE KEY-----"; + +static const char privkey_2048_buf[] = "-----BEGIN RSA PRIVATE KEY-----\r\n" + "MIIEowIBAAKCAQEA8N8hdkemvj6Tpk975/OWhv9BrTsCBCu+ZYfDb5VI7U2meKBg\r\n" + "3dAkyyhRlY3fNwSRzBUMCzsHjpgnsB40wxOgiwlB9n6PMhq0qUVKAdCpKwFztsKd\r\n" + "JJAsCUC+Zlwxn4RpH6ZnMl3a/njRYjuDyI32kucMP/lBRo7ks1798Gy/j+x1h5xA\r\n" + "vZSlFoEXKjCC6S1DWhALePuZnk4m/jGP6g+YfyJXSTqsenKa/DcWndfn/JoElZ0J\r\n" + "nhud8lBXwVe6mMheE1yqfL+VTU1nwg/TPNZrZsFz2sXig/RQCKt6LuSuzhRpsLp+\r\n" + "BdwqEs9xrwlhZnp7j4kQBomISd6kAxQfYVROHQIDAQABAoIBAHgtO4rB8QWWPyCJ\r\n" + "I670r7OnA2OkvzrJgHMzq2SuvPX4+gfRLMM+qDzcXugZIrdWhk+maJ3p07lnXNXY\r\n" + "HEcAMedstQaA2n0LKfwSX/xL2TtlvBABRVoKvI3ZSaXUdcW60KBD69ULUsoICZ/T\r\n" + "Rcr4WX+t20TH3bOQc7ayvEwKVgE95xIUpTH9asw8uOPvKxW2j5OLQgZuWrWyUDg0\r\n" + "MFh92PhWtw3i5zq6OpTTsFJeceKYV/VstIYjZ+FslmhjQxJbr+2DJRbpHXKceqy6\r\n" + "9yWlSV0EM7neFCHlDa2WPhK8we+6IvMiNVQKj46fHGYNBaW/ZSX7TiG5J0Uqj2e9\r\n" + "0MUGJ8ECgYEA+frJabhfzW5+JfGjTObeznJZE6fAOjFzaBIwFu8Kz2mIjYpQlwVK\r\n" + "EepMkv2KkrJuqS4GnI+Nkq7G0BAUyUj9tTJ3HQzvtJrxsnxVi99Yofx1s1P4YAnu\r\n" + "c8t3ElJoQ4BRoQIs/hIvyYn22IxllBHiGESrnPQ38D82xyXQgd6S8JkCgYEA9qww\r\n" + "j7jx6Xpy/D1Dq8Dvalm7pz3J+yHnti4w2cqZ67grUoyGnNPtciNDdfi4JzLiKkUu\r\n" + "SDS3DacvFpFyND0m8sbpMjnR8Rvhj+bfH8KcOAowD+YR/+6vSb/P/aBt6gYXcaBn\r\n" + "cjepx+sE81mnC7UrHb4TjG4hO5t3ZTc6X28gyCUCgYAMZn9lSisecrO5SCJUp0M4\r\n" + "NH3stq6XdGqIKBbQnG0J2u9WLh1PUIjbGKdRx1f/bPCGXe0gCRL5yse7/IA7d+51\r\n" + "9ZnpDAI8EE+bDgXkWWD5MB/alHjGstdsURSICSR47L2f4g6/T8GlGr3vAg/r53My\r\n" + "xv1IXOkFdu1NtbeBKbxaSQKBgENDmw5mAVmIcXiFAEICn4ahp4EoYT6g9T2BhQKu\r\n" + "s6BKnU2qUj7Lr5ETOp8dzqGpx3B9Yux/q3cGotmFmd3S2x8SzJ5MlAoqbyy9aRSR\r\n" + "DeZeKNL9CuV+YcA7lOz1ZWOOe7AZbHwB38NLPBNb3CheI769iTkfAuLtNvabw8go\r\n" + "VokdAoGBALyvBhW+Squ5tx8NOEgAisakhAVOnT6jcoeKy6FyjcvKaWagmCOCC7Gz\r\n" + "QB9Yf1tJ+3di+aLtWWdmU494iKJHBtPMhfrYltCpxHHQGlUc/GLPY3Z5bBYYYWpb\r\n" + "Wzw4ZvDraKlAs7a9CRwS5cpktk5ptK4rc5noSXkvV+yOT75zXat2\r\n" + "-----END RSA PRIVATE KEY-----\r\n"; + +static const char privkey_3072_buf[] = "-----BEGIN RSA PRIVATE KEY-----\r\n" + "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\r\n" + "-----END RSA PRIVATE KEY-----\r\n"; + +#endif + +_Static_assert(sizeof(pki_rsa2048_output) == 2048/8, "rsa2048 output is wrong size"); +_Static_assert(sizeof(pki_rsa3072_output) == 3072/8, "rsa3072 output is wrong size"); +_Static_assert(sizeof(pki_rsa4096_output) == 4096/8, "rsa4096 output is wrong size"); + +void mbedtls_mpi_printf(const char *name, const mbedtls_mpi *X); + + +static void test_cert(const char *cert, const uint8_t *expected_output, size_t output_len); + +TEST_CASE("mbedtls RSA4096 cert", "[mbedtls]") +{ + + test_cert(rsa4096_cert, pki_rsa4096_output, 4096/8); +} + +TEST_CASE("mbedtls RSA3072 cert", "[mbedtls]") +{ + + test_cert(rsa3072_cert, pki_rsa3072_output, 3072/8); +} + +TEST_CASE("mbedtls RSA2048 cert", "[mbedtls]") +{ + test_cert(rsa2048_cert, pki_rsa2048_output, 2048/8); +} + +static void test_cert(const char *cert, const uint8_t *expected_output, size_t output_len) +{ + mbedtls_x509_crt crt; + mbedtls_rsa_context *rsa; + char buf[output_len]; + int res; + + bzero(buf, output_len); + + mbedtls_x509_crt_init(&crt); + + TEST_ASSERT_EQUAL_HEX16_MESSAGE(0, + -mbedtls_x509_crt_parse(&crt, + (const uint8_t *)cert, + strlen(cert)+1), + "parse cert"); + + rsa = mbedtls_pk_rsa(crt.pk); + TEST_ASSERT_NOT_NULL(rsa); + + res = mbedtls_rsa_check_pubkey(rsa); + TEST_ASSERT_EQUAL_HEX16_MESSAGE(0, + -res, + "check cert pubkey"); + + mbedtls_x509_crt_info(buf, sizeof(buf), "", &crt); + puts(buf); + + res = mbedtls_rsa_public(rsa, pki_input, (uint8_t *)buf); + if (res == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + MBEDTLS_ERR_RSA_PUBLIC_FAILED) { + mbedtls_x509_crt_free(&crt); + TEST_IGNORE_MESSAGE("Hardware does not support this key length"); + } + + TEST_ASSERT_EQUAL_HEX16_MESSAGE(0, + -res, + "RSA PK operation"); + + /* + // Dump buffer for debugging + for(int i = 0; i < output_len; i++) { + printf("0x%02x, ", buf[i]); + } + printf("\n"); + */ + + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_output, buf, output_len); + + mbedtls_x509_crt_free(&crt); +} + +#ifdef CONFIG_MBEDTLS_HARDWARE_MPI +static void rsa_key_operations(int keysize, bool check_performance, bool generate_new_rsa); + +static int myrand(void *rng_state, unsigned char *output, size_t len) +{ + size_t olen; + return mbedtls_hardware_poll(rng_state, output, len, &olen); +} + +#ifdef PRINT_DEBUG_INFO +static void print_rsa_details(mbedtls_rsa_context *rsa) +{ + mbedtls_mpi X[5]; + for (int i=0; i<5; ++i) { + mbedtls_mpi_init( &X[i] ); + } + + if (0 == mbedtls_rsa_export(rsa, &X[0], &X[1], &X[2], &X[3], &X[4])) { + for (int i=0; i<5; ++i) { + mbedtls_mpi_printf((char*)"N\0P\0Q\0D\0E" + 2*i, &X[i]); + mbedtls_mpi_free( &X[i] ); + } + } +} +#endif + +#if CONFIG_FREERTOS_SMP // IDF-5260 +TEST_CASE("test performance RSA key operations", "[bignum][timeout=60]") +#else +TEST_CASE("test performance RSA key operations", "[bignum]") +#endif +{ + /** NOTE: + * For ESP32-S3, CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG is enabled + * by default; allocating a lock of 92 bytes, which is never freed. + * + * MR !18574 adds the MPI crypto lock for S3 increasing the leakage by + * 92 bytes. This caused the RSA UT to fail with a leakage more than + * 1024 bytes. + * + * The allocations made by ESP32-S2 (944 bytes) and ESP32-S3 are the same, + * except for the JTAG lock (92 + 944 > 1024). + */ + TEST_ESP_OK(test_utils_set_leak_level(1088, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL)); + for (int keysize = 2048; keysize <= SOC_RSA_MAX_BIT_LEN; keysize += 1024) { + rsa_key_operations(keysize, true, false); + } +} + +#if CONFIG_FREERTOS_SMP // IDF-5260 +TEST_CASE("test RSA-3072 calculations", "[bignum][timeout=60]") +#else +TEST_CASE("test RSA-3072 calculations", "[bignum]") +#endif +{ + // use pre-genrated keys to make the test run a bit faster + rsa_key_operations(3072, false, false); +} + +#if CONFIG_FREERTOS_SMP // IDF-5260 +TEST_CASE("test RSA-2048 calculations", "[bignum][timeout=60]") +#else +TEST_CASE("test RSA-2048 calculations", "[bignum]") +#endif +{ + // use pre-genrated keys to make the test run a bit faster + rsa_key_operations(2048, false, false); +} + +#if CONFIG_FREERTOS_SMP // IDF-5260 +TEST_CASE("test RSA-4096 calculations", "[bignum][timeout=60]") +#else +TEST_CASE("test RSA-4096 calculations", "[bignum]") +#endif +{ + // use pre-genrated keys to make the test run a bit faster + rsa_key_operations(4096, false, false); +} + + +static void rsa_key_operations(int keysize, bool check_performance, bool generate_new_rsa) +{ + mbedtls_pk_context clientkey; + mbedtls_rsa_context rsa; + unsigned char orig_buf[4096 / 8]; + unsigned char encrypted_buf[4096 / 8]; + unsigned char decrypted_buf[4096 / 8]; + int res = 0; + + printf("First, orig_buf is encrypted by the public key, and then decrypted by the private key\n"); + printf("keysize=%d check_performance=%d generate_new_rsa=%d\n", keysize, check_performance, generate_new_rsa); + + memset(orig_buf, 0xAA, sizeof(orig_buf)); + orig_buf[0] = 0; // Ensure that orig_buf is smaller than rsa.N + if (generate_new_rsa) { + mbedtls_rsa_init(&rsa); + TEST_ASSERT_EQUAL(0, mbedtls_rsa_gen_key(&rsa, myrand, NULL, keysize, 65537)); + } else { + mbedtls_pk_init(&clientkey); + + switch(keysize) { + case 4096: + res = mbedtls_pk_parse_key(&clientkey, (const uint8_t *)privkey_4096_buf, sizeof(privkey_4096_buf), NULL, 0, myrand, NULL); + break; + case 3072: + res = mbedtls_pk_parse_key(&clientkey, (const uint8_t *)privkey_3072_buf, sizeof(privkey_3072_buf), NULL, 0, myrand, NULL); + break; + case 2048: + res = mbedtls_pk_parse_key(&clientkey, (const uint8_t *)privkey_2048_buf, sizeof(privkey_2048_buf), NULL, 0, myrand, NULL); + break; + default: + TEST_FAIL_MESSAGE("unsupported keysize, pass generate_new_rsa=true or update test"); + } + + TEST_ASSERT_EQUAL_HEX16(0, -res); + + memcpy(&rsa, mbedtls_pk_rsa(clientkey), sizeof(mbedtls_rsa_context)); + } + +#ifdef PRINT_DEBUG_INFO + print_rsa_details(&rsa); +#endif + + TEST_ASSERT_EQUAL(keysize, (int)rsa.MBEDTLS_PRIVATE(len) * 8); + TEST_ASSERT_EQUAL(keysize, (int)rsa.MBEDTLS_PRIVATE(D).MBEDTLS_PRIVATE(n) * sizeof(mbedtls_mpi_uint) * 8); // The private exponent + +#ifdef SOC_CCOMP_TIMER_SUPPORTED + int public_perf, private_perf; + ccomp_timer_start(); + res = mbedtls_rsa_public(&rsa, orig_buf, encrypted_buf); + public_perf = ccomp_timer_stop(); + + if (res == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE + MBEDTLS_ERR_RSA_PUBLIC_FAILED) { + mbedtls_rsa_free(&rsa); + TEST_IGNORE_MESSAGE("Hardware does not support this key length"); + } + TEST_ASSERT_EQUAL_HEX16(0, -res); + + ccomp_timer_start(); + res = mbedtls_rsa_private(&rsa, myrand, NULL, encrypted_buf, decrypted_buf); + private_perf = ccomp_timer_stop(); + TEST_ASSERT_EQUAL_HEX16(0, -res); + + if (check_performance && keysize == 2048) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(RSA_2048KEY_PUBLIC_OP, "%d us", public_perf); + TEST_PERFORMANCE_CCOMP_LESS_THAN(RSA_2048KEY_PRIVATE_OP, "%d us", private_perf); + } else if (check_performance && keysize == 4096) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(RSA_4096KEY_PUBLIC_OP, "%d us", public_perf); + TEST_PERFORMANCE_CCOMP_LESS_THAN(RSA_4096KEY_PRIVATE_OP, "%d us", private_perf); + } +#else + res = mbedtls_rsa_public(&rsa, orig_buf, encrypted_buf); + TEST_ASSERT_EQUAL_HEX16(0, -res); + res = mbedtls_rsa_private(&rsa, myrand, NULL, encrypted_buf, decrypted_buf); + TEST_ASSERT_EQUAL_HEX16(0, -res); + TEST_IGNORE_MESSAGE("Performance check skipped! (soc doesn't support ccomp timer)"); +#endif + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(orig_buf, decrypted_buf, keysize / 8, "RSA operation"); + + mbedtls_rsa_free(&rsa); +} + + +TEST_CASE("mbedtls RSA Generate Key", "[mbedtls][timeout=60]") +{ + + mbedtls_rsa_context ctx; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + const unsigned int key_size = 2048; + const int exponent = 65537; + +#if CONFIG_MBEDTLS_MPI_USE_INTERRUPT && CONFIG_ESP_TASK_WDT_EN && !CONFIG_ESP_TASK_WDT_INIT + /* Check that generating keys doesnt starve the watchdog if interrupt-based driver is used */ + esp_task_wdt_config_t twdt_config = { + .timeout_ms = 1000, + .idle_core_mask = (1 << 0), // Watch core 0 idle + .trigger_panic = true, + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config)); +#endif // CONFIG_MBEDTLS_MPI_USE_INTERRUPT && CONFIG_ESP_TASK_WDT_EN && !CONFIG_ESP_TASK_WDT_INIT + + mbedtls_rsa_init(&ctx); + mbedtls_ctr_drbg_init(&ctr_drbg); + + mbedtls_entropy_init(&entropy); + TEST_ASSERT_FALSE( mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0) ); + + TEST_ASSERT_FALSE( mbedtls_rsa_gen_key(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, key_size, exponent) ); + + mbedtls_rsa_free(&ctx); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + +#if CONFIG_MBEDTLS_MPI_USE_INTERRUPT && CONFIG_ESP_TASK_WDT_EN && !CONFIG_ESP_TASK_WDT_INIT + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit()); +#endif // CONFIG_MBEDTLS_MPI_USE_INTERRUPT && CONFIG_ESP_TASK_WDT_EN && !CONFIG_ESP_TASK_WDT_INIT + +} + +#endif // CONFIG_MBEDTLS_HARDWARE_MPI diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_sha.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_sha.c new file mode 100644 index 000000000..5a314514b --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_sha.c @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include "esp_types.h" +#include "esp_log.h" +#include "ccomp_timer.h" +#include "esp_heap_caps.h" +#include "idf_performance.h" +#include "esp_private/esp_clk.h" +#include "spi_flash_mmap.h" + +#include "soc/soc_caps.h" + +#include "unity.h" +#include "test_utils.h" +#include "mbedtls/sha1.h" +#include "mbedtls/sha256.h" + +#if SOC_SHA_SUPPORT_SHA512 +#include "mbedtls/sha512.h" +#endif + +#include "sha/sha_parallel_engine.h" + +/* Note: Most of the SHA functions are called as part of mbedTLS, so +are tested as part of mbedTLS tests. Only esp_sha() is different. +*/ + +#define TAG "sha_test" + +#if SOC_SHA_SUPPORTED +TEST_CASE("Test esp_sha()", "[hw_crypto]") +{ + const size_t BUFFER_SZ = 32 * 1024 + 6; // NB: not an exact multiple of SHA block size + + int64_t elapsed; + uint32_t us_sha1; + uint8_t sha1_result[20] = { 0 }; + +#if SOC_SHA_SUPPORT_SHA512 + uint32_t us_sha512; + uint8_t sha512_result[64] = { 0 }; +#endif + + void *buffer = heap_caps_malloc(BUFFER_SZ, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buffer); + memset(buffer, 0xEE, BUFFER_SZ); + + const uint8_t sha1_expected[20] = { 0xc7, 0xbb, 0xd3, 0x74, 0xf2, 0xf6, 0x20, 0x86, + 0x61, 0xf4, 0x50, 0xd5, 0xf5, 0x18, 0x44, 0xcc, + 0x7a, 0xb7, 0xa5, 0x4a }; +#if SOC_SHA_SUPPORT_SHA512 + const uint8_t sha512_expected[64] = { 0xc7, 0x7f, 0xda, 0x8c, 0xb3, 0x58, 0x14, 0x8a, + 0x52, 0x3b, 0x46, 0x04, 0xc0, 0x85, 0xc5, 0xf0, + 0x46, 0x64, 0x14, 0xd5, 0x96, 0x7a, 0xa2, 0x80, + 0x20, 0x9c, 0x04, 0x27, 0x7d, 0x3b, 0xf9, 0x1f, + 0xb2, 0xa3, 0x45, 0x3c, 0xa1, 0x6a, 0x8d, 0xdd, + 0x35, 0x5e, 0x35, 0x57, 0x76, 0x22, 0x74, 0xd8, + 0x1e, 0x07, 0xc6, 0xa2, 0x9e, 0x3b, 0x65, 0x75, + 0x80, 0x7d, 0xe6, 0x6e, 0x47, 0x61, 0x2c, 0x94 }; +#endif + + ccomp_timer_start(); + esp_sha(SHA1, buffer, BUFFER_SZ, sha1_result); + elapsed = ccomp_timer_stop(); + TEST_ASSERT_EQUAL_HEX8_ARRAY(sha1_expected, sha1_result, sizeof(sha1_expected)); + us_sha1 = elapsed; + ESP_LOGI(TAG, "esp_sha() 32KB SHA1 in %" PRIu32 " us", us_sha1); + +#if SOC_SHA_SUPPORT_SHA512 + ccomp_timer_start(); + esp_sha(SHA2_512, buffer, BUFFER_SZ, sha512_result); + elapsed = ccomp_timer_stop(); + TEST_ASSERT_EQUAL_HEX8_ARRAY(sha512_expected, sha512_result, sizeof(sha512_expected)); + + us_sha512 = elapsed; + ESP_LOGI(TAG, "esp_sha() 32KB SHA512 in %" PRIu32 " us", us_sha512); +#endif + + free(buffer); + + TEST_PERFORMANCE_CCOMP_LESS_THAN(TIME_SHA1_32KB, "%" PRId32 " us", us_sha1); + +#if SOC_SHA_SUPPORT_SHA512 + TEST_PERFORMANCE_CCOMP_LESS_THAN(TIME_SHA512_32KB, "%" PRId32 " us", us_sha512); +#endif +} + +TEST_CASE("Test esp_sha() function with long input", "[hw_crypto]") +{ + const void* ptr; + spi_flash_mmap_handle_t handle; + uint8_t sha1_espsha[20] = { 0 }; + uint8_t sha1_mbedtls[20] = { 0 }; + uint8_t sha256_espsha[32] = { 0 }; + uint8_t sha256_mbedtls[32] = { 0 }; + +#if SOC_SHA_SUPPORT_SHA512 + uint8_t sha512_espsha[64] = { 0 }; + uint8_t sha512_mbedtls[64] = { 0 }; +#endif + + const size_t LEN = 1024 * 1024; + + /* mmap() 1MB of flash, we don't care what it is really */ + esp_err_t err = spi_flash_mmap(0x0, LEN, SPI_FLASH_MMAP_DATA, &ptr, &handle); + + TEST_ASSERT_EQUAL_HEX32(ESP_OK, err); + TEST_ASSERT_NOT_NULL(ptr); + + /* Compare esp_sha() result to the mbedTLS result, should always be the same */ + + esp_sha(SHA1, ptr, LEN, sha1_espsha); + int r = mbedtls_sha1(ptr, LEN, sha1_mbedtls); + TEST_ASSERT_EQUAL(0, r); + + esp_sha(SHA2_256, ptr, LEN, sha256_espsha); + r = mbedtls_sha256(ptr, LEN, sha256_mbedtls, 0); + TEST_ASSERT_EQUAL(0, r); + +#if SOC_SHA_SUPPORT_SHA512 + esp_sha(SHA2_512, ptr, LEN, sha512_espsha); + r = mbedtls_sha512(ptr, LEN, sha512_mbedtls, 0); + TEST_ASSERT_EQUAL(0, r); +#endif + + /* munmap() 1MB of flash when the usge of memory-mapped ptr is over */ + spi_flash_munmap(handle); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha1_espsha, sha1_mbedtls, sizeof(sha1_espsha), "SHA1 results should match"); + + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha256_espsha, sha256_mbedtls, sizeof(sha256_espsha), "SHA256 results should match"); + +#if SOC_SHA_SUPPORT_SHA512 + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sha512_espsha, sha512_mbedtls, sizeof(sha512_espsha), "SHA512 results should match"); +#endif +} + +#endif // SOC_SHA_SUPPORTED diff --git a/components/mbedtls/mbedtls_v3/test_apps/main/test_sha_perf.c b/components/mbedtls/mbedtls_v3/test_apps/main/test_sha_perf.c new file mode 100644 index 000000000..41101e712 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/main/test_sha_perf.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * mbedTLS SHA performance test + */ +#include +#include +#include +#include "mbedtls/sha256.h" +#include "unity.h" +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "test_utils.h" +#include "ccomp_timer.h" +#include "test_mbedtls_utils.h" + +TEST_CASE("mbedtls SHA performance", "[aes]") +{ + const unsigned CALLS = 256; + const unsigned CALL_SZ = 16 * 1024; + mbedtls_sha256_context sha256_ctx; + float elapsed_usec; + unsigned char sha256[32]; + + // allocate internal memory + uint8_t *buf = heap_caps_malloc(CALL_SZ, MALLOC_CAP_DMA | MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buf); + memset(buf, 0x55, CALL_SZ); + + mbedtls_sha256_init(&sha256_ctx); + ccomp_timer_start(); + TEST_ASSERT_EQUAL(0, mbedtls_sha256_starts(&sha256_ctx, false)); + for (int c = 0; c < CALLS; c++) { + TEST_ASSERT_EQUAL(0, mbedtls_sha256_update(&sha256_ctx, buf, CALL_SZ)); + } + TEST_ASSERT_EQUAL(0, mbedtls_sha256_finish(&sha256_ctx, sha256)); + elapsed_usec = ccomp_timer_stop(); + + free(buf); + mbedtls_sha256_free(&sha256_ctx); + + /* Check the result. Reference value can be calculated using: + * dd if=/dev/zero bs=$((16*1024)) count=256 | tr '\000' '\125' | sha256sum + */ + const char *expected_hash = "c88df2638fb9699abaad05780fa5e0fdb6058f477069040eac8bed3231286275"; + char hash_str[sizeof(sha256) * 2 + 1]; + utils_bin2hex(hash_str, sizeof(hash_str), sha256, sizeof(sha256)); + + TEST_ASSERT_EQUAL_STRING(expected_hash, hash_str); + + // bytes/usec = MB/sec + float mb_sec = (CALL_SZ * CALLS) / elapsed_usec; + printf("SHA256 rate %.3fMB/sec\n", mb_sec); +#ifdef CONFIG_MBEDTLS_HARDWARE_SHA + // Don't put a hard limit on software SHA performance + TEST_PERFORMANCE_CCOMP_GREATER_THAN(SHA256_THROUGHPUT_MBSEC, "%.3fMB/sec", mb_sec); +#endif +} diff --git a/components/mbedtls/mbedtls_v3/test_apps/pytest_mbedtls_ut.py b/components/mbedtls/mbedtls_v3/test_apps/pytest_mbedtls_ut.py new file mode 100644 index 000000000..1445e9a5a --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/pytest_mbedtls_ut.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.supported_targets +@pytest.mark.generic +def test_mbedtls(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'perf_esp32', + ], + indirect=True, +) +def test_mbedtls_esp32_compiler_perf_opt(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'aes_no_hw', + ], + indirect=True, +) +def test_mbedtls_aes_no_hw(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], + indirect=True, +) +def test_mbedtls_psram(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'psram_esp32', + 'psram_all_ext', + ], + indirect=True, +) +def test_mbedtls_psram_esp32(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32h2 +@pytest.mark.ecdsa_efuse +@pytest.mark.parametrize('config', ['ecdsa_sign',], indirect=True) +def test_mbedtls_ecdsa_sign(dut: Dut) -> None: + dut.run_all_single_board_cases(group='efuse_key') + + +@pytest.mark.esp32c2 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'rom_impl', + ], + indirect=True, +) +def test_mbedtls_rom_impl_esp32c2(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.aes_no_hw b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.aes_no_hw new file mode 100644 index 000000000..aadb89a89 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.aes_no_hw @@ -0,0 +1,2 @@ +CONFIG_MBEDTLS_HARDWARE_AES=n +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=n diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.default b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.default new file mode 100644 index 000000000..e69de29bb diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.ecdsa_sign b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.ecdsa_sign new file mode 100644 index 000000000..be59d96a4 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.ecdsa_sign @@ -0,0 +1,2 @@ +CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY=y +CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.perf_esp32 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.perf_esp32 new file mode 100644 index 000000000..3c5a0fabe --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.perf_esp32 @@ -0,0 +1 @@ +CONFIG_COMPILER_OPTIMIZATION_PERF=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram new file mode 100644 index 000000000..e09896976 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=800 diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_all_ext b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_all_ext new file mode 100644 index 000000000..ad3452cf7 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_all_ext @@ -0,0 +1,4 @@ +CONFIG_SPIRAM=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=800 +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_esp32 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_esp32 new file mode 100644 index 000000000..5acbcfd7f --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.psram_esp32 @@ -0,0 +1,3 @@ +CONFIG_SPIRAM=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=800 +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.rom_impl b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.rom_impl new file mode 100644 index 000000000..4f79484e4 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.ci.rom_impl @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32c2" +CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults new file mode 100644 index 000000000..35ba075f2 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults @@ -0,0 +1,10 @@ +# General options for additional checks +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=n diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32 new file mode 100644 index 000000000..66cbe6577 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32 @@ -0,0 +1,4 @@ +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_XTAL_FREQ_AUTO=y +CONFIG_SPI_FLASH_SHARE_SPI1_BUS=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c2 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c2 new file mode 100644 index 000000000..bb33cff20 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c2 @@ -0,0 +1,2 @@ +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c3 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c3 new file mode 100644 index 000000000..d0ea27a6c --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32c3 @@ -0,0 +1 @@ +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s2 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s2 new file mode 100644 index 000000000..da22be442 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s2 @@ -0,0 +1,2 @@ +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n diff --git a/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s3 b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s3 new file mode 100644 index 000000000..da22be442 --- /dev/null +++ b/components/mbedtls/mbedtls_v3/test_apps/sdkconfig.defaults.esp32s3 @@ -0,0 +1,2 @@ +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n diff --git a/components/openssl/Kconfig b/components/openssl/Kconfig index 0df49080c..4b6de5d5c 100644 --- a/components/openssl/Kconfig +++ b/components/openssl/Kconfig @@ -1,5 +1,5 @@ -menu "OpenSSL" - +menu "OpenSSL (Not Support mbedtls V3.x)" + depends on !MBEDTLS_V3 config OPENSSL_DEBUG bool "Enable OpenSSL debugging" default n diff --git a/components/openssl/platform/ssl_pm.c b/components/openssl/platform/ssl_pm.c index 1448faa4e..90df8a225 100644 --- a/components/openssl/platform/ssl_pm.c +++ b/components/openssl/platform/ssl_pm.c @@ -26,6 +26,12 @@ #include "mbedtls/certs.h" #define X509_INFO_STRING_LENGTH 8192 +#if defined(CONFIG_MBEDTLS_V3) +// Only for compliation, not work +#define MBEDTLS_SSL_MINOR_VERSION_0 0 /*!< SSL v3.0 */ +#define MBEDTLS_SSL_MINOR_VERSION_1 1 /*!< TLS v1.0 */ +#define MBEDTLS_SSL_MINOR_VERSION_2 2 /*!< TLS v1.1 */ +#endif struct ssl_pm { @@ -261,7 +267,7 @@ static int mbedtls_handshake( mbedtls_ssl_context *ssl ) { int ret = 0; - while (ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) { + while (ssl->MBEDTLS_PRIVATE(state) != MBEDTLS_SSL_HANDSHAKE_OVER) { ret = mbedtls_ssl_handshake_step(ssl); SSL_DEBUG(SSL_PLATFORM_DEBUG_LEVEL, "ssl ret %d state %d", ret, ssl->state); @@ -391,7 +397,7 @@ OSSL_HANDSHAKE_STATE ssl_pm_get_state(const SSL *ssl) struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - switch (ssl_pm->ssl.state) + switch (ssl_pm->ssl.MBEDTLS_PRIVATE(state)) { case MBEDTLS_SSL_CLIENT_HELLO: state = TLS_ST_CW_CLNT_HELLO; @@ -426,9 +432,11 @@ OSSL_HANDSHAKE_STATE ssl_pm_get_state(const SSL *ssl) case MBEDTLS_SSL_SERVER_KEY_EXCHANGE: state = TLS_ST_SR_KEY_EXCH; break; +#if !defined(CONFIG_MBEDTLS_V3) case MBEDTLS_SSL_SERVER_NEW_SESSION_TICKET: state = TLS_ST_SW_SESSION_TICKET; break; +#endif case MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT: state = TLS_ST_SW_CERT_REQ; break; @@ -605,6 +613,14 @@ void pkey_pm_free(EVP_PKEY *pk) pk->pkey_pm = NULL; } +#if defined(CONFIG_MBEDTLS_V3) +static int mbedtls_ctr_drbg_random_fake(void *p_rng, unsigned char *output, + size_t output_len) +{ + return 0; +} +#endif + int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) { int ret; @@ -632,8 +648,12 @@ int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len) load_buf[len] = '\0'; mbedtls_pk_init(pkey_pm->pkey); - +#if defined(CONFIG_MBEDTLS_V3) + ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, len + 1, NULL, 0, + mbedtls_ctr_drbg_random_fake, NULL); +#else ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, len + 1, NULL, 0); +#endif ssl_mem_free(load_buf); if (ret) { diff --git a/components/wpa_supplicant/src/crypto/crypto_mbedtls-ec.c b/components/wpa_supplicant/src/crypto/crypto_mbedtls-ec.c index 42290b038..8edb90b0b 100644 --- a/components/wpa_supplicant/src/crypto/crypto_mbedtls-ec.c +++ b/components/wpa_supplicant/src/crypto/crypto_mbedtls-ec.c @@ -164,7 +164,7 @@ int crypto_ec_point_to_bin(struct crypto_ec *e, int len = mbedtls_mpi_size(&e->group.P); if (x) { - if(crypto_bignum_to_bin((struct crypto_bignum *) & ((mbedtls_ecp_point *) point)->X, + if(crypto_bignum_to_bin((struct crypto_bignum *) & ((mbedtls_ecp_point *) point)->MBEDTLS_PRIVATE(X), x, len, len) < 0) { return -1; } @@ -172,7 +172,7 @@ int crypto_ec_point_to_bin(struct crypto_ec *e, } if (y) { - if(crypto_bignum_to_bin((struct crypto_bignum *) & ((mbedtls_ecp_point *) point)->Y, + if(crypto_bignum_to_bin((struct crypto_bignum *) & ((mbedtls_ecp_point *) point)->MBEDTLS_PRIVATE(Y), y, len, len) < 0) { return -1; } @@ -187,17 +187,17 @@ int crypto_ec_get_affine_coordinates(struct crypto_ec *e, struct crypto_ec_point int ret = -1; mbedtls_ecp_point *point = (mbedtls_ecp_point *)pt; - if (!mbedtls_ecp_is_zero(point) && (mbedtls_mpi_cmp_int( &point->Z, 1 ) == 0 )) { + if (!mbedtls_ecp_is_zero(point) && (mbedtls_mpi_cmp_int( &point->MBEDTLS_PRIVATE(Z), 1 ) == 0 )) { // Affine coordinates mean that z should be 1, wpa_printf(MSG_ERROR, "Z coordinate is neither 0 or 1"); return -1; } if (x) { - MBEDTLS_MPI_CHK(mbedtls_mpi_copy((mbedtls_mpi*) x, &((mbedtls_ecp_point* )point)->X)); + MBEDTLS_MPI_CHK(mbedtls_mpi_copy((mbedtls_mpi*) x, &((mbedtls_ecp_point* )point)->MBEDTLS_PRIVATE(X))); } if (y) { - MBEDTLS_MPI_CHK(mbedtls_mpi_copy((mbedtls_mpi*) y, &((mbedtls_ecp_point* )point)->Y)); + MBEDTLS_MPI_CHK(mbedtls_mpi_copy((mbedtls_mpi*) y, &((mbedtls_ecp_point* )point)->MBEDTLS_PRIVATE(Y))); } return 0; cleanup: @@ -217,11 +217,14 @@ struct crypto_ec_point *crypto_ec_point_from_bin(struct crypto_ec *e, len = mbedtls_mpi_size(&e->group.P); pt = os_zalloc(sizeof(mbedtls_ecp_point)); + if (!pt) { + return NULL; + } mbedtls_ecp_point_init(pt); - MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pt->X, val, len)); - MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pt->Y, val + len, len)); - MBEDTLS_MPI_CHK(mbedtls_mpi_lset((&pt->Z), 1)); + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pt->MBEDTLS_PRIVATE(X), val, len)); + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pt->MBEDTLS_PRIVATE(Y), val + len, len)); + MBEDTLS_MPI_CHK(mbedtls_mpi_lset((&pt->MBEDTLS_PRIVATE(Z)), 1)); return (struct crypto_ec_point *) pt; @@ -291,8 +294,8 @@ static int ecp_opp(const mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbe } /* In-place opposite */ - if (mbedtls_mpi_cmp_int(&R->Y, 0) != 0) { - MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&R->Y, &grp->P, &R->Y)); + if (mbedtls_mpi_cmp_int(&R->MBEDTLS_PRIVATE(Y), 0) != 0) { + MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(&R->MBEDTLS_PRIVATE(Y), &grp->P, &R->MBEDTLS_PRIVATE(Y))); } cleanup: @@ -313,7 +316,7 @@ int crypto_ec_point_solve_y_coord(struct crypto_ec *e, mbedtls_mpi_init(&temp); int ret = 0; - y = &((mbedtls_ecp_point *)p)->Y; + y = &((mbedtls_ecp_point *)p)->MBEDTLS_PRIVATE(Y); /* Faster way to find sqrt * Works only with curves having prime p @@ -337,8 +340,8 @@ int crypto_ec_point_solve_y_coord(struct crypto_ec *e, if (y_bit != mbedtls_mpi_get_bit(y, 0)) MBEDTLS_MPI_CHK(mbedtls_mpi_sub_mpi(y, &e->group.P, y)); - MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&((mbedtls_ecp_point* )p)->X, (const mbedtls_mpi*) x)); - MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&((mbedtls_ecp_point *)p)->Z, 1)); + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&((mbedtls_ecp_point* )p)->MBEDTLS_PRIVATE(X), (const mbedtls_mpi*) x)); + MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&((mbedtls_ecp_point *)p)->MBEDTLS_PRIVATE(Z), 1)); } else { ret = 1; } @@ -422,9 +425,9 @@ int crypto_ec_point_is_on_curve(struct crypto_ec *e, /* Calculate y^2 mod P*/ MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&two, 2)); - MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&y_sqr_lhs, &((const mbedtls_ecp_point *)p)->Y , &two, &e->group.P, NULL)); + MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&y_sqr_lhs, &((const mbedtls_ecp_point *)p)->MBEDTLS_PRIVATE(Y) , &two, &e->group.P, NULL)); - y_sqr_rhs = (mbedtls_mpi *) crypto_ec_point_compute_y_sqr(e, (const struct crypto_bignum *) & ((const mbedtls_ecp_point *)p)->X); + y_sqr_rhs = (mbedtls_mpi *) crypto_ec_point_compute_y_sqr(e, (const struct crypto_bignum *) & ((const mbedtls_ecp_point *)p)->MBEDTLS_PRIVATE(X)); if (y_sqr_rhs && (mbedtls_mpi_cmp_mpi(y_sqr_rhs, &y_sqr_lhs) == 0)) { on_curve = 1; @@ -447,10 +450,30 @@ int crypto_ec_point_cmp(const struct crypto_ec *e, } int crypto_key_compare(struct crypto_key *key1, struct crypto_key *key2) { +#ifdef CONFIG_MBEDTLS_V3 + int ret = 0; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + MBEDTLS_MPI_CHK(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0)); + if (mbedtls_pk_check_pair((mbedtls_pk_context *)key1, (mbedtls_pk_context *)key2, mbedtls_ctr_drbg_random, &ctr_drbg) < 0) { + goto cleanup; + } + + ret = 1; +cleanup: + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + return ret; +#else if (mbedtls_pk_check_pair((mbedtls_pk_context *)key1, (mbedtls_pk_context *)key2) < 0) return 0; return 1; +#endif } void crypto_debug_print_point(const char *title, struct crypto_ec *e, @@ -490,11 +513,15 @@ struct crypto_key * crypto_ec_set_pubkey_point(const struct crypto_ec_group *gro mbedtls_pk_context *key = (mbedtls_pk_context *)crypto_alloc_key(); if (!key) { - wpa_printf(MSG_ERROR, "%s: memory allocation failed\n", __func__); + wpa_printf(MSG_ERROR, "%s: memory allocation failed", __func__); return NULL; } point = (mbedtls_ecp_point *)crypto_ec_point_from_bin((struct crypto_ec *)group, buf); + if (!point) { + wpa_printf(MSG_ERROR, "%s: Point initialization failed", __func__); + goto fail; + } if (crypto_ec_point_is_at_infinity((struct crypto_ec *)group, (struct crypto_ec_point *)point)) { wpa_printf(MSG_ERROR, "Point is at infinity"); goto fail; @@ -503,7 +530,22 @@ struct crypto_key * crypto_ec_set_pubkey_point(const struct crypto_ec_group *gro wpa_printf(MSG_ERROR, "Point not on curve"); goto fail; } +#ifdef CONFIG_MBEDTLS_V3 + mbedtls_ecp_group *ecp_grp = (mbedtls_ecp_group *)group; + if (mbedtls_ecp_check_pubkey(ecp_grp, point) < 0) { + // ideally should have failed in upper condition, duplicate code?? + wpa_printf(MSG_ERROR, "Invalid key"); + goto fail; + } + /* Assign values */ + if( ( ret = mbedtls_pk_setup( key, + mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY) ) ) != 0 ) + goto fail; + mbedtls_ecp_copy(&mbedtls_pk_ec(*key)->MBEDTLS_PRIVATE(Q), point); + mbedtls_ecp_group_load(&mbedtls_pk_ec(*key)->MBEDTLS_PRIVATE(grp), ecp_grp->id); + pkey = (struct crypto_key *)key; +#else if (mbedtls_ecp_check_pubkey((mbedtls_ecp_group *)group, point) < 0) { //typecast // ideally should have failed in upper condition, duplicate code?? wpa_printf(MSG_ERROR, "Invalid key"); @@ -533,6 +575,7 @@ struct crypto_key * crypto_ec_set_pubkey_point(const struct crypto_ec_group *gro pkey = (struct crypto_key *)key; cleanup: +#endif crypto_ec_point_deinit((struct crypto_ec_point *)point, 0); return pkey; fail: @@ -556,7 +599,7 @@ struct crypto_ec_point *crypto_ec_get_public_key(struct crypto_key *key) { mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; - return (struct crypto_ec_point *)&mbedtls_pk_ec(*pkey)->Q; + return (struct crypto_ec_point *)&mbedtls_pk_ec(*pkey)->MBEDTLS_PRIVATE(Q); } @@ -584,14 +627,14 @@ struct crypto_ec_group *crypto_ec_get_group_from_key(struct crypto_key *key) { mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; - return (struct crypto_ec_group *)&(mbedtls_pk_ec(*pkey)->grp); + return (struct crypto_ec_group *)&(mbedtls_pk_ec(*pkey)->MBEDTLS_PRIVATE(grp)); } struct crypto_bignum *crypto_ec_get_private_key(struct crypto_key *key) { mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; - return ((struct crypto_bignum *)&(mbedtls_pk_ec(*pkey)->d)); + return ((struct crypto_bignum *)&(mbedtls_pk_ec(*pkey)->MBEDTLS_PRIVATE(d))); } int crypto_ec_get_publickey_buf(struct crypto_key *key, u8 *key_buf, int len) @@ -599,12 +642,12 @@ int crypto_ec_get_publickey_buf(struct crypto_key *key, u8 *key_buf, int len) mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; unsigned char buf[MBEDTLS_MPI_MAX_SIZE + 10]; /* tag, length + MPI */ unsigned char *c = buf + sizeof(buf ); - size_t pk_len = 0; + int pk_len = 0; memset(buf, 0, sizeof(buf) ); pk_len = mbedtls_pk_write_pubkey( &c, buf, pkey); - if (!pk_len) + if (pk_len < 0) return -1; if (len == 0) @@ -637,11 +680,14 @@ struct crypto_key *crypto_ec_get_key(const u8 *privkey, size_t privkey_len) mbedtls_pk_context *kctx = (mbedtls_pk_context *)crypto_alloc_key(); if (!kctx) { - wpa_printf(MSG_ERROR, "memory allocation failed\n"); + wpa_printf(MSG_ERROR, "memory allocation failed"); return NULL; } +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_pk_parse_key(kctx, privkey, privkey_len, NULL, 0, crypto_rng_wrapper, NULL); +#else ret = mbedtls_pk_parse_key(kctx, privkey, privkey_len, NULL, 0); - +#endif if (ret < 0) { //crypto_print_error_string(ret); goto fail; @@ -752,7 +798,7 @@ int crypto_ecdsa_get_sign(unsigned char *hash, mbedtls_ecdsa_context *ctx = os_malloc(sizeof(*ctx)); if (!ctx) { - wpa_printf(MSG_ERROR,"failed to allcate memory\n"); + wpa_printf(MSG_ERROR,"failed to allcate memory"); return -1; } mbedtls_ecdsa_init(ctx); @@ -760,8 +806,8 @@ int crypto_ecdsa_get_sign(unsigned char *hash, if (mbedtls_ecdsa_from_keypair(ctx, mbedtls_pk_ec(*pkey)) < 0) { goto fail; } - ret = mbedtls_ecdsa_sign(&ctx->grp, (mbedtls_mpi *)r, (mbedtls_mpi *)s, - &ctx->d, hash, SHA256_MAC_LEN, crypto_rng_wrapper, NULL); + ret = mbedtls_ecdsa_sign(&ctx->MBEDTLS_PRIVATE(grp), (mbedtls_mpi *)r, (mbedtls_mpi *)s, + &ctx->MBEDTLS_PRIVATE(d), hash, SHA256_MAC_LEN, crypto_rng_wrapper, NULL); fail: mbedtls_ecdsa_free(ctx); @@ -773,6 +819,22 @@ int crypto_ecdsa_get_sign(unsigned char *hash, int crypto_edcsa_sign_verify(const unsigned char *hash, const struct crypto_bignum *r, const struct crypto_bignum *s, struct crypto_key *csign, int hlen) { +#ifdef CONFIG_MBEDTLS_V3 + /* (mbedtls_ecdsa_context *) */ + mbedtls_ecp_keypair *ecp_kp = mbedtls_pk_ec(*(mbedtls_pk_context *)csign); + if (!ecp_kp) { + return -1; + } + + mbedtls_ecp_group *ecp_kp_grp = &ecp_kp->MBEDTLS_PRIVATE(grp); + mbedtls_ecp_point *ecp_kp_q = &ecp_kp->MBEDTLS_PRIVATE(Q); + int ret = mbedtls_ecdsa_verify(ecp_kp_grp, hash, hlen, + ecp_kp_q, (mbedtls_mpi *)r, (mbedtls_mpi *)s); + if (ret != 0) { + wpa_printf(MSG_ERROR, "ecdsa verification failed"); + return ret; + } +#else mbedtls_pk_context *pkey = (mbedtls_pk_context *)csign; int ret = 0; @@ -794,7 +856,7 @@ int crypto_edcsa_sign_verify(const unsigned char *hash, mbedtls_ecdsa_free(ctx); os_free(ctx); - +#endif return ret; } @@ -805,7 +867,7 @@ void crypto_debug_print_ec_key(const char *title, struct crypto_key *key) mbedtls_ecp_keypair *ecp = mbedtls_pk_ec( *pkey ); u8 x[32], y[32], d[32]; wpa_printf(MSG_ERROR, "curve: %s\n", - mbedtls_ecp_curve_info_from_grp_id( ecp->grp.id )->name ); + mbedtls_ecp_curve_info_from_grp_id( ecp->MBEDTLS_PRIVATE(grp).id )->name ); int len = mbedtls_mpi_size((mbedtls_mpi *)crypto_ec_get_prime((struct crypto_ec *)crypto_ec_get_group_from_key(key))); wpa_printf(MSG_ERROR, "prime len is %d\n", len); @@ -874,7 +936,7 @@ static int pk_write_ec_param( unsigned char **p, unsigned char *start, const char *oid; size_t oid_len; - if( ( ret = mbedtls_oid_get_oid_by_ec_grp( ec->grp.id, &oid, &oid_len ) ) != 0 ) + if( ( ret = mbedtls_oid_get_oid_by_ec_grp( ec->MBEDTLS_PRIVATE(grp).id, &oid, &oid_len ) ) != 0 ) return( ret ); MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_oid( p, start, oid, oid_len ) ); @@ -889,7 +951,7 @@ static int pk_write_ec_pubkey_formatted( unsigned char **p, unsigned char *start size_t len = 0; unsigned char buf[MBEDTLS_ECP_MAX_PT_LEN]; - if( ( ret = mbedtls_ecp_point_write_binary( &ec->grp, &ec->Q, + if( ( ret = mbedtls_ecp_point_write_binary( &ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(Q), format, &len, buf, sizeof( buf ) ) ) != 0 ) { diff --git a/components/wpa_supplicant/src/crypto/crypto_mbedtls-rsa.c b/components/wpa_supplicant/src/crypto/crypto_mbedtls-rsa.c index 2f50f64ea..06f22385a 100644 --- a/components/wpa_supplicant/src/crypto/crypto_mbedtls-rsa.c +++ b/components/wpa_supplicant/src/crypto/crypto_mbedtls-rsa.c @@ -110,6 +110,13 @@ struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len) return (struct crypto_public_key *)pkey; } +#ifdef CONFIG_MBEDTLS_V3 +static int crypto_rng_wrapper(void *ctx, unsigned char *buf, size_t len) +{ + return os_get_random(buf, len); +} +#endif + struct crypto_private_key * crypto_private_key_import(const u8 *key, size_t len, const char *passwd) @@ -120,8 +127,12 @@ struct crypto_private_key * crypto_private_key_import(const u8 *key, return NULL; mbedtls_pk_init(pkey); - +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_pk_parse_key(pkey, key, len, (const unsigned char *)passwd, + passwd ? os_strlen(passwd) : 0, crypto_rng_wrapper, NULL); +#else ret = mbedtls_pk_parse_key(pkey, key, len, (const unsigned char *)passwd, passwd ? os_strlen(passwd) : 0); +#endif if (ret < 0) { wpa_printf(MSG_ERROR, "failed to parse private key"); @@ -210,7 +221,16 @@ int crypto_public_key_encrypt_pkcs1_v15(struct crypto_public_key *key, ret ); goto cleanup; } +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_rsa_pkcs1_encrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, + ctr_drbg, inlen, in, out); + if(ret != 0) { + wpa_printf(MSG_ERROR, " failed ! mbedtls_rsa_pkcs1_encrypt returned -0x%04x", -ret); + goto cleanup; + } + *outlen = mbedtls_rsa_get_len(mbedtls_pk_rsa(*pkey)); +#else ret = mbedtls_rsa_pkcs1_encrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, ctr_drbg, MBEDTLS_RSA_PUBLIC, inlen, in, out); @@ -219,6 +239,7 @@ int crypto_public_key_encrypt_pkcs1_v15(struct crypto_public_key *key, goto cleanup; } *outlen = mbedtls_pk_rsa(*pkey)->len; +#endif cleanup: mbedtls_ctr_drbg_free( ctr_drbg ); @@ -256,11 +277,15 @@ int crypto_private_key_decrypt_pkcs1_v15(struct crypto_private_key *key, if (ret < 0) goto cleanup; - +#ifdef CONFIG_MBEDTLS_V3 + i = mbedtls_rsa_get_len(mbedtls_pk_rsa(*pkey)); + ret = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, + ctr_drbg, &i, in, out, *outlen); +#else i = mbedtls_pk_rsa(*pkey)->len; ret = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, ctr_drbg, MBEDTLS_RSA_PRIVATE, &i, in, out, *outlen); - +#endif *outlen = i; cleanup: @@ -277,6 +302,41 @@ int crypto_private_key_sign_pkcs1(struct crypto_private_key *key, const u8 *in, size_t inlen, u8 *out, size_t *outlen) { +#ifdef CONFIG_MBEDTLS_V3 + int ret; + const char *pers = "rsa_encrypt"; + mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; + mbedtls_entropy_context *entropy = os_malloc(sizeof(*entropy)); + mbedtls_ctr_drbg_context *ctr_drbg = os_malloc(sizeof(*ctr_drbg)); + + if (!pkey || !entropy || !ctr_drbg) { + if (entropy) + os_free(entropy); + if (ctr_drbg) + os_free(ctr_drbg); + return -1; + } + mbedtls_ctr_drbg_init( ctr_drbg ); + mbedtls_entropy_init( entropy ); + ret = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, + entropy, (const unsigned char *) pers, + strlen(pers)); + + if((ret = mbedtls_rsa_pkcs1_sign(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, ctr_drbg, + (mbedtls_pk_rsa(*pkey))->MBEDTLS_PRIVATE(hash_id), + inlen, in, out)) != 0 ) { + wpa_printf(MSG_ERROR, " failed ! mbedtls_rsa_pkcs1_sign returned %d", ret ); + goto cleanup; + } + *outlen = mbedtls_rsa_get_len(mbedtls_pk_rsa(*pkey)); + + cleanup: + mbedtls_ctr_drbg_free( ctr_drbg ); + mbedtls_entropy_free( entropy ); + os_free(entropy); + os_free(ctr_drbg); + return ret; +#else int ret; mbedtls_pk_context *pkey = (mbedtls_pk_context *)key; @@ -289,6 +349,7 @@ int crypto_private_key_sign_pkcs1(struct crypto_private_key *key, *outlen = mbedtls_pk_rsa(*pkey)->len; return 0; +#endif } @@ -336,10 +397,16 @@ int crypto_public_key_decrypt_pkcs1(struct crypto_public_key *key, goto cleanup; } - i = mbedtls_pk_rsa(*pkey)->len; + i = mbedtls_pk_rsa(*pkey)->MBEDTLS_PRIVATE(len); +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_rsa_pkcs1_decrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, + &ctr_drbg, &i, + crypt, plain, *plain_len); +#else ret = mbedtls_rsa_pkcs1_decrypt(mbedtls_pk_rsa(*pkey), mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, &i, crypt, plain, *plain_len); +#endif if( ret != 0 ) { wpa_printf(MSG_ERROR, " failed ! mbedtls_rsa_pkcs1_decrypt returned %d", ret ); diff --git a/components/wpa_supplicant/src/crypto/tls_mbedtls.c b/components/wpa_supplicant/src/crypto/tls_mbedtls.c index 409281ff9..9e0684902 100644 --- a/components/wpa_supplicant/src/crypto/tls_mbedtls.c +++ b/components/wpa_supplicant/src/crypto/tls_mbedtls.c @@ -17,6 +17,10 @@ #include "utils/includes.h" #include "utils/common.h" +#ifdef CONFIG_MBEDTLS_V3 +#include "ssl_misc.h" +#endif + #include "tls/tls.h" #include "crypto/sha1.h" #include "crypto/md5.h" @@ -159,10 +163,15 @@ static int set_pki_context(tls_context_t *tls, const struct tls_connection_param wpa_printf(MSG_ERROR, "mbedtls_x509_crt_parse returned -0x%x", -ret); return ret; } - +#ifdef CONFIG_MBEDTLS_V3 + ret = mbedtls_pk_parse_key(&tls->clientkey, cfg->private_key_blob, cfg->private_key_blob_len, + (const unsigned char *)cfg->private_key_passwd, + cfg->private_key_passwd ? os_strlen(cfg->private_key_passwd) : 0, mbedtls_ctr_drbg_random, &tls->ctr_drbg); +#else ret = mbedtls_pk_parse_key(&tls->clientkey, cfg->private_key_blob, cfg->private_key_blob_len, (const unsigned char *)cfg->private_key_passwd, cfg->private_key_passwd ? os_strlen(cfg->private_key_passwd) : 0); +#endif if (ret < 0) { wpa_printf(MSG_ERROR, "mbedtls_pk_parse_keyfile returned -0x%x", -ret); return ret; @@ -519,7 +528,7 @@ int tls_connection_established(void *tls_ctx, struct tls_connection *conn) { mbedtls_ssl_context *ssl = &conn->tls->ssl; - if (ssl->state == MBEDTLS_SSL_HANDSHAKE_OVER) { + if (ssl->MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_HANDSHAKE_OVER) { return 1; } @@ -566,7 +575,7 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx, } /* State machine just started, get client hello */ - if (tls->ssl.state == MBEDTLS_SSL_CLIENT_HELLO) { + if (tls->ssl.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_CLIENT_HELLO) { ret = mbedtls_ssl_handshake_step(&tls->ssl); } @@ -576,15 +585,15 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx, } /* Already read sever data till hello done */ - if (tls->ssl.state == MBEDTLS_SSL_CLIENT_CERTIFICATE) { + if (tls->ssl.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_CLIENT_CERTIFICATE) { /* Read random data before session completes, not present after handshake */ - if (tls->ssl.handshake) { - os_memcpy(conn->randbytes, tls->ssl.handshake->randbytes, + if (tls->ssl.MBEDTLS_PRIVATE(handshake)) { + os_memcpy(conn->randbytes, tls->ssl.MBEDTLS_PRIVATE(handshake)->randbytes, TLS_RANDOM_LEN * 2); } /* trigger state machine multiple times to reach till finish */ - while (tls->ssl.state <= MBEDTLS_SSL_CLIENT_FINISHED) { + while (tls->ssl.MBEDTLS_PRIVATE(state) <= MBEDTLS_SSL_CLIENT_FINISHED) { ret = mbedtls_ssl_handshake_step(&tls->ssl); if (ret < 0) { break; @@ -593,8 +602,8 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx, } /* Trigger state machine till handshake is complete or error occures */ - if (tls->ssl.state == MBEDTLS_SSL_FLUSH_BUFFERS) { - while (tls->ssl.state <= MBEDTLS_SSL_HANDSHAKE_OVER) { + if (tls->ssl.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_FLUSH_BUFFERS) { + while (tls->ssl.MBEDTLS_PRIVATE(state) <= MBEDTLS_SSL_HANDSHAKE_OVER) { ret = mbedtls_ssl_handshake_step(&tls->ssl); if (ret < 0) { break; @@ -663,8 +672,8 @@ struct wpabuf * tls_connection_decrypt(void *tls_ctx, int tls_connection_resumed(void *tls_ctx, struct tls_connection *conn) { - if (conn && conn->tls && conn->tls->ssl.handshake) { - return conn->tls->ssl.handshake->resume; + if (conn && conn->tls && conn->tls->ssl.MBEDTLS_PRIVATE(handshake)) { + return conn->tls->ssl.MBEDTLS_PRIVATE(handshake)->resume; } return 0; @@ -799,14 +808,14 @@ static int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, int ret; u8 seed[2 * TLS_RANDOM_LEN]; mbedtls_ssl_context *ssl = &conn->tls->ssl; - mbedtls_ssl_transform *transform = ssl->transform; + mbedtls_ssl_transform *transform = ssl->MBEDTLS_PRIVATE(transform); if (!ssl || !transform) { wpa_printf(MSG_ERROR, "TLS: %s, session ingo is null", __func__); return -1; } - if (ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) { - wpa_printf(MSG_ERROR, "TLS: %s, incorrect tls state=%d", __func__, ssl->state); + if (ssl->MBEDTLS_PRIVATE(state) != MBEDTLS_SSL_HANDSHAKE_OVER) { + wpa_printf(MSG_ERROR, "TLS: %s, incorrect tls state=%d", __func__, ssl->MBEDTLS_PRIVATE(state)); return -1; } @@ -818,16 +827,22 @@ static int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, } wpa_hexdump_key(MSG_MSGDUMP, "random", seed, 2 * TLS_RANDOM_LEN); - wpa_hexdump_key(MSG_MSGDUMP, "master", ssl->session->master, TLS_MASTER_SECRET_LEN); + wpa_hexdump_key(MSG_MSGDUMP, "master", ssl->MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(master), TLS_MASTER_SECRET_LEN); + +#ifdef CONFIG_MBEDTLS_V3 + mbedtls_md_type_t type = ssl->MBEDTLS_PRIVATE(handshake)->ciphersuite_info->mac; +#else + mbedtls_md_type_t type = transform->ciphersuite_info->mac; +#endif - if (transform->ciphersuite_info->mac == MBEDTLS_MD_SHA384) { - ret = tls_prf_sha384(ssl->session->master, TLS_MASTER_SECRET_LEN, + if (type == MBEDTLS_MD_SHA384) { + ret = tls_prf_sha384(ssl->MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(master), TLS_MASTER_SECRET_LEN, label, seed, 2 * TLS_RANDOM_LEN, out, out_len); - } else if (transform->ciphersuite_info->mac == MBEDTLS_MD_SHA256) { - ret = tls_prf_sha256(ssl->session->master, TLS_MASTER_SECRET_LEN, + } else if (type == MBEDTLS_MD_SHA256) { + ret = tls_prf_sha256(ssl->MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(master), TLS_MASTER_SECRET_LEN, label, seed, 2 * TLS_RANDOM_LEN, out, out_len); } else { - ret = tls_prf_sha1_md5(ssl->session->master, TLS_MASTER_SECRET_LEN, + ret = tls_prf_sha1_md5(ssl->MBEDTLS_PRIVATE(session)->MBEDTLS_PRIVATE(master), TLS_MASTER_SECRET_LEN, label, seed, 2 * TLS_RANDOM_LEN, out, out_len); } @@ -864,14 +879,14 @@ int tls_connection_get_random(void *tls_ctx, struct tls_connection *conn, mbedtls_ssl_context *ssl = &conn->tls->ssl; os_memset(data, 0, sizeof(*data)); - if (ssl->state == MBEDTLS_SSL_CLIENT_HELLO) { + if (ssl->MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_CLIENT_HELLO) { return -1; } data->client_random = conn->randbytes; data->client_random_len = TLS_RANDOM_LEN; - if (ssl->state != MBEDTLS_SSL_SERVER_HELLO) { + if (ssl->MBEDTLS_PRIVATE(state) != MBEDTLS_SSL_SERVER_HELLO) { data->server_random = conn->randbytes + TLS_RANDOM_LEN; data->server_random_len = TLS_RANDOM_LEN; } diff --git a/components/wpa_supplicant/src/utils/common.h b/components/wpa_supplicant/src/utils/common.h index 9d9cef3fd..a65e0edf0 100644 --- a/components/wpa_supplicant/src/utils/common.h +++ b/components/wpa_supplicant/src/utils/common.h @@ -443,6 +443,9 @@ struct wpa_freq_range_list { }; #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#ifndef TEST_FAIL +#define TEST_FAIL() 0 +#endif void wpa_bin_clear_free(void *bin, size_t len); int int_array_len(const int *a); From 02ab0e0a39468ebba8cfccbb54c284ea28646e00 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Fri, 18 Apr 2025 14:17:04 +0800 Subject: [PATCH 57/59] feat: Support mbedtls V3 for https_mbedtls CI --- examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv2 | 1 + examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv3 | 1 + 2 files changed, 2 insertions(+) create mode 100644 examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv2 create mode 100644 examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv3 diff --git a/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv2 b/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv2 new file mode 100644 index 000000000..dac573bd2 --- /dev/null +++ b/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv2 @@ -0,0 +1 @@ +CONFIG_MBEDTLS_V2=y \ No newline at end of file diff --git a/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv3 b/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv3 new file mode 100644 index 000000000..8526fb91b --- /dev/null +++ b/examples/protocols/https_mbedtls/sdkconfig.ci.mbedtlsv3 @@ -0,0 +1 @@ +CONFIG_MBEDTLS_V3=y \ No newline at end of file From a2a95350b5d0ab559446c6f23a4774af67a750c3 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 30 Apr 2025 15:03:30 +0800 Subject: [PATCH 58/59] feat(esp-tls): update to upstream 3.6.3 --- components/esp-tls/esp_tls_mbedtls.c | 4 ++++ components/mbedtls/mbedtls_v3/Kconfig | 6 ++++++ .../mbedtls_v3/port/include/mbedtls/esp_config.h | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index b8791836a..756a0a05e 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -389,6 +389,10 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t return ESP_ERR_MBEDTLS_SSL_SET_HOSTNAME_FAILED; } free(use_host); +#ifdef CONFIG_MBEDTLS_V3 + } else { + mbedtls_ssl_set_hostname(&tls->ssl, NULL); +#endif } if ((ret = mbedtls_ssl_config_defaults(&tls->conf, diff --git a/components/mbedtls/mbedtls_v3/Kconfig b/components/mbedtls/mbedtls_v3/Kconfig index 961cc4707..fffa5f82b 100644 --- a/components/mbedtls/mbedtls_v3/Kconfig +++ b/components/mbedtls/mbedtls_v3/Kconfig @@ -1148,3 +1148,9 @@ config MBEDTLS_USE_CRYPTO_ROM_IMPL the flash footprint and hence care must be taken to keep some reserved space for the application binary in flash layout. + config MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION + bool "Allow weak certificate verification" + default n + help + This options allows weak certificate verification by skipping the hostname verification. + It is not recommended to use this option. diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h index 579b6a37f..0ce60511d 100644 --- a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h @@ -2078,6 +2078,21 @@ #undef MBEDTLS_ERROR_C #endif +/** + * \def MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME + * + * Caller: library/ssl_tls.c + * + * Allow weak certificate verification without a hostname. + * This option is not recommended for production use. + */ + +#if CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION +#define MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME +#else +#undef MBEDTLS_SSL_CLI_ALLOW_WEAK_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME +#endif + /** * \def MBEDTLS_GCM_C * From 42240423953ada23aef71bddccbf0b78bc77d0a5 Mon Sep 17 00:00:00 2001 From: Xu Chun Guang Date: Thu, 8 May 2025 12:10:15 +0800 Subject: [PATCH 59/59] feat: Perform an exponentiation by the algorithm of mbedtls V2 which is faster --- components/mbedtls/mbedtls_v3/CMakeLists.txt | 4 + components/mbedtls/mbedtls_v3/Kconfig | 7 + components/mbedtls/mbedtls_v3/component.mk | 2 +- .../mbedtls/mbedtls_v3/port/esp_bignum.c | 934 +++++++----------- .../port/include/mbedtls/esp_config.h | 8 +- 5 files changed, 378 insertions(+), 577 deletions(-) diff --git a/components/mbedtls/mbedtls_v3/CMakeLists.txt b/components/mbedtls/mbedtls_v3/CMakeLists.txt index dbfc6dc24..56ce65f5b 100644 --- a/components/mbedtls/mbedtls_v3/CMakeLists.txt +++ b/components/mbedtls/mbedtls_v3/CMakeLists.txt @@ -196,6 +196,10 @@ target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_mem.c" "${current_dir}/port/esp_timing.c" "${current_dir}/port/esp_hardware.c") +if(CONFIG_MBEDTLS_MPI_EXP_MOD_ALT) + target_sources(mbedcrypto PRIVATE "${current_dir}/port/esp_bignum.c") +endif() + if(CONFIG_SOC_AES_SUPPORTED) target_sources(mbedcrypto PRIVATE "${current_dir}/port/aes/esp_aes_xts.c" "${current_dir}/port/aes/esp_aes_common.c" diff --git a/components/mbedtls/mbedtls_v3/Kconfig b/components/mbedtls/mbedtls_v3/Kconfig index fffa5f82b..dfb873eb7 100644 --- a/components/mbedtls/mbedtls_v3/Kconfig +++ b/components/mbedtls/mbedtls_v3/Kconfig @@ -639,6 +639,13 @@ config MBEDTLS_SHA3_C Enabling this configuration option increases the flash footprint by almost 4KB. +config MBEDTLS_MPI_EXP_MOD_ALT + bool "Enable the alternative exponentiation" + default y + help + Perform an exponentiation by the algorithm of mbedtls V2, + which is faster when calling "mbedtls_mpi_exp_mod()". + choice MBEDTLS_TLS_MODE bool "TLS Protocol Role" default MBEDTLS_TLS_SERVER_AND_CLIENT diff --git a/components/mbedtls/mbedtls_v3/component.mk b/components/mbedtls/mbedtls_v3/component.mk index 66da38fb2..227e1bcf0 100644 --- a/components/mbedtls/mbedtls_v3/component.mk +++ b/components/mbedtls/mbedtls_v3/component.mk @@ -10,7 +10,7 @@ COMPONENT_SRCDIRS := $(CURRENT_DIR)/mbedtls/library $(CURRENT_DIR)/port COMPONENT_OBJEXCLUDE := $(CURRENT_DIR)/mbedtls/library/net_sockets.o -ifndef CONFIG_MBEDTLS_HARDWARE_MPI +ifndef CONFIG_MBEDTLS_MPI_EXP_MOD_ALT COMPONENT_OBJEXCLUDE += $(CURRENT_DIR)/port/esp_bignum.o endif diff --git a/components/mbedtls/mbedtls_v3/port/esp_bignum.c b/components/mbedtls/mbedtls_v3/port/esp_bignum.c index e9596bec8..0fd608a27 100644 --- a/components/mbedtls/mbedtls_v3/port/esp_bignum.c +++ b/components/mbedtls/mbedtls_v3/port/esp_bignum.c @@ -1,455 +1,246 @@ /* - * Multi-precision integer library - * ESP-IDF hardware accelerated parts based on mbedTLS implementation + * Multi-precision integer library * - * SPDX-FileCopyrightText: The Mbed TLS Contributors - * - * SPDX-License-Identifier: Apache-2.0 - * - * SPDX-FileContributor: 2016-2023 Espressif Systems (Shanghai) CO LTD + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ -#include -#include -#include -#include -#include -#include -#include - -#include "esp_system.h" -#include "esp_log.h" -#include "esp_attr.h" -#include "esp_intr_alloc.h" -#if CONFIG_PM_ENABLE -#include "esp_pm.h" -#endif - -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "soc/hwcrypto_periph.h" -#include "soc/periph_defs.h" -#include "soc/soc_caps.h" - -#include "bignum_impl.h" - -#include - - -/* Some implementation notes: +/* + * The following sources were referenced in the design of this Multi-precision + * Integer library: * - * - Naming convention x_words, y_words, z_words for number of words (limbs) used in a particular - * bignum. This number may be less than the size of the bignum + * [1] Handbook of Applied Cryptography - 1997 + * Menezes, van Oorschot and Vanstone * - * - Naming convention hw_words for the hardware length of the operation. This number maybe be rounded up - * for targets that requres this (e.g. ESP32), and may be larger than any of the numbers - * involved in the calculation. + * [2] Multi-Precision Math + * Tom St Denis + * https://github.com/libtom/libtommath/blob/develop/tommath.pdf + * + * [3] GNU Multi-Precision Arithmetic Library + * https://gmplib.org/manual/index.html * - * - Timing behaviour of these functions will depend on the length of the inputs. This is fundamentally - * the same constraint as the software mbedTLS implementations, and relies on the same - * countermeasures (exponent blinding, etc) which are used in mbedTLS. */ -static const __attribute__((unused)) char *TAG = "bignum"; +#include "common.h" -#define ciL (sizeof(mbedtls_mpi_uint)) /* chars in limb */ -#define biL (ciL << 3) /* bits in limb */ +#if defined(MBEDTLS_BIGNUM_C) -#if defined(CONFIG_MBEDTLS_MPI_USE_INTERRUPT) -static SemaphoreHandle_t op_complete_sem; -#if defined(CONFIG_PM_ENABLE) -static esp_pm_lock_handle_t s_pm_cpu_lock; -static esp_pm_lock_handle_t s_pm_sleep_lock; -#endif +#include "mbedtls/bignum.h" +#include "bignum_core.h" +#include "bignum_internal.h" +#include "bn_mul.h" +#include "mbedtls/platform_util.h" +#include "mbedtls/error.h" +#include "constant_time_internal.h" -static IRAM_ATTR void esp_mpi_complete_isr(void *arg) -{ - BaseType_t higher_woken; - esp_mpi_interrupt_clear(); +#include +#include - xSemaphoreGiveFromISR(op_complete_sem, &higher_woken); - if (higher_woken) { - portYIELD_FROM_ISR(); - } +#include "mbedtls/platform.h" + +#if defined(MBEDTLS_MPI_EXP_MOD_ALT) +#define MBEDTLS_INTERNAL_VALIDATE_RET( cond, ret ) \ + do { \ + if( !(cond) ) \ + { \ + return( ret ); \ + } \ + } while( 0 ) + +/* Internal macro meant to be called only from within the library. */ +#define MBEDTLS_INTERNAL_VALIDATE( cond ) \ + do { \ + if( !(cond) ) \ + { \ + return; \ + } \ + } while( 0 ) + +#define MPI_VALIDATE_RET( cond ) \ + MBEDTLS_INTERNAL_VALIDATE_RET( cond, MBEDTLS_ERR_MPI_BAD_INPUT_DATA ) +#define MPI_VALIDATE( cond ) \ + MBEDTLS_INTERNAL_VALIDATE( cond ) + +#define MULADDC_INIT \ +{ \ + mbedtls_mpi_uint s0, s1, b0, b1; \ + mbedtls_mpi_uint r0, r1, rx, ry; \ + b0 = ( b << biH ) >> biH; \ + b1 = ( b >> biH ); + +#define MULADDC_CORE \ + s0 = ( *s << biH ) >> biH; \ + s1 = ( *s >> biH ); s++; \ + rx = s0 * b1; r0 = s0 * b0; \ + ry = s1 * b0; r1 = s1 * b1; \ + r1 += ( rx >> biH ); \ + r1 += ( ry >> biH ); \ + rx <<= biH; ry <<= biH; \ + r0 += rx; r1 += (r0 < rx); \ + r0 += ry; r1 += (r0 < ry); \ + r0 += c; r1 += (r0 < c); \ + r0 += *d; r1 += (r0 < *d); \ + c = r1; *(d++) = r0; + +#define MULADDC_STOP \ } - -static esp_err_t esp_mpi_isr_initialise(void) +static void mpi_sub_hlp( size_t n, mbedtls_mpi_uint *s, mbedtls_mpi_uint *d ) { - esp_mpi_interrupt_clear(); - esp_mpi_interrupt_enable(true); - if (op_complete_sem == NULL) { - static StaticSemaphore_t op_sem_buf; - op_complete_sem = xSemaphoreCreateBinaryStatic(&op_sem_buf); - if (op_complete_sem == NULL) { - ESP_LOGE(TAG, "Failed to create intr semaphore"); - return ESP_FAIL; - } + size_t i; + mbedtls_mpi_uint c, z; - esp_err_t ret; - ret = esp_intr_alloc(ETS_RSA_INTR_SOURCE, 0, esp_mpi_complete_isr, NULL, NULL); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to allocate RSA interrupt %d", ret); - - // This should be treated as fatal error as this API would mostly - // be invoked within mbedTLS interface. There is no way for the system - // to proceed if the MPI interrupt allocation fails here. - abort(); - } + for( i = c = 0; i < n; i++, s++, d++ ) + { + z = ( *d < c ); *d -= c; + c = ( *d < *s ) + z; *d -= *s; } - /* MPI is clocked proportionally to CPU clock, take power management lock */ -#ifdef CONFIG_PM_ENABLE - if (s_pm_cpu_lock == NULL) { - if (esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "mpi_sleep", &s_pm_sleep_lock) != ESP_OK) { - ESP_LOGE(TAG, "Failed to create PM sleep lock"); - return ESP_FAIL; - } - if (esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "mpi_cpu", &s_pm_cpu_lock) != ESP_OK) { - ESP_LOGE(TAG, "Failed to create PM CPU lock"); - return ESP_FAIL; - } + while( c != 0 ) + { + z = ( *d < c ); *d -= c; + c = z; d++; } - esp_pm_lock_acquire(s_pm_cpu_lock); - esp_pm_lock_acquire(s_pm_sleep_lock); -#endif - - return ESP_OK; } -static int esp_mpi_wait_intr(void) -{ - if (!xSemaphoreTake(op_complete_sem, 2000 / portTICK_PERIOD_MS)) { - ESP_LOGE("MPI", "Timed out waiting for completion of MPI Interrupt"); - return -1; - } - -#ifdef CONFIG_PM_ENABLE - esp_pm_lock_release(s_pm_cpu_lock); - esp_pm_lock_release(s_pm_sleep_lock); -#endif // CONFIG_PM_ENABLE - esp_mpi_interrupt_enable(false); - - return 0; -} +void mpi_mul_hlp( size_t i, mbedtls_mpi_uint *s, mbedtls_mpi_uint *d, mbedtls_mpi_uint b ); -#endif // CONFIG_MBEDTLS_MPI_USE_INTERRUPT - -/* Convert bit count to word count - */ -static inline size_t bits_to_words(size_t bits) +void mpi_mul_hlp( size_t i, mbedtls_mpi_uint *s, mbedtls_mpi_uint *d, mbedtls_mpi_uint b ) { - return (bits + 31) / 32; -} + mbedtls_mpi_uint c = 0, t = 0; -/* Return the number of words actually used to represent an mpi - number. -*/ -#if defined(MBEDTLS_MPI_EXP_MOD_ALT) || defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) -static size_t mpi_words(const mbedtls_mpi *mpi) -{ - for (size_t i = mpi->MBEDTLS_PRIVATE(n); i > 0; i--) { - if (mpi->MBEDTLS_PRIVATE(p[i - 1]) != 0) { - return i; - } +#if defined(MULADDC_HUIT) + for( ; i >= 8; i -= 8 ) + { + MULADDC_INIT + MULADDC_HUIT + MULADDC_STOP } - return 0; -} - -#endif //(MBEDTLS_MPI_EXP_MOD_ALT || MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) - -/** - * - * There is a need for the value of integer N' such that B^-1(B-1)-N^-1N'=1, - * where B^-1(B-1) mod N=1. Actually, only the least significant part of - * N' is needed, hence the definition N0'=N' mod b. We reproduce below the - * simple algorithm from an article by Dusse and Kaliski to efficiently - * find N0' from N0 and b - */ -static mbedtls_mpi_uint modular_inverse(const mbedtls_mpi *M) -{ - int i; - uint64_t t = 1; - uint64_t two_2_i_minus_1 = 2; /* 2^(i-1) */ - uint64_t two_2_i = 4; /* 2^i */ - uint64_t N = M->MBEDTLS_PRIVATE(p[0]); - - for (i = 2; i <= 32; i++) { - if ((mbedtls_mpi_uint) N * t % two_2_i >= two_2_i_minus_1) { - t += two_2_i_minus_1; - } - two_2_i_minus_1 <<= 1; - two_2_i <<= 1; + for( ; i > 0; i-- ) + { + MULADDC_INIT + MULADDC_CORE + MULADDC_STOP + } +#else /* MULADDC_HUIT */ + for( ; i >= 16; i -= 16 ) + { + MULADDC_INIT + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_STOP } - return (mbedtls_mpi_uint)(UINT32_MAX - t + 1); -} - -/* Calculate Rinv = RR^2 mod M, where: - * - * R = b^n where b = 2^32, n=num_words, - * R = 2^N (where N=num_bits) - * RR = R^2 = 2^(2*N) (where N=num_bits=num_words*32) - * - * This calculation is computationally expensive (mbedtls_mpi_mod_mpi) - * so caller should cache the result where possible. - * - * DO NOT call this function while holding esp_mpi_enable_hardware_hw_op(). - * - */ -static int calculate_rinv(mbedtls_mpi *Rinv, const mbedtls_mpi *M, int num_words) -{ - int ret; - size_t num_bits = num_words * 32; - mbedtls_mpi RR; - mbedtls_mpi_init(&RR); - MBEDTLS_MPI_CHK(mbedtls_mpi_set_bit(&RR, num_bits * 2, 1)); - MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(Rinv, &RR, M)); - -cleanup: - mbedtls_mpi_free(&RR); - - return ret; -} - - - - - - -/* Z = (X * Y) mod M - - Not an mbedTLS function -*/ -int esp_mpi_mul_mpi_mod(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M) -{ - int ret = 0; - - size_t x_bits = mbedtls_mpi_bitlen(X); - size_t y_bits = mbedtls_mpi_bitlen(Y); - size_t m_bits = mbedtls_mpi_bitlen(M); - size_t z_bits = MIN(m_bits, x_bits + y_bits); - size_t x_words = bits_to_words(x_bits); - size_t y_words = bits_to_words(y_bits); - size_t m_words = bits_to_words(m_bits); - size_t z_words = bits_to_words(z_bits); - size_t hw_words = esp_mpi_hardware_words(MAX(x_words, MAX(y_words, m_words))); /* longest operand */ - mbedtls_mpi Rinv; - mbedtls_mpi_uint Mprime; - - /* Calculate and load the first stage montgomery multiplication */ - mbedtls_mpi_init(&Rinv); - MBEDTLS_MPI_CHK(calculate_rinv(&Rinv, M, hw_words)); - Mprime = modular_inverse(M); - - esp_mpi_enable_hardware_hw_op(); - /* Load and start a (X * Y) mod M calculation */ - esp_mpi_mul_mpi_mod_hw_op(X, Y, M, &Rinv, Mprime, hw_words); - - MBEDTLS_MPI_CHK(mbedtls_mpi_grow(Z, z_words)); - - esp_mpi_read_result_hw_op(Z, z_words); - Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); + for( ; i >= 8; i -= 8 ) + { + MULADDC_INIT + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE -cleanup: - mbedtls_mpi_free(&Rinv); - esp_mpi_disable_hardware_hw_op(); + MULADDC_CORE MULADDC_CORE + MULADDC_CORE MULADDC_CORE + MULADDC_STOP + } - return ret; -} + for( ; i > 0; i-- ) + { + MULADDC_INIT + MULADDC_CORE + MULADDC_STOP + } +#endif /* MULADDC_HUIT */ -#if defined(MBEDTLS_MPI_EXP_MOD_ALT) || defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) + t++; -#ifdef ESP_MPI_USE_MONT_EXP -/* - * Return the most significant one-bit. - */ -static size_t mbedtls_mpi_msb( const mbedtls_mpi *X ) -{ - int i, j; - if (X != NULL && X->MBEDTLS_PRIVATE(n) != 0) { - for (i = X->MBEDTLS_PRIVATE(n) - 1; i >= 0; i--) { - if (X->MBEDTLS_PRIVATE(p[i]) != 0) { - for (j = biL - 1; j >= 0; j--) { - if ((X->MBEDTLS_PRIVATE(p[i]) & (1 << j)) != 0) { - return (i * biL) + j; - } - } - } - } + do { + *d += c; c = ( *d < c ); d++; } - return 0; + while( c != 0 ); } - /* - * Montgomery exponentiation: Z = X ^ Y mod M (HAC 14.94) + * Fast Montgomery initialization (thanks to Tom St Denis) */ -static int mpi_montgomery_exp_calc( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, - mbedtls_mpi *Rinv, - size_t hw_words, - mbedtls_mpi_uint Mprime ) +static void mpi_montg_init( mbedtls_mpi_uint *mm, const mbedtls_mpi *N ) { - int ret = 0; - mbedtls_mpi X_, one; - - mbedtls_mpi_init(&X_); - mbedtls_mpi_init(&one); - if ( ( ( ret = mbedtls_mpi_grow(&one, hw_words) ) != 0 ) || - ( ( ret = mbedtls_mpi_set_bit(&one, 0, 1) ) != 0 ) ) { - goto cleanup2; - } - - // Algorithm from HAC 14.94 - { - // 0 determine t (highest bit set in y) - int t = mbedtls_mpi_msb(Y); - - esp_mpi_enable_hardware_hw_op(); - - // 1.1 x_ = mont(x, R^2 mod m) - // = mont(x, rb) - MBEDTLS_MPI_CHK( esp_mont_hw_op(&X_, X, Rinv, M, Mprime, hw_words, false) ); - - // 1.2 z = R mod m - // now z = R mod m = Mont (R^2 mod m, 1) mod M (as Mont(x) = X&R^-1 mod M) - MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Rinv, &one, M, Mprime, hw_words, true) ); - - // 2 for i from t down to 0 - for (int i = t; i >= 0; i--) { - // 2.1 z = mont(z,z) - if (i != t) { // skip on the first iteration as is still unity - MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, Z, M, Mprime, hw_words, true) ); - } - - // 2.2 if y[i] = 1 then z = mont(A, x_) - if (mbedtls_mpi_get_bit(Y, i)) { - MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, &X_, M, Mprime, hw_words, true) ); - } - } + mbedtls_mpi_uint x, m0 = N->p[0]; + unsigned int i; - // 3 z = Mont(z, 1) - MBEDTLS_MPI_CHK( esp_mont_hw_op(Z, Z, &one, M, Mprime, hw_words, true) ); - } + x = m0; + x += ( ( m0 + 2 ) & 4 ) << 1; -cleanup: - esp_mpi_disable_hardware_hw_op(); + for( i = biL; i >= 8; i /= 2 ) + x *= ( 2 - ( m0 * x ) ); -cleanup2: - mbedtls_mpi_free(&X_); - mbedtls_mpi_free(&one); - return ret; + *mm = ~x + 1; } -#endif //USE_MONT_EXPONENATIATION - /* - * Z = X ^ Y mod M - * - * _Rinv is optional pre-calculated version of Rinv (via calculate_rinv()). - * - * (See RSA Accelerator section in Technical Reference for more about Mprime, Rinv) - * + * Montgomery multiplication: A = A * B * R^-1 mod N (HAC 14.36) */ -static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, mbedtls_mpi *_Rinv ) +static int mpi_montmul( mbedtls_mpi *A, const mbedtls_mpi *B, const mbedtls_mpi *N, mbedtls_mpi_uint mm, + const mbedtls_mpi *T ) { - int ret = 0; + size_t i, n, m; + mbedtls_mpi_uint u0, u1, *d; - mbedtls_mpi Rinv_new; /* used if _Rinv == NULL */ - mbedtls_mpi *Rinv; /* points to _Rinv (if not NULL) othwerwise &RR_new */ - mbedtls_mpi_uint Mprime; + if( T->n < N->n + 1 || T->p == NULL ) + return( MBEDTLS_ERR_MPI_BAD_INPUT_DATA ); - size_t x_words = mpi_words(X); - size_t y_words = mpi_words(Y); - size_t m_words = mpi_words(M); + memset( T->p, 0, T->n * ciL ); - /* "all numbers must be the same length", so choose longest number - as cardinal length of operation... - */ - size_t num_words = esp_mpi_hardware_words(MAX(m_words, MAX(x_words, y_words))); + d = T->p; + n = N->n; + m = ( B->n < n ) ? B->n : n; - if (num_words * 32 > SOC_RSA_MAX_BIT_LEN) { - return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; - } - - if (mbedtls_mpi_cmp_int(M, 0) <= 0 || (M->MBEDTLS_PRIVATE(p[0]) & 1) == 0) { - return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; - } - - if (mbedtls_mpi_cmp_int(Y, 0) < 0) { - return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; - } + for( i = 0; i < n; i++ ) + { + /* + * T = (T + u0*B + u1*N) / 2^biL + */ + u0 = A->p[i]; + u1 = ( d[0] + u0 * B->p[0] ) * mm; - if (mbedtls_mpi_cmp_int(Y, 0) == 0) { - return mbedtls_mpi_lset(Z, 1); - } + mpi_mul_hlp( m, B->p, d, u0 ); + mpi_mul_hlp( n, N->p, d, u1 ); - /* Determine RR pointer, either _RR for cached value - or local RR_new */ - if (_Rinv == NULL) { - mbedtls_mpi_init(&Rinv_new); - Rinv = &Rinv_new; - } else { - Rinv = _Rinv; - } - if (Rinv->MBEDTLS_PRIVATE(p) == NULL) { - MBEDTLS_MPI_CHK(calculate_rinv(Rinv, M, num_words)); + *d++ = u0; d[n + 1] = 0; } - Mprime = modular_inverse(M); - - // Montgomery exponentiation: Z = X ^ Y mod M (HAC 14.94) -#ifdef ESP_MPI_USE_MONT_EXP - ret = mpi_montgomery_exp_calc(Z, X, Y, M, Rinv, num_words, Mprime) ; - MBEDTLS_MPI_CHK(ret); -#else - esp_mpi_enable_hardware_hw_op(); - -#if defined (CONFIG_MBEDTLS_MPI_USE_INTERRUPT) - if (esp_mpi_isr_initialise() != ESP_OK) { - ret = -1; - esp_mpi_disable_hardware_hw_op(); - goto cleanup; - } -#endif + memcpy( A->p, d, ( n + 1 ) * ciL ); - esp_mpi_exp_mpi_mod_hw_op(X, Y, M, Rinv, Mprime, num_words); - ret = mbedtls_mpi_grow(Z, m_words); - if (ret != 0) { - esp_mpi_disable_hardware_hw_op(); - goto cleanup; - } + if( mbedtls_mpi_cmp_abs( A, N ) >= 0 ) + mpi_sub_hlp( n, N->p, A->p ); + else + /* prevent timing attacks */ + mpi_sub_hlp( n, A->p, T->p ); -#if defined(CONFIG_MBEDTLS_MPI_USE_INTERRUPT) - ret = esp_mpi_wait_intr(); - if (ret != 0) { - esp_mpi_disable_hardware_hw_op(); - goto cleanup; - } -#endif //CONFIG_MBEDTLS_MPI_USE_INTERRUPT + return( 0 ); +} - esp_mpi_read_result_hw_op(Z, m_words); - esp_mpi_disable_hardware_hw_op(); -#endif +/* + * Montgomery reduction: A = A * R^-1 mod N + */ +static int mpi_montred( mbedtls_mpi *A, const mbedtls_mpi *N, + mbedtls_mpi_uint mm, const mbedtls_mpi *T ) +{ + mbedtls_mpi_uint z = 1; + mbedtls_mpi U; - // Compensate for negative X - if (X->MBEDTLS_PRIVATE(s) == -1 && (Y->MBEDTLS_PRIVATE(p[0]) & 1) != 0) { - Z->MBEDTLS_PRIVATE(s) = -1; - MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(Z, M, Z)); - } else { - Z->MBEDTLS_PRIVATE(s) = 1; - } + U.n = U.s = (int) z; + U.p = &z; -cleanup: - if (_Rinv == NULL) { - mbedtls_mpi_free(&Rinv_new); - } - return ret; + return( mpi_montmul( A, &U, N, mm, T ) ); } -#endif /* (MBEDTLS_MPI_EXP_MOD_ALT || MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) */ - /* * Sliding-window exponentiation: X = A^E mod N (HAC 14.85) */ @@ -458,220 +249,213 @@ int mbedtls_mpi_exp_mod( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi *_RR ) { int ret; -#if defined(MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK) - /* Try hardware API first and then fallback to software */ - ret = esp_mpi_exp_mod( X, A, E, N, _RR ); - if( ret == MBEDTLS_ERR_MPI_NOT_ACCEPTABLE ) { - ret = mbedtls_mpi_exp_mod_soft( X, A, E, N, _RR ); - } -#else - /* Hardware approach */ - ret = esp_mpi_exp_mod( X, A, E, N, _RR ); -#endif - /* Note: For software only approach, it gets handled in mbedTLS library. - This file is not part of build objects for that case */ + size_t wbits, wsize, one = 1; + size_t i, j, nblimbs; + size_t bufsize, nbits; + mbedtls_mpi_uint ei, mm, state; + mbedtls_mpi RR, T, W[ 2 << MBEDTLS_MPI_WINDOW_SIZE ], Apos; + int neg; - return ret; -} + MPI_VALIDATE_RET( X != NULL ); + MPI_VALIDATE_RET( A != NULL ); + MPI_VALIDATE_RET( E != NULL ); + MPI_VALIDATE_RET( N != NULL ); -#if defined(MBEDTLS_MPI_MUL_MPI_ALT) /* MBEDTLS_MPI_MUL_MPI_ALT */ + if( mbedtls_mpi_cmp_int( N, 0 ) <= 0 || ( N->p[0] & 1 ) == 0 ) + return( MBEDTLS_ERR_MPI_BAD_INPUT_DATA ); -static int mpi_mult_mpi_failover_mod_mult( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t z_words); -static int mpi_mult_mpi_overlong(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t y_words, size_t z_words); + if( mbedtls_mpi_cmp_int( E, 0 ) < 0 ) + return( MBEDTLS_ERR_MPI_BAD_INPUT_DATA ); -/* Z = X * Y */ -int mbedtls_mpi_mul_mpi( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y ) -{ - int ret = 0; - size_t x_bits = mbedtls_mpi_bitlen(X); - size_t y_bits = mbedtls_mpi_bitlen(Y); - size_t x_words = bits_to_words(x_bits); - size_t y_words = bits_to_words(y_bits); - size_t z_words = bits_to_words(x_bits + y_bits); - size_t hw_words = esp_mpi_hardware_words(MAX(x_words, y_words)); // length of one operand in hardware - - /* Short-circuit eval if either argument is 0 or 1. - - This is needed as the mpi modular division - argument will sometimes call in here when one - argument is too large for the hardware unit, but the other - argument is zero or one. - */ - if (x_bits == 0 || y_bits == 0) { - mbedtls_mpi_lset(Z, 0); - return 0; - } - if (x_bits == 1) { - ret = mbedtls_mpi_copy(Z, Y); - Z->MBEDTLS_PRIVATE(s) *= X->MBEDTLS_PRIVATE(s); - return ret; - } - if (y_bits == 1) { - ret = mbedtls_mpi_copy(Z, X); - Z->MBEDTLS_PRIVATE(s) *= Y->MBEDTLS_PRIVATE(s); - return ret; - } - - /* Grow Z to result size early, avoid interim allocations */ - MBEDTLS_MPI_CHK( mbedtls_mpi_grow(Z, z_words) ); - - /* If either factor is over 2048 bits, we can't use the standard hardware multiplier - (it assumes result is double longest factor, and result is max 4096 bits.) - - However, we can fail over to mod_mult for up to 4096 bits of result (modulo - multiplication doesn't have the same restriction, so result is simply the - number of bits in X plus number of bits in in Y.) - */ - if (hw_words * 32 > SOC_RSA_MAX_BIT_LEN/2) { - if (z_words * 32 <= SOC_RSA_MAX_BIT_LEN) { - /* Note: it's possible to use mpi_mult_mpi_overlong - for this case as well, but it's very slightly - slower and requires a memory allocation. - */ - return mpi_mult_mpi_failover_mod_mult(Z, X, Y, z_words); - } else { - /* Still too long for the hardware unit... */ - if (y_words > x_words) { - return mpi_mult_mpi_overlong(Z, X, Y, y_words, z_words); - } else { - return mpi_mult_mpi_overlong(Z, Y, X, x_words, z_words); - } - } - } + /* + * Init temps and window size + */ + mpi_montg_init( &mm, N ); + mbedtls_mpi_init( &RR ); mbedtls_mpi_init( &T ); + mbedtls_mpi_init( &Apos ); + memset( W, 0, sizeof( W ) ); - /* Otherwise, we can use the (faster) multiply hardware unit */ - esp_mpi_enable_hardware_hw_op(); + i = mbedtls_mpi_bitlen( E ); - esp_mpi_mul_mpi_hw_op(X, Y, hw_words); - esp_mpi_read_result_hw_op(Z, z_words); + wsize = ( i > 671 ) ? 6 : ( i > 239 ) ? 5 : + ( i > 79 ) ? 4 : ( i > 23 ) ? 3 : 1; - esp_mpi_disable_hardware_hw_op(); +#if( MBEDTLS_MPI_WINDOW_SIZE < 6 ) + if( wsize > MBEDTLS_MPI_WINDOW_SIZE ) + wsize = MBEDTLS_MPI_WINDOW_SIZE; +#endif - Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); + j = N->n + 1; + MBEDTLS_MPI_CHK( mbedtls_mpi_grow( X, j ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_grow( &W[1], j ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_grow( &T, j * 2 ) ); -cleanup: - return ret; -} + /* + * Compensate for negative A (and correct at the end) + */ + neg = ( A->s == -1 ); + if( neg ) + { + MBEDTLS_MPI_CHK( mbedtls_mpi_copy( &Apos, A ) ); + Apos.s = 1; + A = &Apos; + } -int mbedtls_mpi_mul_int( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_uint b ) -{ - mbedtls_mpi _B; - mbedtls_mpi_uint p[1]; + /* + * If 1st call, pre-compute R^2 mod N + */ + if( _RR == NULL || _RR->p == NULL ) + { + MBEDTLS_MPI_CHK( mbedtls_mpi_lset( &RR, 1 ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_shift_l( &RR, N->n * 2 * biL ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_mod_mpi( &RR, &RR, N ) ); - _B.MBEDTLS_PRIVATE(s) = 1; - _B.MBEDTLS_PRIVATE(n) = 1; - _B.MBEDTLS_PRIVATE(p) = p; - p[0] = b; + if( _RR != NULL ) + memcpy( _RR, &RR, sizeof( mbedtls_mpi ) ); + } + else + memcpy( &RR, _RR, sizeof( mbedtls_mpi ) ); - return( mbedtls_mpi_mul_mpi( X, A, &_B ) ); -} + /* + * W[1] = A * R^2 * R^-1 mod N = A * R mod N + */ + if( mbedtls_mpi_cmp_mpi( A, N ) >= 0 ) + MBEDTLS_MPI_CHK( mbedtls_mpi_mod_mpi( &W[1], A, N ) ); + else + MBEDTLS_MPI_CHK( mbedtls_mpi_copy( &W[1], A ) ); -/* Deal with the case when X & Y are too long for the hardware unit, by splitting one operand - into two halves. + MBEDTLS_MPI_CHK( mpi_montmul( &W[1], &RR, N, mm, &T ) ); - Y must be the longer operand + /* + * X = R^2 * R^-1 mod N = R mod N + */ + MBEDTLS_MPI_CHK( mbedtls_mpi_copy( X, &RR ) ); + MBEDTLS_MPI_CHK( mpi_montred( X, N, mm, &T ) ); - Slice Y into Yp, Ypp such that: - Yp = lower 'b' bits of Y - Ypp = upper 'b' bits of Y (right shifted) + if( wsize > 1 ) + { + /* + * W[1 << (wsize - 1)] = W[1] ^ (wsize - 1) + */ + j = one << ( wsize - 1 ); + + MBEDTLS_MPI_CHK( mbedtls_mpi_grow( &W[j], N->n + 1 ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_copy( &W[j], &W[1] ) ); + + for( i = 0; i < wsize - 1; i++ ) + MBEDTLS_MPI_CHK( mpi_montmul( &W[j], &W[j], N, mm, &T ) ); + + /* + * W[i] = W[i - 1] * W[1] + */ + for( i = j + 1; i < ( one << wsize ); i++ ) + { + MBEDTLS_MPI_CHK( mbedtls_mpi_grow( &W[i], N->n + 1 ) ); + MBEDTLS_MPI_CHK( mbedtls_mpi_copy( &W[i], &W[i - 1] ) ); + + MBEDTLS_MPI_CHK( mpi_montmul( &W[i], &W[1], N, mm, &T ) ); + } + } - Such that - Z = X * Y - Z = X * (Yp + Ypp<n; + bufsize = 0; + nbits = 0; + wbits = 0; + state = 0; - Note that this function may recurse multiple times, if both X & Y - are too long for the hardware multiplication unit. -*/ -static int mpi_mult_mpi_overlong(mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t y_words, size_t z_words) -{ - int ret = 0; - mbedtls_mpi Ztemp; - /* Rather than slicing in two on bits we slice on limbs (32 bit words) */ - const size_t words_slice = y_words / 2; - /* Yp holds lower bits of Y (declared to reuse Y's array contents to save on copying) */ - const mbedtls_mpi Yp = { - .MBEDTLS_PRIVATE(p) = Y->MBEDTLS_PRIVATE(p), - .MBEDTLS_PRIVATE(n) = words_slice, - .MBEDTLS_PRIVATE(s) = Y->MBEDTLS_PRIVATE(s) - }; - /* Ypp holds upper bits of Y, right shifted (also reuses Y's array contents) */ - const mbedtls_mpi Ypp = { - .MBEDTLS_PRIVATE(p) = Y->MBEDTLS_PRIVATE(p) + words_slice, - .MBEDTLS_PRIVATE(n) = y_words - words_slice, - .MBEDTLS_PRIVATE(s) = Y->MBEDTLS_PRIVATE(s) - }; - mbedtls_mpi_init(&Ztemp); - - /* Get result Ztemp = Yp * X (need temporary variable Ztemp) */ - MBEDTLS_MPI_CHK( mbedtls_mpi_mul_mpi(&Ztemp, X, &Yp) ); - - /* Z = Ypp * Y */ - MBEDTLS_MPI_CHK( mbedtls_mpi_mul_mpi(Z, X, &Ypp) ); - - /* Z = Z << b */ - MBEDTLS_MPI_CHK( mbedtls_mpi_shift_l(Z, words_slice * 32) ); - - /* Z += Ztemp */ - MBEDTLS_MPI_CHK( mbedtls_mpi_add_mpi(Z, Z, &Ztemp) ); + while( 1 ) + { + if( bufsize == 0 ) + { + if( nblimbs == 0 ) + break; -cleanup: - mbedtls_mpi_free(&Ztemp); + nblimbs--; - return ret; -} + bufsize = sizeof( mbedtls_mpi_uint ) << 3; + } -/* Special-case of mbedtls_mpi_mult_mpi(), where we use hardware montgomery mod - multiplication to calculate an mbedtls_mpi_mult_mpi result where either - A or B are >2048 bits so can't use the standard multiplication method. + bufsize--; - Result (number of words, based on A bits + B bits) must still be less than 4096 bits. + ei = (E->p[nblimbs] >> bufsize) & 1; - This case is simpler than the general case modulo multiply of - esp_mpi_mul_mpi_mod() because we can control the other arguments: + /* + * skip leading 0s + */ + if( ei == 0 && state == 0 ) + continue; - * Modulus is chosen with M=(2^num_bits - 1) (ie M=R-1), so output - * Mprime and Rinv are therefore predictable as follows: - isn't actually modulo anything. - Mprime 1 - Rinv 1 + if( ei == 0 && state == 1 ) + { + /* + * out of window, square X + */ + MBEDTLS_MPI_CHK( mpi_montmul( X, X, N, mm, &T ) ); + continue; + } - (See RSA Accelerator section in Technical Reference for more about Mprime, Rinv) -*/ + /* + * add ei to current window + */ + state = 2; + + nbits++; + wbits |= ( ei << ( wsize - nbits ) ); + + if( nbits == wsize ) + { + /* + * X = X^wsize R^-1 mod N + */ + for( i = 0; i < wsize; i++ ) + MBEDTLS_MPI_CHK( mpi_montmul( X, X, N, mm, &T ) ); + + /* + * X = X * W[wbits] R^-1 mod N + */ + MBEDTLS_MPI_CHK( mpi_montmul( X, &W[wbits], N, mm, &T ) ); + + state--; + nbits = 0; + wbits = 0; + } + } -static int mpi_mult_mpi_failover_mod_mult( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, size_t z_words) -{ - int ret; - size_t hw_words = esp_mpi_hardware_words(z_words); + /* + * process the remaining bits + */ + for( i = 0; i < nbits; i++ ) + { + MBEDTLS_MPI_CHK( mpi_montmul( X, X, N, mm, &T ) ); - esp_mpi_enable_hardware_hw_op(); + wbits <<= 1; - esp_mpi_mult_mpi_failover_mod_mult_hw_op(X, Y, hw_words ); - MBEDTLS_MPI_CHK( mbedtls_mpi_grow(Z, hw_words) ); - esp_mpi_read_result_hw_op(Z, hw_words); + if( ( wbits & ( one << wsize ) ) != 0 ) + MBEDTLS_MPI_CHK( mpi_montmul( X, &W[1], N, mm, &T ) ); + } - Z->MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s) * Y->MBEDTLS_PRIVATE(s); /* - * Relevant: https://github.com/espressif/esp-idf/issues/11850 - * If the first condition fails then most likely hardware peripheral - * has produced an incorrect result for MPI operation. This can - * happen if data fed to the peripheral register was incorrect. - * - * z_words is calculated as the worst-case possible size of the result - * MPI Z. The difference between z_words and the actual words taken by - * the MPI result (mpi_words(Z)) can be a maximum of 1 word. - * The value z_bits (actual bits taken by the MPI result) is calculated - * as x_bits + y_bits bits, however, in some cases, z_bits can be - * x_bits + y_bits - 1 bits (see example below). - * 0b1111 * 0b1111 = 0b11100001 -> 8 bits - * 0b1000 * 0b1000 = 0b01000000 -> 7 bits. - * The code rounds up to the nearest word size, so the maximum difference - * could be of only 1 word. The second condition handles this. + * X = A^E * R * R^-1 mod N = A^E mod N */ - assert((z_words >= mpi_words(Z)) && (z_words - mpi_words(Z) <= (size_t)1)); + MBEDTLS_MPI_CHK( mpi_montred( X, N, mm, &T ) ); + + if( neg && E->n != 0 && ( E->p[0] & 1 ) != 0 ) + { + X->s = -1; + MBEDTLS_MPI_CHK( mbedtls_mpi_add_mpi( X, N, X ) ); + } + cleanup: - esp_mpi_disable_hardware_hw_op(); - return ret; -} -#endif /* MBEDTLS_MPI_MUL_MPI_ALT */ + for( i = ( one << ( wsize - 1 ) ); i < ( one << wsize ); i++ ) + mbedtls_mpi_free( &W[i] ); + + mbedtls_mpi_free( &W[1] ); mbedtls_mpi_free( &T ); mbedtls_mpi_free( &Apos ); + + if( _RR == NULL || _RR->p == NULL ) + mbedtls_mpi_free( &RR ); + + return( ret ); +} +#endif /* MBEDTLS_MPI_EXP_MOD_ALT */ +#endif /* MBEDTLS_BIGNUM_C */ diff --git a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h index 0ce60511d..aa930cb25 100644 --- a/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h +++ b/components/mbedtls/mbedtls_v3/port/include/mbedtls/esp_config.h @@ -206,9 +206,15 @@ #define MBEDTLS_MPI_MUL_MPI_ALT #else #undef MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK -#undef MBEDTLS_MPI_EXP_MOD_ALT #undef MBEDTLS_MPI_MUL_MPI_ALT +#if defined(CONFIG_MBEDTLS_MPI_EXP_MOD_ALT) +#define MBEDTLS_MPI_EXP_MOD_ALT +#else +#undef MBEDTLS_MPI_EXP_MOD_ALT #endif +#endif + +#define MBEDTLS_MPI_WINDOW_SIZE 6 #ifdef CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN #define MBEDTLS_ECDSA_SIGN_ALT pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy

Alternative Proxy

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