From 1a9765171b52db6be4d653629ebb745c9cd270a8 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Tue, 4 Feb 2020 16:59:41 -0800 Subject: [PATCH] OEMCrypto v16.2 unit tests and reference code --- docs/Upgrading_Widevine_Drm_to_Pi.pdf | Bin 231997 -> 254714 bytes oemcrypto/include/OEMCryptoCENC.h | 184 +- oemcrypto/odk/Android.bp | 5 +- oemcrypto/odk/README | 2 +- oemcrypto/odk/include/OEMCryptoCENCCommon.h | 15 + .../odk/include/core_message_serialize.h | 4 + .../include/core_message_serialize_proto.h | 9 +- oemcrypto/odk/include/core_message_types.h | 9 +- oemcrypto/odk/include/odk.h | 150 +- oemcrypto/odk/include/odk_assert.h | 27 - oemcrypto/odk/include/odk_overflow.h | 33 - oemcrypto/odk/include/odk_serialize.h | 44 - oemcrypto/odk/include/odk_structs.h | 229 +- oemcrypto/odk/include/odk_structs_priv.h | 54 - oemcrypto/odk/include/odk_target.h | 13 + oemcrypto/odk/include/serialization_base.h | 91 - oemcrypto/odk/kdo/include/oec_util.h | 172 -- oemcrypto/odk/kdo/include/oec_util_proto.h | 59 - .../odk/src/core_message_deserialize.cpp | 36 +- oemcrypto/odk/src/core_message_serialize.cpp | 35 +- .../odk/src/core_message_serialize_proto.cpp | 62 +- oemcrypto/odk/src/odk.c | 148 +- oemcrypto/odk/src/odk.gypi | 3 +- oemcrypto/odk/src/odk_overflow.h | 3 + oemcrypto/odk/src/odk_serialize.c | 84 +- oemcrypto/odk/src/odk_serialize.h | 25 +- oemcrypto/odk/src/odk_structs_priv.h | 48 +- oemcrypto/odk/src/odk_timer.c | 509 +++-- oemcrypto/odk/src/odk_util.c | 25 + oemcrypto/odk/src/odk_util.h | 24 + oemcrypto/odk/src/serialization_base.c | 44 +- oemcrypto/odk/src/serialization_base.h | 6 + oemcrypto/odk/test/odk_fuzz.cpp | 13 +- oemcrypto/odk/test/odk_test.cpp | 172 +- oemcrypto/odk/test/odk_test.h | 64 - oemcrypto/odk/test/odk_timer_test.cpp | 1948 ++++++++++------- oemcrypto/ref/src/oemcrypto_engine_ref.cpp | 132 +- oemcrypto/ref/src/oemcrypto_engine_ref.h | 18 + oemcrypto/ref/src/oemcrypto_ref.cpp | 69 +- oemcrypto/ref/src/oemcrypto_session.cpp | 34 +- oemcrypto/ref/src/oemcrypto_usage_table_ref.h | 6 +- oemcrypto/test/oec_device_features.cpp | 7 - oemcrypto/test/oec_device_features.h | 1 - oemcrypto/test/oec_session_util.cpp | 257 ++- oemcrypto/test/oec_session_util.h | 39 +- .../test/oemcrypto_session_tests_helper.cpp | 1 - oemcrypto/test/oemcrypto_test.cpp | 117 +- util/test/test_clock.cpp | 2 +- util/test/test_sleep.h | 2 +- 49 files changed, 2819 insertions(+), 2215 deletions(-) delete mode 100644 oemcrypto/odk/include/odk_assert.h delete mode 100644 oemcrypto/odk/include/odk_overflow.h delete mode 100644 oemcrypto/odk/include/odk_serialize.h delete mode 100644 oemcrypto/odk/include/odk_structs_priv.h create mode 100644 oemcrypto/odk/include/odk_target.h delete mode 100644 oemcrypto/odk/include/serialization_base.h delete mode 100644 oemcrypto/odk/kdo/include/oec_util.h delete mode 100644 oemcrypto/odk/kdo/include/oec_util_proto.h create mode 100644 oemcrypto/odk/src/odk_util.c create mode 100644 oemcrypto/odk/src/odk_util.h delete mode 100644 oemcrypto/odk/test/odk_test.h diff --git a/docs/Upgrading_Widevine_Drm_to_Pi.pdf b/docs/Upgrading_Widevine_Drm_to_Pi.pdf index fc9ae0dec202d516ac700c708d501b18838a9db2..41fb95afaa25e0f00c36b547b5e3cc06368e2938 100644 GIT binary patch delta 157415 zcmZVl18^o!)b|f>Y}>YNCmU>R+qRQyW82=?-q^fi+qUie_jf;cz4g@dPR&%GIeqHe zJyl&b=hJ7VD;e=^2q_+ySkA#z+{o38TAY`anU$HDg_)b>-^Ibkz|2j>%uGd-fTP39 z#ly(V$i~CY!N|hGMXX|EZscsm#KFeJ$i_*-B<5^plNv&(EZ3#Z1gj%uKAzB*{k1@t+|m z$fT)YY;9)Z`d>E(G3Wm}fBuvD=lRJ+%=JIsy|Hsg@Vj<@FKQ4A+7UutBaT2qz z{AUUNJD5~lo!v}a6^)$#XDV>aO#jCuW?@a+tp_0oVN1#bp#-q;2nxEm{(CMvSkLTB z-ED_;PGtXQgFc|0FwPex1U#6n#p(KQ43O00YO4egy?FM|Z%LU3363w*9EuA0wUPjW z1kyO7IN*CMl|Yrc?rFsrQ2fIy>HCy!=2H>;*D{7e|ZEWe; zZF>(Pu{h7e0ibn%c)NeLS8Yxtw0sGEn!a3rxH1tIK&Py^uKUXNb--FScDr-w#IvT) zwOkoRH&C=YRgwVV{z)>XiKNjm#zL|BSh`p%3jK-K@#XRO{un?KuF3|Z&NC}btDJN6 zt7`tKNUavCw%}k)=)4d|12=+mGQ7w=ptL7v@AYNv93WazGFigExhpZ`_Hpsk$H`>% z@RR=!g=Oo~^bPDZ2p_*I!#+h9UOS@bN|}f`fsow?`|UDt+wMb$b9o)J_|~aemtpDA zN8O`|37%h-lxhCFB%!^2K{|y4xBX|+fxIeAElYL9N@lZMN^`6RN4UvV^R0bltk{7W z#&HAKDL~WDgkyfIsVT)u@)S=>C615Wy)ZQpxJpFVay2%0kX3In|HMPHlDrpBxn z@Dls)-w+Soc&K_{Tq zyB#9raP0I{&j%;fl+2f!Wy5F6yX4K zSb`>sUn1%`kdSzcd!-N2ukBb--w)`cX#b6fUb4Aglm&OSH4Ld|b+8}*ypQuqcLG0a z0^(UIG+LL34f}hG0bc>6hltOHLU;5!S9gA~8`_V07AIqifw#e|(Dz|ys9-*B3Gr{8 zs|rL`;1eXLS}(E@4YBz`8DzUrH}YIKrMDzyTc|M~xW^spJIDn*Be8R^g%;p8_BT7J zs-b5N>Rf`qXpt~%zI#5XIL-+$eB?MNmYvsgC>okoaRuG=4~d1rOjH>7@uHc4t*%el z10}c?x*hfnagtakVW9c)j|DCWD)NqV$6H6(NFV@{`D9;1$nUF}?F}8#Ms^9wmQyRP;@WI9+54H2oF?zG3{`TlzVggz3$Q5s?;yIQZBpP(iRuFBKGv zd;SB(AnWCK6v3rCybS;><%AIzO^3akzgW&0pG4?AJ6)ff_kkE(qrv}M3QL?ggz%{r zI-RXF{cV9+0597g9ZI3Z0QO#Q?*V0+LyOOmNUDOAYK$T_U{n_#YHEjoLYOxW8=M0R zTAtxx?AK4SU)0zFQ{JMT`~2`H6CzuE$NtCDZCPo5X7iYHm`DMNfywl*vd_^jQ3A`T z(*6%E?PlzdF8A#ZBXWMaQswXpJqa7J2qY9>gPa32i4yZ8;MA$*~dSX zh=1yjEk#r$nayiu@Q;DY?L0YtIv^wQ%k1khcOw3{DCej7Vd%~TKBRvf9Ts#j=Zv>SgcVY5=Q#kU)CVx@>!MYekcU+7t@4d4Z9AwQcX4?D< z$y2WH2w#8qmNzLsG{^I;@cBS?R3a{P!~wonA1`J0kq}AG)Q*s482W{bja{T|>+ z>A24m@Dt6)mF-on+A z>@W{yD+@O19 z3;u5Hs9EPL(}EpZxxgNmt+y$_m&=sFu`0l=A}JKSS41;ll%h?HGWqKXM7Uv|&#&Dl zvE?b1Zk1H+NAu<(+=s|Y0?c6h>Q;-dQ;@c=$G2CN28+3-nd4R81*xY|299s z4_ROqMoQCsg0=SOZ0uGilvByPUSCtKey0LYf2}^`5%SM^eZ}vdy~7*FU$HDrmj1QDo0tI}=uDe?4;K|XF!ylxa^tYd~Jqww>^ zQY+~EuqKio2-Gyyx*~UxkpC-9Q2~IJE?s>h%$s~{Rq_^rP;Y^fiih7nBT#Xr4xb0? zv=5$dlgTp^y3fTbl=8JF34r?)KxIVd znle4i^+-58P^vf*dJ!s*s@`Ew0bfmV=cF)bGh$so@g7Lx`XdAv{C3INW&mlqvZiN} zjv!v>pN=4_dHjJOCklvzKR%fonBymYa%PaFN8x|k!1oh7GeahcF$d$ze1Q!xtA4`{ zuta-dMNacLKme-u%&bQ9t2yWv+na=m|Z8h+Ib6 zZcg+5;JcL5jVK@sw|%kDq7WbFC+5;lkX4Sx-TYEbxL1-}+DVJn`VDw}Bcw;Kl0d*2 z6m8ZQxe_4=dH+XPb`kIF`%+qyI-xvH`5bEv(XM8< z`)Qq_z6j(No{cbG`TDl^1)VuwDVKIU4nAK5UWpDk`F}6IxqpDzj0o-d-zj+h{t|-w zf&h>c4d#u6-B^4gd|)7*)$SYJA@B!=jr1hc?kn-jTEF0}(gRvCFyc;iZn&3JgaR<{r2i%=>^gm7@W)WCw1X%l;@zUZ!sjj-3^xX5 z)djFj!+a4;&)oBKo-*x1+?jQTAD3~#bq60mPX3AWm2iaPOsa+L4o!Q+s>i5JtcB|i zSRS!EfLn>JegoWlCi#Lm>>@ZrHP7C2zIt>9T^`8ad38n+EL`u}eezuN^BugeNKZo@ zxU9r@Jj%TwUnlv13-v=C00Z+3TfXR@_Q>v3`lfGKUU^>OpLWsi;4T)qjD-Y?1%FSE z1RX&7M))0&Tv(iIa0`EdK|PkNEVct_XPby4fnd0}DK5}EWfgjOH8ZOC*5 zb=x5vt4Nya+Zev}f?Ki(QR|zni70n$z^A z+^KuzcFMJDjqN>h%)fDN*JIuxwugDhqg@BR(|fdsreW%_*5B-b#_7Fqo=Ip(!A)9n^= z{CPi65c5jr)EteYOWwrOy^V{a5>lun+WQk~o}@?YAurmBic8^g||Yn&@H4^#xS`}Qj0fyYOllebQ*?wFbTdo=sy ze*ykehK&mj+2p|+(`+SLp>wP0>W!wJ9~f)N#TH9{uI7C*(AT8=|?W;R#FGncIdGnNzetkxU!DjZw^d%>L2GS>Z#-^_$zB4SmH zrMa=LdNi?*FHJCG06MKcYP2GhreYb#F_p&)p{>L-#)WN3`@%sfqOr^qP@-|s?4=Nj zZlNSSa>FYbqNgZ^#&P59d^?P@=%%@%)9aC|Sk_yP8SF~}h}z;_F6Yh8KPo6toY`k9 zd31)+c>t!BT0+o`v4_>l;F?5VXKSK3r+NZQ!lOi{m>x5=qFHNYSopU&?=o}d8urZ7 z{#Kcmv@G@^iI2;4*_cTKrk)Q|IG)Y2T2hFNb^GaBd}1=K7Zk~Fy9}$V!b_6Xv1X{X ztNTgL4jE8`6|C;1sT~pq1jBA-8-^Tk;DoS7KdJ_%m6-jOCAKFGvaZ%1e~mH|8t>UD@wwQTKW;IR9DcNj3on%#Qn#f3@X{kD$TCa;w4XA85^ z;zpdV0>HaVOn}E2@cA2v8C2v#zkPLkOOPXuju}LFtlC|FV%6gDPePMEak}1D1r?Q- zH-cJL#5#@fznCSMihP^W0+3booe9p@g{ol%LN1FF5r# zNtd_Ve_B}4QqrgYqM4zFi%R~RAV8D!?)~9ER~(^EPW|x5j>+o~jI73LHQ-O~ahYut&toar91iQS~ zuHQ~~eNR0FagAU0bMtZwk_+B(s*)tFCm zb%LhWbZ=S?$ZM1z2ql~!X|Mz)A9CAgtW6jhOIxBqSU_O;X^SuA-&+Hmjza+zyst-5 zJV12Y66^2+Y9fQ5tli)`mDUyXGxDLUy!wRf5`E1mnyJg!I+iKK<%IHsPR{NH3pNM6 zo>CgcW=?PL{of#QsO@1lNX$*UX(3k>UvbV03;0kvA@VGHw^ijhacZKaKPR$qSsXI4 zkJVN1_EljvHKN((7lygTf~PQVGfFu3Vk&&;+(FodhscxT3D0bHfTF2Ven*5Vigd51S`)2nN!At@6bJ=Rg^-KsMCH@0 z3MhVV6URS*PkE|*CQLRCHrZIZD~5RM_%lIiKe?jcRyPLs{>TdzP&N}+f*CPX07~6 zV+|jsW7Es==TDKID-WBBjG3LI9ZJ56I>%30G=bfZ^eLogt^#E5;%97=##Qd|@7fl= zElCWaJwzsNQSg%Y9HQd6ygr_Jfr0+s{l_VQfmE36#!w&;unMc`N8B(jJtZBs*Dza4 zRXx9v*Gj|GoV8W#f{;@w>0^e^L|k|#kwjdxkL#C0hY8sgDA`r8ww0dYRl!|_@IX=G zMFhfE=Gon8K>OR-TFBW>Hi_7p&Z0Dhb*q=Mo0p!QpPgCIn~6y)#l#5XYg1q4dBrh6 zjic;y?7cEbGLA6+Yek@^nNd^c*kMPP=PWoAS517W(k)Fm<>*pPIrg3;c>M4DXVa5b&MDg#TfxAPuH@7>xU+PR=uA`w8 zOY4}``VS`s2vPbvruS_@_Bfw!EVVTekv67jx3y34*)X|+?h4-7l4|@`EV=j9xvHx! zg#2A8ro~~2MXqZ2GJ(b#d*~7oN>!0n!Zg+qi=~)WIVBT!oWJI(&9s+E(Nm59O`?ep zS@t-d+T%&eQGb(BYnxe{s-*Bl0v3)4M@-D1>4KEUA!TyU68Xz5BU`lcg#hj^D+4I_ z%lytorP>I9&vA_-d&1zIb`tDh{k{W3fy)P#B`$j05tmPgm8&26|NrRyKM?)DJp41M z0t6?`&kgi5ZRS7j7H$KfApciC_j`oS4^f3@KSkQkfgZ#xb~?F-s_>-|e*e-EpsTlvQge*ee5 z4s)MViZY_2JDrdII0YS_zx;)r=T^-0dYkQQ!7!14|8$zBKh%;@A4Lp7b-^!xT*G13 ziR+%fw}~adyw`ImPk*$fzNL2Gp)pJJ8pyKtC_M)|2WtY(1}co{+tAk@ z`6#fgKrp#ifupt{6-^g+`G-r=92Lc3UiN}|evx}_L2ha( z<>-9kLnnE8wxjZEF=;sYmV_cD$dkJ6=okwiuU!u4Dzk6F22Kf_k8osagCJKd<}0fA z%*2Tc@!7FHQ>NiC4cJWOC*PMm#P1;zW#KO}nT6oKsUdrx#oye_3#bDD1dELXCt1Tw zb`YKqDOnE6@4lE={*JH=WO%|@>5Xm3DFmYzhs#dC68}Ke-4R_yuop-2WTt>nMpohR zCc&76(jR1(iuy!@?ENw5iXG043`v40$`2D{#B2;{O8nLrhy7dl0uDRyg$LXSdQ6NY zJRL@ig#xa@I8TIg@3?Fnuo^yqEEKfM49|iU3KR@MiI3t&{Ye_T$Bp}Q=nKTe5Z;*Z zfaFL_a8GlvXMh_U?;Q`lmpd6K24YMSElDr>0Zu7QvL`;+)5pV%Qja|q@rf~Y$PF_! zRGDOiI!0pq-qVksj{1&W;+zesg7S+y)JVGuN+ut@KH7vQnMD2=zzsHLLX;1Yi}H>< zCH4VLn-5wKdn=X>b37#YTZbx#6;9Oq%8Yb~%Pr&%N{Vlj z59uTbg!{wkj*r<35$=Z16bupmh>!P&(H$N)pU{bfeQ+yGq7M0RDEwLsjXshfgkmOm z1^yRvF+?Ux8tE4ffH4qrFv^H#kF^#0UKbP#_ywNktcy5~th1}!xfc3DWf``#LSzK- zm!u}xeNe3q)QO`injnCq4!0icR-hGriCBN?7Pb}Mi9wgp2dgHS4!Jh4DczZ4ucHpJ zCb|o3xmPWG*|`&Oi9|o{h0_l)&>FD+bLF=u*#@!?n*E+9 zo2~c;I2WmXXqK@0`c_Fa^)-znG z(1#YTFpv~J-J81tcS8@{?o~;*U;-ZR;K;5*^+X5&kls-YeuLFv9d0}O5Z$LTp86|IrE}Qu55_p2$>UpB6Y>92aN$ymyIPApTFz)4Xf8lJ2@sHj5aijWR z#`fg_P~u$pK_pSW17F~Me_WIKLj#Q!1|%Kv4+*&gURd^2ZZuwg+@ife*M1>*BYz_j z0iSw>c51`(`l-V6NCYAB2DXhA`mt`129xl=Ve&}*M{ns~_@BVD1CzqL@C$Dg2C?^Q z+x#RoKuyp#xrv5PWSJX@qHcd{6q#VVi{U~7;<#}cxOQ5h{<2}OM3a0%67ymmXObox z41pdQ`tHDAUtHir)d7(9ELS7)Uh1Ld5qDNPpd?3Ai_nEP{loK^hFJg%M=OC|ZwGuz zXo+*D*sQrPBcBHx)Ww3Vf&5W1ceqdEVFzZ;92vhh$Yr%hpj$IA7Cl zjO0v-SVPOR#(I$+ouH2gDxFIr2Ba;}8UwuTh_%c$NaBFVMxed#*7uv@H}LC(PZLlV z^a%V;+5c7s1P@PFLr#O53W^lU6Sl$)6L``RK=a)J9_MIq^NgkM*AtE4--gsrF z4w#>0R*WQ4{{iDC#izHs@i6Cbjno44-Q6Wz@UMi!#7M&+ei>5}^7IoSzVRWYf`4wBeqBJ1aixLVeC7z~)*LjX4=LDsqC)Qw zeAU$!4{l52e(X-oEi&K@AW&#-A&TW;W-k@R;e+tGf=PfCPh}gPBPOy%I0gWYf~l_l z{8?Ku;R~5?+~1qhY9!lDd=UPVTH431uU^Vut+H$On#MZiSO@@ymvU__*!A^?>bf{N zI_(EamCl57*`fF2an)SA9=RjJrMe<^e3T(oZY=PSYNB3EAQ&5AEPf&p=O~w8JUD#7 z$NSbD7qY3~qR}{)JmN4XsMP~{GH$%{;71T}j6Xp^h7X6Ykm(1Lrra9IZe4`vWv>Hg zPBzx$*LrLCH$5oK2s;{Tu4TS25&C|dS2&98b+ivHx7p_d4xnxQz9t8m^q)OowGiO2 zn(A8XtxW2Q$hYiW$a3;g(M4k-+Cx2}abjNa@aj;1X|YYjy2c_lyL$ryRrYb1`=;Lv zPqU$WD4cXkEwriF>18$+d3bgEJzod#@MQMDwY2(H6E;tz2v=FA^1eYJIcCQ&0m8U)`&z#}AgA-xxC>(egZFZ-2FtJs( zC6>RDvFnwYb%nruDF*{~7K}R$jXZ8umI_s(K2Z`8{Z?;iBJgE-3-S3;SW8nHe}Q(m zX?uD9;Y1et!jn0Lkykb^%~VYdf%(kdn|@7O*lWYdH~Ml^kw)!fgH1sNM=SQ@Qy5z# zy*2C4!t(o)Pd|(U&kAf3S;(KMg02MM+1PCLV7LTgG#*QO zI#3NC{N&JNu^{_}fH{=Z+vDU-^@lgaaRk@{;xd(pIStWglI&epkuYs+lP* zi?ok8mxz~QesQ~;XRpT~E`eS_a`k#qd03Z^Po?-)v3Z)6Ri!x`-?e~NV3?7_)x5$2 zU!+jb!;AjVKupJ+=En+_hAw8-qL1!o%?7k>X|+Q~P(QD8ew}EH2UiDlBDqboOujy-yJeL1H{5G=SSB$^;@1M_*((BaU0XHASsnJ zE_OW5ojF{HXoezLc)#1Bws_&6496VX-UCA8E_H)tl{BuFRjdtR)<4Tbgz1DUHl$mHqftHm8PO?Wpk!H08|=Bf`wGtpb&o?WZDQXhm6g4wGrIVeGT4KF8v zyNqn3z?J$h zw87=lD9@mz8fS&39ZU@}ye9J4L%sylyqm-=?W#;ZDJ>^;f($x`4ufer*?>dDF)AW5lAc2G8hO1xLn*k*ka36=@*d2|5$VPqBUEu~A& z?TeVtdmh1oLW!uuHOOx0i%C>c?i;Z~kXc-XqOmJ*YqQ+S83ML`2Kh-!%p{I&%vK*y zDHv7BEDd9p&O8b)$JU6xB@W&?U<<}jZzK6mE*{L9aew*Ck;s>vt zJ~{iy-}eiiC(@2T1&XFjEs_`Omz(Yq^6acQdducrsa%u-=vgH3%oOtr7_|8qcK#qNGx#ydQAqztNpk}U3GHY^!Qk&$V-;N>^9rM8T zzn|LOMfu9DP}p5b18~q0h}Q%XkU8bc1I63~R%MzK^h||#WMh+nuRm!3h%;-{R%q+!6&V#( zxT+C0uxHmwTWYroej8G`u)J2#l9k-OB=FF~9{Qs|n3V^}zTC1keQ1d7=a(rLPZE845U1w?KfH!{D?F)C6-BHIA49_K3a6@%{MUlph98zBHb` z&vtfC0)ctO0FtB;rh>}CXYrPwS<+PHf*79^>j(M!g-rFc0oBB|@%jPuCy0Iq1TyGR z!X-i%4tKS8Q3D9sbsSVWH*-ro`KGh&W+?@L=w=W#hBN<#@Wq{kh1et8v;?oO~hNR!a8P*st8NUhq@=`W~k8 zjwTs$LCT4xY+LuA8>JB-}ds0nOtj@6<#>_& za3v%pF!r{D`5vz0hjWF95rL2z^2BFaz!{0Dvdx+zOri~=_E^qiEsjyFtVLaXXZ@!V z26tP{QUW^jtJDK=bl9ieB!>?Qey@K%pI!k#DwtY)_ggYt_m7 z=yqwxP4XvAWtpq}wX~*NK&eHSM$q-NTCVH5`xhe_7SWRwRL@(rCU$K|XWII82|;r8 zjwl$$-+@Fab|p4P1`4J5849gg44F$#b0$Q>zgtGzt)or_i1`fLB;QQ-h%u5vsACX- zz?h#dGrb-wTq{T`6wQRaZQ-urrU>!Qonf4^ zOM=#fN?OjJZq^oXLQ2e-n~%}c0M=0f?8P&bPMz9OQ#n}v?qQZ7LaEv@JV8inDzmEd zbMSZ@Jlu}XexylZW@tw)kv~(!-8`$fpYG6*4$8xbGSw(443}vlcOa&0d}61Bk9@uK zd0!IOKWV++4O2q|psVq7c~5fmUS3J|E9vqTiBXQOA!O9Yi5)xl%w4*v%v~4&_JLM& zN>_jC#_PA8R-85hlC;eu#b_T=Uafs6yE(cayQ>9<(zMDo?4?L(CPvIz$g+$ZoRu5E z)FeCP>$Ej47b^yiN1V`ilgMJ%@z)79 zW5HsV38?TXxpmnfr-@`k`WDv#i}oo$9XE7~^lOy!;rXC8&DAT`FNfvTCk;6`D)p=& zIdoyk-@plmH(Eby2Da+AjL6=REO)9r&YY2!mLGXs=&jFBS2Zjyhib|dT;ZOi?ah4y z*wGQPFz^dZ<0R42GWQ-lRYFZ+oW!^|d*TW0zHcfvO=tLqj&n||(>cBX<(WO5ydH;< z8?RyxUKedTsRs34%c1XI+`q#SJmS)QPdr^WCkxL@6QB88IPE$D9>=}^Vh$9W81@ls z5;-qn2ftN}MGrtSur(X|wVP12x|xWMjCV<-X`WW7mZh1`KR&?Gf?UQ*hz)2ND30AJ zkKN7o?V_{t`x(DQyOI-+NxV+&M3Iq;hW(dK)3|a9GOi58>m(N2TINkQMo!W?$e)Kz4T-e zkxU{&!KU+m+sJ!n`MPzhoKC(Bs{QVh4eO~4>zx4Bv3XhngRsqU25J1t_u>|6Nl(Y= zJ#Q=WD*~R(b*ix+aK25%eicKN=V?DH5GA#w0Y37gyp)AePq<5VA;(wLR}v4A(#+o2 zF!9rt@|-}&<3gd6P3gtQ60$OKasU`rYTYFpnVtsQFqA_i`1=4Pqa31qSJkN1rNAXr z1({SdN?4^5!lFsN2E}?ALTq3N{d(rDz12&h!kF=irsg6NP)&HT09u8~fDKhPWoy)X zZUb=##DXov6nI@{iU52E9bd1i_#a*>B!(tvXqg_7?tLFPnCfG^uBV;$znDq#6>7-e zG-?|wM1s{*%^N~zplajZqUP(6;HB^-hb043j#Ie)6)~lP8QQqYTFTeeXe1ew$DJDZ zau%uy6%8~1a%Ca|qH6jUDIPVMN@|?genv!W{F56ew)dBJm(Nz+S1U?ezOvA24HJT_ zt&ytLEm8?;_7aXb^xB;Q3|qeHj$I9EtD(3=+rniDGeI?kHS($!Wr2!?oRv1JB1&Df z5+=^ns^|v3h`AWg3F%?vATag0Wa5$V#9$QyokRkFNkUNz&m*5QRXbe58k3bPb$bQ9 zjgvq5qh?>MSr}`3<2IcATYcDQ&v#`E-ETiP;XFH?npEn-8X94%M2~*uzl(=bH=tst zzEc0-xT~-f$rR6((R0D< zEWEbN{8$R6G>FR5=D@Xrs&>z67jqZOb7Ht6DcpB21k#${%WVqUvrGG8djU(poe*&8 zvBy6}-#!LEGC5Btxt=%NmhX|K%F`kRmbr=nNbSd}S#__uK}7K8_bRRdj>5VyCV#NI zVPmKjqKcY|+=?RNb9B;wS)pl3lJ=m*iPYG9*yh8{@(7{?R^lxaipjh-%y5Wtb;#}6QhQqwAukBYCrpmWAKT9vW=uHmTUiLSt5v$@m#tm(@DJ^Q*ld}KXN z*2=)m|MODFZ@nRSqHln;+5dY9XfCxU7o`Je^*Y?kQjA*ygLe2s*p`3DGIXa6IKg5u z7#}7$mubm~LFD@7)cECC`LkHN+)|%G2Tk}q)(=Oi;K)@ ze31+1@xVC-mix7;ko*0JJUcMtVG%3LVPd0^>fGX<$UAgvz252RMMbjMq^mZU?S7=O z>rpn(am?%ITGY8Lm3aGKy5Or7fRGsBu!`f_3$Lg>cwUlxX;vvGKS(hn7ncV7UFRXOd9%E{6y+gD6nq6Gm$Au zX|~5Zqt;xo0JixLtXw{=#Pivz_rF!Wd@}iltEXmKo6P{nS{?21L|`rt$cfV-=2~u6 z?cOiFOnmy4d~)_9MT}JMFS@iJp6ndqylXuQ4U}|3;CEFGOfaqXuzn9~G-Fp!b0tT^%*T?$YmP-1fOpvRZ z&%0SAKRKheSzbz49{DS_W5tOGZyD;O z@~6&j?1F#|rhF@<-}KsOnxLa?z;_xPp>MR{3ZKJAIod-W8&fzsAp1=izw0S=@>pGH zd6AECR=)pyMPA+#z70KWS!5KKD`0FMc5wZ-O(Mo}K3mh)Xww#j`g%^;O}niIDlE~m zhkinCyF(ct>oZ?Y|L>#m65Fvg7rWaK05O8=>$F2zrsG)|xA(q6fs0St(S*{39Z<%75w;p#eIm10!PwFvP>I!jyNv=c~uqgGe9m-F|@TE)Z zOl+L)7hV9~lH@y^=tv8;0|TU^@^-{Zt_E5`u&RjQqK_iy`cKlIPtaAC2n9 z5Wu*M`3i_8MeaIyG)R&QYyHO8BaZED=W{f3@fqP23@=hoIW;)>E4y8x(#7Xq<>0at zG-Ievv3W{vOi})zJa;TcB*~;5>oe=MeNzDS)0Tl#yO4cfnd5?lZ7#fYEmgf5xr2Be zUP9WrVudrroipW1>$jON;SptQ-vhzm@sr%DF?%ERRs~vM#e#KN4ftp`iIM z^9J=qQf_4?xJmU*L9WSLR9D05YFZ@@P}Voo;Kvko9HMxt*Z6~u3@u;7qniMpe!Ytl zM7$JqohG<6fyKyi#TVJ37W}7t6?LF4J7>-jW+7vP8`HH?nM%`#36r&SR z=YC0;h~al1SaPZcrm^k&NY;O;6Du1>xHb1a`}QJzd-iHU7C@-aFay zqDo7dRp|D9T^jwB2gnv2;?5d*6<{GUeQ_~n8mCb>(kVOnG>EmrL+ni@O+Rrr$Fw)!&-{g>YSC$@1 z!s(NqlGkmMr9QQv09R0BS0v}&S5PUf-q&h!NpM4}#u7gqF3to>$|D#Baqe8+?}(Bm zoL%O{Ja%8yBt*$TQN}LBnLuJsLTn z>nFt@SoE#hXthV{p86bk7|uO!A++Tk)TjUc;z83btbjfJBVJOTJj`H(;H{_pp)rbU z7DEbRr}D^I@b^|by%W>I{(kXxTW)PF@mXzO$8S_9tIqfKB7z|Fi$D;-^%UzjDFxu4UWO- zSP*R^n7)U$Mhkhk@(a{UpkIry^FZ9@@$ZRExT4ZGdRwANL{t+ZuLJBuUAu>Qc`;RQq&!89u`YBo5GT?b-Zv%p5iq~x)r$BD|6Vb z9DHRhXqYk(7)&zlh9xYRXm*7_kQmlT_}e(80gmS#ZmR++9%tIu9Ab^J$S) zJ5ch}D1+}jx9;PNYLL>i`UjU^*|BI1HAArw$3W}Q*Dktt;N_W zx5_aQ0b5|jd3kZ~=9g5PIH+-YY;%HpkM^VKx1=GiwC}2YP{C<-ZOu9N4~zvYxu8ZL zsn)>dcb)n;lQ}r<($~z9K$OMH-Umztc)A2>s7=5OyxuI1!RnkF*t&*<-|RG}0S^Dv zbw5@_W+)2Pnbb{F#*}||Xg*$O0iG?PD{eX(Fx!axq4D!90iMQw|J+xNO#15BMhw#( zT?y_YyKvK$igg#7-ECetG`iLdn^MJcpf0*`nk(g2VE&@aJP|*xPU5lz=h!A7$mQ?) z+Y2DmFf-Ju;w020fpOl{mt(#+RdC$b31*k22h6}Hn~BU?+t8^SvE!KCAj_!Hsw8G| z-CifVcG5A^BCqy3%lG{lr^UcV3*A}e#p*1b}I5*I*Yzs>LRHZ7ZRGzio9RlbQ3u?C49zYKt$>gcL$-Rua_8N#|tDe;s zsXlY|X8+W-o^tgs1!0}rw3SND@HWD(uW4B#31(ltZCJgxJb_4mFy^S&i9~vcWMu4=S><9+Nos%e5kNlR*i1AM%*u!mVQ(T0)v=k;d&l>5r)`>iY zCKs)vQx7Mt-BW;5Mt7Cul5@u7k_{|{Y0%M*m}oJ#;kMiem}&1+2m{q&0#K=op0wy4 zZu__{cVKv@>Tl+_`OmhTQAgA?#;;09UH77kAY9!iibKWSL%9QI>!03u-5wBW^Io}u zx^>9p~DKju?IO-;v(Z}Prwmkan z5MMCkHyzRh3`dce#u=VWd%%#dikOcxGnVi)N7}9Rh&=b`Bo~ex<+W4#i?yHWzbxcy zQM?#}u#(jaza|6km94`uJ^gr6&%^m~2EV|O^mU?M*I3;k!zk0&zZof2$+`Al*`|)N zmOnpm=W9dMO}D4M$Px67PI=NTaDZD7AJNA=-LC^y9Y|FK_^gyu&Oi+PjG$@Vu-j8M zjhlL^XssA^a;7llKEghvUFls~g!a(kP=oB*pvy?X6PxbCkxw?`em>^Dhi;1I%Z;bv z&1znrGBqbhqCH#@9e4o&Km8dl`Y^^O*Xro*SXoH zY?hAtn+q1={jq#H3vdhgHe@xvu9Mabs2(7bd@W(nIXdRicv;U<=V+|4*XE}a?0QB0 zjQWggav&o0yHY`pp8=mro_lYPTusSyasWufx|x|WZ0BLOVDwYsq#Cv0^o!%98e8_x zL>s||Q6cY6VWrbz8DHq+VLBrS38tR?KBv zy7;Re_FtMB8`@t(u8p;pt{UvWB-|K{W=E>2uwqWMCki*J#ToFn8so+L+wioK=NEel zFRHz<%-r-w3V{>VyjX~r+C7Da%a9CsbDVkNfz{erukBCPs+`ewU=$NUfaz7j;wPgiI?fAG02>jnXiAgi2jHFcV|Rjx%z1(+5geN$X4C^iPy`M9Uz2Rhcw} zHBGJ2#jXky01_sbxBWiJmxbIuuWQn{nm0D@q<+oRHn`Dl`hu?V=QS+FPu5@MK8{qD zn1A}GHh4X4))Qwi+E#b9za>~3-K}NVoL17~RabgZd>pzI}(*1CA+M*=kpTBt0R1h(FslJ&~xcxJ4RnGbqo(*eF zYK>S}ad2D_@Sre*gG7c%j8Ir%aA=@{NE@(F;$9Yd)`>W8GW8HYdW)fuGOTyhejs)f zRs^PTa^57CL;2T~25yg9@8ti6(#Kfrfe*s-%mtPQZliHJf-DHn5D^)NfqSpigs~so z7_#z5gh7jX2Arv=sKuTAWL&JyEU&KHG*JFSSBkkQQ`uIilH(>~3Qd}2&8LT{#OiTo zPeSq3ndS1`Z!)OLR$9pJr|{clJbx`}$``|MCRR0FTW5E;-tahe^`1P(1Zwdp@n()k zMf&Gp

gHje-tJoJ!S_G#?y%zwGDjx9hBuPMrxtgJ34I2}#xBa$68Fy+-xR+if1& z7a%sJwG{jtI(y!|=Kn*vbN`Epp=gee12Y8$`Xk|9PIxg>i=7_kZ^HvG4b%Q zvGV+v0l$KW_InIxzAy>n_lLygkSN z^7iFjpk}yi|1rz|KfFCVfQ_B)e|UQ~b{1CF|Kja0I=wtG2G;<(YMsvy6Au?Qi5YU{ zb#f%6^x(Z5b)#r~NFroL;UI}1WHeGrT&R%Bs2jrKkeIL(hQj=0&KsB*;)6x`L3?P2 z1PMi==3DLFV0%2ruhqwK*PUJGuhka3pA~0kRcF;cRpn42sG>kRkY7rNGaW&0gdTcv zDC+sH0};8HMNm~0pOCTCWTveieG^V@QX-wGbzgADF4PlQ_?iaKU;H7qxG=Y`N$15A z9jq?+y4Sd2D9hYt8`-=E5@@1U41VLuxbRdp0!z48$QP2O-w|c#7E{+eldI7)2z>m& z^9AXBsxDlBZiSqqVwTn>+i|J3}Jnxiio>0LAKze~V}#N$2d z#aVlvSec`Kb!Cf{RJ%FTqOK zo^RY!&6`?))?;6aaYMI&TT3vA_AQKe2DU=z1_|uIO96cX`QST?Yy*7~)c}2xIQ-{_ zM1EM&az#8tTEW&sTu~hUDuvzwSwOD^dsO%cfjZNSd;>ueT?Rpt@Ix0WB;`dC{{#sy z_=;5LAW#E|opGYTgW-l#qw*J4vqagk>``}R#6x&xwWKlR#Dnt8i8YzM8F(&*iUA=A zmjHsrLVqC13#$d7&sz3KpJ?}tTFUL|aEK3=mEt+#wJ|wCEf(U9C@woe;X!kw$AsUV z&qDNQ@h%laMmi`7WI}M`%Y$YY?dx)I-g35q%7bTzTZ1}4@W&h#od&?q$n|8+JO%*H zTTmv!bVJTS97G-?RV@F;B3dHtpmAW|r~_Sp?9sd8XF|8A4x60}4WwD}WFohKF@oqu z+JkivZf3uLRf)obUJL)qH|5;HK2dOm-_f&V-N9>saAe$(b_KLyIuf=a-Vil#IDu_e z?E6{b?szwV-S{`4P1%F+2XYmzgAxl@pyd@o&k_b87c2%KpTEGg!8@XBgSX&TK>~d- zZrC4i+VF3j8ju{}+n{e?T^;ruPNZE8_Ea7)w2@mdH<4T9hf8LmdMsundMsvx1J>Pq zkpv)Gzy%OiB8WgYU_A*gMYBM%X3TrcA2j#S8}J2kUvMsGZ@n5IYas>x(d`1TEsDd~ zGwMB`mO6Xn4`kkWH_W_UA-SOZ0l-{HUtBwA{)k*~U&u=-f*;{Z(q z+CdB?>l`c_(~yf^ct{lYGWm51bd!m{Cl2fe?=CZ(CoaQ zfIndOAkWGM49^4(_?(av3Q0hN3W?N!5J`rE2rb?^oYVy56=;A8N!C!t<^$1rbHyAe zkcyaRvH}bWn?btgZZW*^Z#cZ6Z)_fzb^s?*dwQ-C!w$1P0ow)7=-2tzv&uchuD0UD zAYT!&lv@%)1#uwLXD=QPtUE$8)B!?8`vJ$UACRAnw^+P!g5V$2Zv;0QJ%Cw<6NsJb z6N(+k2gn_9!u1)ANPrSS4iHQ(e?H*5V*dO3{Gt0BdHHnvyV>on`8!nM`)S+tE#q6X z1bFYj`+5EyrXb{|RWkjRr0{*#J#%Vs`tK+O&Yv2b@qUieyS}BhDSZE2J~hxUnD2f8 zaf#M|!itgP#1EBFPR-nNP;U3y zF<$b4#jeMefLe5hAnnyVE+g&4nx9BwpR|MvGhIWD?ujgSApPWo;6fU^9?TVEx7?8| zP>2()X%prHZi0Or4A{>g|C(hxqSPnG(sTzZw8X0|cb<_HIFCdV-w9^KQiB zq_oigW%>(wV90#mW?#{fpGPHJ^D*LwkT?r^)q?!{5<;K)5=xIB?V(ry$Y_QGnPrhd zK@QZWKftXO^{gkz-4B|WMA`F=;sMcpKU5Ccb8J5nI5a@oGKRe7L^pCFs2lwrPXeG) z2K74)@t6oNv4=Qa1=oQe>2a!bv_RJpHt^}WdI){uDDt=krP^wu&_}}950F_k{P8B* z{sra>gb2SGH^nM(gv=h=gvhpq{>4FH{f@Em%K1gz3fVL`QDJ*MuxRL)7T5)99sJ-M z*vX9y)YwC%$eJLFW2x`;Kkna=6l_*ZiOD~fXxF_@BwEeLP*_6T-A@?RW#%a>>428GDNopB^cLnnq!bX^fO7uH{uiZC+TKoqRNS68*tH&X}qmc~778h89e~K;Hv9v}p*z@oYWyaHFUpxnkys+QVevplI?*(?(eM!gna9yDv1N4^I z5)@F^AM{Dn5*f`iPDc<}E0VJ%mC<@FDg1*7U`(C~ol*FqK@&etc#4UbMcbvBLk{5g ztatVUU#w=}`yD~QQlv@35FnUXU)OWBi_~BZ(epCGLjY4v{CUh(H8z}sk z6o#mtu#b_HmY{$rKVn4@1fsw`hQ@pN3@|Pj+86wMySsof`x#TlQa%+RD5d>QHfRra zzsL`MkaegTbOy&6j`-Vz{J|~p$q$yfpkhZV1Z`;2r>Yl}o2p!h3IRy|EVrEue~_DsnBY-0%D}Vdm zLDW2cTDx9}wP?#m2c?AunQMX@q-*S$h&Z1a_&=D= z#x?ZJ-FR6|a~cJxQNzRsix#6)CgD(OQ6r&PN?j#Fj(){yZ@i!1Tjc%t02!=C=#1|a z2U~C(L*wdJ#8Yk%PDR_hth&Z)r0_7?{%vtzKoXJo}Pa%Erp!w2`hHGtol# zh9rxzwO&8#Hc$IZ9Dd_y6Q@jH&3|s9{^+a-Khv*Pp$-AW10WN*9j=lLmeYAcZOHa@ z+NeK!6VG#q@x66M#G6@@v5XR;8tbM;4%dpV1~8hQH#dO z*(7@}RQ_zAarOq1W2^rNRO=?$2~avm1~TC;>-U8(8lX)66l|TacKrI;#foP)FA z?oP_cti32@eUbs5HpLw_&yJWjrQ6j>1(Cf`*&Y-t!Rq`vUCC?LS?q2#F!dcipOkp~ zj*R$7%|AN)@T*%<{6`QAX*My7h+|xUr-^Im9$8MC|c7f16icQfZvu zX9)3*jNfUU@=@Z_V?B!fWTzOgKU=HZD;y`mK|sNd0A!2=&pFjUv{?u+6*z(H0ge_v z^$Y2p*i38m#yMMrZ*1kCs8^j4!aeA#)msB~WebgbY4XF=FDao%d)3@Jt(jOAWR158 zyN0$ZDgxA1oYIiI2}pwOO$$_-yycXf55uq<9DHtTO#l5cj@TSly-FKAG(J z!F8xt%=??N?E5HoqdkjWI+K|f$B#XCf=l4y$O)5`11f;{}$27<9)d*vqE5#+2CE)$0! z>=9!XiBaiZyPM5iKY(67l)8}X>}_%h&;HzRZ_EP2ndAPbeuef0#P4+M;l=Ikm)@Vx zgmx+mOnV^Q$q(nJafSkxJC@(DCuczOAjMlz_|?)?l;K?Cc!t^%mb(akk0_S+UfiS} z&g+N`ZK!6_bvKO8#~qsVyiKWIY% zL6?gT3OZ|Q!yr^Bjz=c>bP(sNJdq2rWY-AUO7u( zdIOf}^fwMl)sF_kMB3OsX6np(Q`iHg20RUyK|*QsviU<-EwYRRDJNwsX)C9KU@Vvf zsp7#?8A>ERW^SBZkChxCla92slQISRu!e2B*48BrkrJQHRm54!GS{LYzj>}t9Du$;NqmVvpxp^YM}3iuvm+EfbPWg3bH9T&JBOktI~}3C#Vx+vO#l zb${9BP@82$;I4-ia^9cnN}_s}jUx_04vUW%ubqa%(A(bMVSG_Q)dl$^_DP&FbrID? zEZvn5t_(3L&R?~ou7t*_R6Q&c^{L?+Dq%JDeB0%@(0Aa6e24qi1f*DN9isXHlLf}U zdtDy%64wTGB=tHlQs~4T#NSc!LPQSjj=!xJ->~(gO9sKe@BAr$nU;wqszq0#RRwlL z_BKG7H$=8h*~+j3QRnNIzCZkRV6>vx0>?5!h4sT_)-0KSdyIEjjegByhVKX8N`DM~ zb)IX)_8m(lYrK-0^^#rG~KU0Q}FcV8# zqzMp`_p{v`ghF1Qf{TtY%Vj6|4Rx{dg*HvvkM0ZKrO56KR3w=+rWYkr+_cUqE5bZp zQ&4_X(!(032Zst3vXw68I8BZ#x9ee?fzYR+b-M*KOOn$cVW5q%Bn0T0^n0*B(T?*n zb(O7GzUtAvyX69!4Po0qtEX1meH2=1Xr8MHRBp$1Q|PZR0KW2)$d{WX`+588Gkj7K z;2z`o3%P;7a>O~ZbXszV@fL*9kU;PO53Dv-QGQOQ8eB)r z7;Sl#wGRCPjJ?-ZGRDJ^FU$mnfkF)CtRnH+k`0LA99Vx~PSZyYa`-KGF zFx4G6Pcs-)W({a;&y_YlT4e>b3WK7KD0NF80as(7)68s+>3-^FyVd10Xl1|}J56OP zY?ZB6T@)WmJMNT&`J;AjO*NFDrM$ZJHs5u}k8ANr2Pp*aRFNOgWK_4WOg_;8R|{ZI zE1w8d6IABVKhnKm=KtvDzlgN6TcGfOe>5TLPjPJ?6#w+AR+GP5fvYxY@J{fKxYOo1 z>j}jHI_TgNAMX0UY$^=Dgi&`HF>ZkF>|->vXCQm33UQ0HOZD7fJND$%>KFlnhlr-{ z=tpGwW$y8*6wnat=f=5uW~b--0TWz9tV8Sr0KIhkbi=IO^sBO4MiRi?Jpv#@;|4$E z@eKcJ{{RO#aZ|Q>E^o%;Q$Bl=Np97vq}&AzN%$=zrk!P;5nP(dC1M4l$}D1|Ql<{c zCN5%JeqNNUnXKE=Mx<6W^$>2evorzy%Wi4)lm*XuCmy;;>D4x{q9qsBdH z$+XABNt@RXs<01p%zDvp=7}rgtn9~8-pptfI9_HrXbG?E#(`6J)1icrtu4jG;UUC( z@x^?9R0l9ECbLro$ z@!%79ELM}pT3l`zl10b3G8eSj9#dxMsG>6hpNWeup1aQr_gL^5X+R>Y1X2bxB*q-S za%M}NAo~R?`;>r?9JYKR=T75z{7Pf?bMutO!4Ba2lK99D+D~ZXQA>_P z3mJ9tM3r0(&-Ne?!9nAoZy5UP&j6KO0%{Adapjri*7(c1a&*!-g z7TTW2)%)(jhe+Fu?_<D8b$T!6xT&&|RnqL`j*%*1Mi*A^> zXZ?}QzM%asc8oX#`E)p>_O$M&h-1doqXZZ-REnlj?;$M;q7CHmT!BwV+Q#ceRK|cl zFDCjGF1N@=Q05;iKgp$i9E~^u7cU&lLR~m63{ez;N1D(`xWU+R&om8Gmea$A$ToTT zf#a-m)0UHVu&diHmR+F$CyqbEoVuL(K$7SR!2~u2O1|r@G^I-Bnh5(Rtv;<(a+F$0 ztl(iJ;;7-hn0f1VrLYgwy~0Rg{BF&v;&QV0IydY4=wTGyK@VTZ<>C;cYqHZxe7_2w zxKT*-h&j{oeC|)+W{o^>mDB0s2ce2V$GPxFT20v(*yJ8)W}6bsVG+8v)n9ZAzIWbt zi7xf7$>ZO?9Wac2@T;xHokmU0!M9Y)gqE24lT6rYIH`2wF&=GOO&!+t#o!qpui*Nb z5MW*$rzjChYbEB;7|CZ6QOu3|N3ktMoatjOT4m}cXW3~K42|(X+DyD0lck>d325lv z%FM`C$nDOO9)S|nO6ctE6xLxUtz&KuXs60cSFK|ZyWV(R5mTE=-WpDcGJM&efLHLQ zN@#N7&EM|&9b$42lw9w`59SxZ2k(|A`5Bg_nlp*M)j_7cCJ@54uSo&0bIn~N4 z-@4tUl4h{D3rJGkDTxP~{CX@yo&k8Y-EqxggnI_f0)Ew4m_>!Ss7v=cqD*z9CQ*mv zEhc;%a;T?H!}#=akFC}h3>>QHvzatojVDZAl_FZ3bSNdDAHj4)h)-a2m}pq^mNjf>CX!T5S-gz-hsit=!W z8|xHGohiM4KCM5^moxg?G9E`fCqpUb1Pnm|XMhx>b&pm0Qh@AI01%3xn~lh85M%)9 zlJlhvZ_5CvJ}XmkLu?)3#QJH{uFqh8GYD~yrG-Yk95U{trQ*w$9xR40rmeOim|qK9 zVvT;c;OiE}tyZqA!Dh1vXwi5DKhs(yaaQw$!2PYj$x$vu|1V3KOS-ZTPYc~7o-)=< z#44hLgeD%_OB|%07zKk510@W$Hj1!1{wI%uT&NVVEp)*O4K2liGYEDy9I*!sYO)++ zh9VG`Eka--*D#}z!CZ%B2_ZNcybm^1tHLmpsKR`=Ru~H&5e)T;mH3@z^^|C3cN&rL z*?VD0{eX%2z9}`2u1G@IgGB+QXJ{Bl0tylq#iSwy1gf*hsTxIwfg+jC zb|$;mSRE%hzP>(TH?)V&T&uzNR+GURGW>RRzJ3g@Z zqVjYA;V(C}#zRhupHY3jYwniMNRUGEGBtJA?ol9idXRQQChTu6$lpyp+@Pk0_A=nw znRQqKJp%pwMjPWC_y+7mA|*fl75=7I(xxV3P=!y|9lU^B`9+gokQ0*gtF)fd0(7ox z1$%5c1a`Kgn&Ycdk>$BeCG{7py|sX6`d4F7M&&T<^~UA6MoSj5YB&aT^kt|nLKv`y z(VbS+Qo;j;)NXz0vVWOS{_;wTE5%{un9f>qx=r8MUhJ(Zc|$1;XDVy<%=Mr)5QW63 zmxHe!1#j-vWZTC8A9j#7_II`&?U&QwkhkhDD_GgP^}k-yLR{ZY`^I11o)v-nyL^gW zi$?ct!+S!TpQ0b@YVRV`0$^<1c%B0Y4_f8oU{)>R=@*{AGOZ=ZR52d90gU2y*Ut+= zZ-h5-3fIZVy>77k=wkW@V(O+|aqH()9U|(cMH4qjC7V_*Y#nh(>&H^KEQ0LPPop*88A&p&JFM}LycPDs%hpJ_vY6r` z!U(eXbh2eo6mSjGALWfwI71KcBcS97of7KS4vlkTb%f+M%7D4*-9fr-fVch<(FVyoXzsdkOPHGNCs?L zKHOW+(}7-m1M!#4)nCg12Q$HT&rFUc#6pM5x=uSbej}=p9{u=0mN(7w!}@oE-&E)Z z)N8dVqa(s16~97II|zaI)vD;=#Rh%ciMUfBImm9Km81w6oAHtCDrBJl4C*5xHq1_T zq1uR*N}y`_3M%bZ)AaMmNn0w>ZMi*o0;}=U*N;9Q2sL60wd9FdsQtDS81DEgK_ga8 zkh{k1j3hnEyx2&_-JqTW_MlQJK_bb$ASApUU>${Vjk<5j)u2FHS|pt>3Q81i4+;`0 zSDHS1GZ7^j#t9HM7x|BoqqzO7%&ePyMSxKUQyn?C|8%7pv;%^>e&Fb4b9QG`ve2(} zg{j10=v876^#I8haLFa#jky8*;2kuuw5e4bo=U6K_|ww%h(q_Xv_=w{$uw6$`8&iLg{0CrDPwMdpS zx$%8Z0uhABrq+NKxKaz2{xgnGrHlrmz)4> zpJ+-nH(em5&A%86C6LAS-vgBm4Y09;Psx9*qH>8W>v*kRkF^2F9kc{jR5C=0fE^U+ zRIv;bO|G6%_t;?a6Aq$m82waKNliJ3;0Un{QmC6)Z7J+Y1d$W->IA-?oCD43JgISn zS1_aipBWk!QFtvRQ2Bg4FWW^w$cV`^ow7Ws)n;I{yPX#9rigKAo-jG5)TS~PTTp3g zq}dL3OE2?Hpg}G6-^e&VjZ<@xZ_pIZU&%;}qmKicZ6)uBXEC6j>hckc0{K)_sw@;=tSCk5<7hxPLJ?YH_`m zUbX^%;g2kD4{9Y}2yj7r$NdP+41#qO3?Be_%pFn5lqQjDf7`2J!8OpJZZuCs4MOr9 zfA2vMatMb9kPKw)deL=>=tM_oHK6B>OsS;O*U(4*eo%YM%&_IMo#2cyt~NWyJw}@HtGbnq`w!fMR)^W_;8cg&+SMs}J8GaYnL?sfMc`VuX)}&KeGr2BU{SP zQ3OH+EwYEDZ~%=AmTVltGq(Z>$FYZHM9HL-odbtE80JVpBL;3`)&4mVjaNsB;(JYf&)WfJ}Cye zocoI1!^;K{zNt#$D#i$}<ESv$8$b(!pnH~0+^HJ+zH_S|MSP(aom~pk zBwrLhWsGXL&d{1f`%s&tr{o*(%F&LZoI1s*vJ7ez6*gdg$4;u3_6X)r$}sQ`y_US+ zD1G=#PeMZ(jb_i!W|`*1KXh~q!l`6n)fAZl8heS&j@K5M`#$=-1l!GzZrOLUB`eK4 z>+RbyK(Va>_S=5weZ`MUG2i>E<=d0)fp1))=wG!pu=rPzc@ok>iFU!jF8~<;uMAVM zPOdrW%NqFJc-H8?l>a*jhAfTmbXSwS5U?|NbL@t{?iO-9=2+PE#$WYCxhw` zMO2;`QXzB(^eiJ5+R_|Y(Hl}l`88{mG?-yk*+_Y7mb9qz@7uTdVS)@^Kw*^O=emlE z58Sbky-Xyc_xGk$Z1&V+^BcDh

J;$BY+JLDweC9b<#r`Z!#cobPl-CYxd_Ej9sVHdU(CFfcxKLduNqmxky+V&+ zi7C0;sk2%_j)Y2IaRMyJG<5U#s|g(rCZ_=6(|Dq?o46-5QU~LJE5!^SX=lkpVCGJd z>0&{%gZI6q_|gp!G|~k++(4-AN9{NE%dFWs^wzg0f8M&r>swHG?4J;Js4bL#`SYpO zSGFi?_J(k>*bf=yab$7QzWx5l{B$LN&FgYnLsC$qhHU+mM} z_-scZY3vmU&G;G_*Q#k|8(8`lx(@o+QivAm(NIh<$_a`Xo~fk#NEW|;;BsGgA=w1{ z(#a_ejso{YsqS_2WIS?$w_oTZ{Hc&ebr?X%5cj_fc84**fTm8|i z_)Zr6c#tEPw8;~$o$hKsdcFDki~TT2Ql#Q%_j>FZCyz1FU@Y1;iWW5|p#lq`xUI%t zz`9~K(852F&jbrYjKS4)b6WNZrVNSOsfTAo+B(~93W|E4Fgt$F;aR z6AG~Sc$^fasv$x`*)lU%E&NO5o*J5z(RQ*o@)n-B1OL18$}sQpSM25PlitRw*2mty zhtYns+~J@e^Cb~bDbK9%+Zl8j|BR`~puy%2$ekTrmtLD)azM{HcDZ*sr+paUtj-FN zECOdl>KkyP3Kx-0xCe7n3La9A-Y1>zxD91`qfQm+iH2%*iKz8ggv=ggIm$8muQ`Rb zx-CPm%?On!4C7yj77>C`Xk;_SmMRM#q>khi`LN6?Y34t!dTgBD(1htZ=NS zTTgHAp31`w4@jPm6?Q1-x-X>#_FukSI&p~nFtGQfxkD9=xvMnBqfHVUtg6P5TCy8{ z%EYn;HpI^~9CK`AymM9dD1da>{-9sOKzL#L)qI-7SyL{0_pHS;$F>c7N!h~6$RAfA z@|YE&#cP((o`gHQ=#ByxZ$gD6QDsQYo4bxWn9?$Cnxr<3H0CbCjVo%4#wi%Hmpc`MhUbH>fh()k z^_{sZC zK$*9j;mL)TOsuN9)r$I(t%_5p;_5Ds@8-?ggdpEX4W zdkE(iHKJOj7$ue-(h{`8CJ#rZ_%6;QRF27SZ3hpNWnAjV3wR}m3kNl9K&EN`p4WVj ztl8V&$ETG7x!83`UPu<5*DnmR7pDVO)f^rifD)RaA?NqH<%3w1io&Ept68rxxI@H4 zz`ldBtW^SRy|unXLIsJXN8Ds?ihSLtSF!Uv-aM5`-+&$4CCjI&u^g4qzBQ?$jl#2^ zd$)mbC)**IkjV&9N$ihZprHhOtGWt1C$Ac2#DS!{Sc_B%PRd@xf|9cTg>bZO>~1>! zg~If>XQa?X5=U1ywQtFkYkAa^W?*`5jlF=zlKUISqCyFiyiV`x+rfxASPB@w*BwOt0kyU7giSiId*LnP3k4e3zr)1`F1bboLhGYV6^Z z63*g;h6Yz)!M8OKsJFJp(ebw(rPEa|v zJ=UkI;dAG$_g-ymv_UMBy5}(C{^C`)RcjwA9oi|Wb)UAow!0$gjO8!Ch@g;T^~FsV z!c{amR3#nzAd{9>P4F?zQ-}r5^=k$*a$b zcT3n>Sevyikh2o<-UwQihw}wE#{YHJKa!dFR`ei-wzl}HT!|NX+Gf^bhuw{OVqv$^%eJc2(H6;T` zXByT3Z;7Se1KwwzGL9QA8$PL*pL6GVp?T)#3iZd_KRc75Lt`oWS7uj3G4S7$^oO>d zy1o+bVD>Tgt-kP&uV1pI2a$$Ml_q<%pn8J>8gYW#QHp8@3Dg(Ku`0DWWMlsH_Guq! zv+JWF!BdBX^!!{?gNxpgX2e^V(!_Lakuq=qs<_KM3!i*J+b9yO zh_89g6$}z@DZTgFah~62u7OQ&v0O)10j7bjujCcX{9Ul@6;K}3#oIpU=j#q#A7{7C z$e@#N!5qb47CZswc{b?t@!KL{?2EdM92+c=8B>#qRJdZ8Wtx;3ONuSqm&cu=)Hc{a zMQWgjO=xpFu+}ZwA_AE0R_u`t6cpNa%FXtu7>S&eNVADgYSWFLchIk}SC1^O6YORn z8S_NznueM-MTO^{SWw0bjpA`+HaQeKEok&Fpz7$pRICNwwJQqJ|^J^8l`sDgT zZ~DIcVcUm2J<^^kues@-jiAmO*W$O!#ozbn$*usdm%mf_a(uhJ&=c#Ya_cwlBj=a+ z=EcRLh=@|wqLh?somRV=bUgZOnaevJJoaPAPGZ((f{n`?$x{>APA?TuMI#R6V2fI1 zHi3>@vt#Hd_evsMN9T%h3)LEkche*$wmMsAl_I82__PwY+0~0{7kIxF#1K3F8@(~( z3v>SPC*Evlq)}ZZyiLvkAFZBZc<-`2Gz~%@BB@aHB$AKRg3keOZzDOknPMDMhfYx94@V`XbpwbnS1~#NH$(2>=73ZhKiDcF|Rg)tK_b= zuwt{zeuhUTUbjE%Q*t{%ro?194OTI@w$D!Ie-&E@!-9xhu&Z3NZaRh(r}e+La^4-h zo##rjzV9}NsIeJ0+CGb4btJu@!{Mo7nd~~i)BKu1_ZRW;n3lf0J-^uHrhK6Fz28$b z3<9?d0#RHf;=LfX~D{B z8%ZmR_CZrHATIP%YbZ-A7u{DTE${&N@N;0P&ncc9-$NjEeN(H;;Yr@gn0`U=z9Bd_ zGq=tS9~T}c(vOcNa>aXB@wR7pCZTZMMj_*i8*+$2&!)}{{(bXwabAt4G@UK$<71%D zbl_pRZvC1B$Pv2&EpXF&ft3|ocS`G;cPCL<;aiQRmykPuQ%H=>-Uz}2u&fqS=CrNq z7ib9M6{IXEL8YyJ<}8rdSu^v}Qd029gXvJ#h9r0%VMTk!TA1m>xI}CY*qh_8f?r6d z3iafg!5V{KpmH0p2JZdAnqrz?P8J!zp%lt?f4%Vbi-c|f8k@Peu)lxq_G1&+9`rf2 zwe9|WG29TZDZ;c{pwncUa5IB%JtR0r<54iw>T~7_iGk`{!CdOYh0taOXMSNkY9x6^ z`9TxK$}nz&$UUiKU&_G(6*t)>uyTy{J*T_!`PFsNeBJrHK8Y{y zxBSpKv^q$k2QZkk1R^SvQ?zNF=cf zTNpcq6zLS!furOfcV;l4djvhZAv{pNiU$;*lM0fB4B~8S(E-%)Dpu(H zla(kLjj$Tt^UB3t0vE<@Nqor^@1RLiB)w+xhJwn_JnuVPZXm-gW;CoV;bO zgx{MOB^5pn;9N9|tv-+X1A)qPm)Y6e40H;`uQ`cU-p;^f|Jfq{|jku5CLh8z7rGo2Qw8 zLFNbKhLNHti=9$3zLjnO91-?hV?}9$76s@Q`cV6Xz zFmwYjb|wEj5Upoi0z=`o!g(L`igk&=LAOJ>R_VX%WbT7|{pv;giERsMe?`v;-dOTv z4G2rlC_W~&OR!025hhvEw9fot8&$$b&ast2dp|9-Ue{aV6A}%-=Hgn<+-*DBb*(rA$cm2&Vn_Blw9kZndlI(zC zIw-}pD7K;7bCImcyO&@hz#ZjCtXlq*!-d)`$6@)vg=%|@{jEOew zz|9H&C5;ubQw_;V)Uf?a<F0xOzvFnSnN4wh4eQ2R@)p0AlIdy(JQw{HOh{$dT0R&Uz;re{9ynz9*4m` zgvWs`M*&%Gnui^;RJtmdVm^?^l?s-nDMDQsl_lIh$h>;9e0)7S6H0ZPKM9EH=19h; zi#Z;WWf*CZXM~i=KS*b3o5MbBJ$&l+kIDf6fy0rW=UaO8sn^!pO~%-NcPHA92D>eN zj^A~h=(xH*m)5U8AFBYs-9`Gn`4y6xK{)*_qf1!v%Y1j0_X=CDd{*H(uvHnFKRwFv z&TcY#hIYaiduMy+$7jbCXfEkiHXgQsI0ZpAij*TOrcpX|>TTSa9h7bQOxq0m4r zB4WywC0WQZo3 zMj>A&ExAy;ykCm9ZA7&?#u{PgO|qg#t}CZxFH*&!twE~vO|Du1x5I=re1#2uZ+Xe_ zr28YG`-Kf2zWw6Nl0(7VEV|vX0~(w zQ<@-dR58SK&TODWjd3vGPv8uqXhYva6L4|$pE{1>)-n(@C$znjWZO9 z59lh-%5ku$_rgej)2A&~iI62@`ep%%>Kn04#RLNl1Q3DKo)Og#MMyYHOT&b{PooJs z6&9hI<}=zW!v6X5uOlX%^+#*0fsA8bm%RDETnLaQx?DTN$iU=~R!g1PqqPPT<^8nO ziBcRc5t6cjp786$yw#)Pp1YZbHAFFc@yDtN?S+sw#{~o~;rFF<$6s${iB|P!LRM@7 zb?hjXfCJ{AE$S(3#Ur>@t(LIu9%#)a=Bd&!ZYTOSllHuc*5%bBDOWYxzQ!aJ@Hd9v z_&C0z(FsuQG{NQNNo`>@EM6hyZB)gGj)^xlCBvpHGta~=IVhfR%GU(BreaVs?CcVM zh`_Vz5PFP+@4k%W*M{Os((l9awq0C{&rexa0h+w7S6W(Wc;+3(#zxoJH5KK@6VhH# z2wJtVefX+l^SK; z2e8IM?sy&xyFqI6B{DX`QuZs&^qTrRHoj81OL`-6)1Jj|{s9uAauN3Nrc`bq9i=A{ z5Xo9&6ejo)n-wbre=ggm&a3sAZIO@sGz=tJrV(S%|JJ7fg2U?56P1=uy7r@@|K{KZ zqdX%dHH&XdCJ*~?zJO1KkG;o4@rNnh8hEzHJ3^#f zE-p6aoY1Q^nwhjO-=~2!`1eYY4;iKih%>azHxwEnSGN%^yJqcG+f(Vg`5V%O0?H`M z=&f65UO~vV8$JAy=*%ep^#6q|&CkUh_ge4=E^SWrt_{vix|h3!IJMBprR!^TZ4!^6 zJl?#Fd6actq2*EelfW8bFiNQ%NLpmJGybmMAg4iNrF*25*)BIM-4-)qUFp4KZ2uv5 zO*Vt^5dMsH%Kq*$Y-4msA?F)s2&gfU|1DFe!q<6AV>5)WBL-v$r1=Ujkx9g%6C@l*C@9n6gRA#%HSo4F2&<5|Bz?AcGIf zTfF3Ow*(vv;X3&6XjqD21Ir|Pvt$?i8N(fWX*9gAH(oSvJQ6#GaoD;LcdP9sdq-ei zOVA!=^zpEqICNG0ciZ6H;0ue_a-@rHYX>ujCMl9ok5QdfHX7_2O;(o$JJue~eQ7#N ziEoH>Z~lc;HnFFxEN%BcQarcW)S6y)^tQYik8(R99!sAF4#$-(0qIBLq_~UgKH;>* z+SC4s0_ZX5CWu$CZR%RXg>-sagE(yAO7-wo^u(=JIQ>i~Vhs{S4B?cEL99LaphK~+ zo0e^<+0_%f1A$}i-NF;IW%GSE;h!YmZ`=B+;Z!LZ$+d`B(jrR099ifw(S;J+80kk( zN{^I6i}Z+>5o6Ky0nho62))2+N*E%EpB+fr%;0E*%`<6C5W@O?4y&2Cwku&N;yrq2AZIS19QefS|*Ub>15of4+gE%V{&F zsf2Ls5+nW^kxCMAOO?8aPv+CnPyVGaqeTzp?A}I+u5iEY0f@jALL0;$bhhC)ibts( z`@kI)@}tcw<~wo)4fh$hF?C#r4RV4UdZqCCF9%#ao!cZ{HDUvIJMV`(Tdw#u%{M;I(yV$jLDE7 zX%H%}PsL(Y2cXxKsWn)nD=qxiNH9osO0`Ng0OOr(>ave}BOPSe{XJ@aYQB2@0Dq(0 z>wOP7%JbrK!}_2TZgpbw5WtI~=#%5e12%qHy=tjfY8ebs3l2kM8GR1rZ&|C3(%;4C z9fk3%#5;W3xkvVnD&MhcIa`+69tbxJx34@dKA^5s1N^dp?QoC6D9qL7Ny+>)|ACT% zC4Uyr?}3y51tYihw}6Y0DKVb)1ss;o&6V36>x~W8J%Ogtsq|3V?I^gXT)T0li5(QiFOw^j8&et2jzT8V!lVI7-2fL({lo_) zJEglcL!hM!Q)QJQZWcTrG!p8JB*eDTZzoGA-hikQ(fP zuW(4q{59XCJ8Tfab?fEd)8n4LgST|hrO}ej$~pEul1tl&HWsq zPV*W@;j=1_3_#RYkN!ZwaUT;^9L7JA3aBxfC~_rXtUljYgEhZ?JVlSuaWPnRVwFIc z`RFFOCUOblgaNU@J9P)DZ)u;lPe8XL6z~jNGFviVjDK{2d~X#l3ybkkaVhENLw!+i zBo%ChgmI;AId>vyqcM>o85y({C>g8R4U`9V!m}8b!ae%^P|2CHk5WwuM+7g00Bg1l z5=_NZe9|aVoT#4L0ai$rnts~YYw|crL6{qX@2Aud&sd!r*N*ugP~c0tZ!dL!l|eQH z>8)*o)o-O65A}Zw9V^{b^~~6y3ynm|^b`UD6o@85un&kfZ1tq=Fn_~CsE$40i}$|c zJBvl%e)}kFE=7pkExM`n;5B}D0Sq1lR{#Qg@%$=k*RAM-J&c1Mi%sxdae*OW=tr;=g|2>+1b%cbessWI2@PQT zLlY%BSAV_IL->XRy{)3ObSFaR@pSRB-R-UAGn!iFIoQ12C*ZxbMvKD?7XUt&577Zy z%w~UA&1W2^OBW@4_AxwYOI`ETUa!bn#zrEtUJU(IT&}`VwcM}wGcJ*@Hod~m**y+k z-zK>n-rq01vd*6urf+Gv9=>T@PYGD|tKf2f&x|HZK*jTA)#EsD_3xI~^Xc>8wuiWIH`QBq|F7{a1;P8`z7{Vd ztL^s9Y0=vA>HVd%N}{cj$^6--1Lf_!2j-$M&`P8l`|vQ{0TWPjQ`?=Sm^6Ehw-?`) zA(t_IEwWeK6^SRp>=bAg*aZ|n(04rM+X*_9)-iXi=W%ujq?CXh>#Q)Uj>fjc#-_#I zl-5B3Zd_89XVdwD1kMDaze0NIDn)~*c8^=rWj`aFToLH=CC<0Q-Ki3Z(yv!i-=e5G z1gV^t-CmrU%YE{SWZDuV5}G%cz6ieKp})s`s8wU;LA*R(n!HH;NeVdKr(q3gi>=63 zJ6bJXi=gD-q&fp?!{YB#@ZN6+-C3vsmAipQ&Fr7W_lc}eP7NkTdEVMh(!wWF_v1fJ ze#2g?#H%p9x7~dHnWijc)>aNu4rJ_0?h{`rdX`i92-w?~d{7hlVJcR3n}B}?6Wq5%u{5_m~CLhM#>NkcWZ z%}67a)Vpin+GX3M8P*YI-IZO#p;#&ucsZqg_cpR~QL*8&ZI8c&sIiUFVen3xwr_|oITx2i-boCNGN)iim~&=P_v_*`ktwgp{Qz-(%LiRDUr znh9$9-PS|~;&Q%e^2E&wmc4OhnvD-jr-A$3IvPq-3*u`zlTMr6rWcT9b}fxpKtA?h zJ7YZ%Y1}I&PW*2DMJ!Xx6Ypz>haKhPQ@is`3xsxOlBag#A`*)f-`9(lbDE^_lR5x2 zO-lnwZTFmBL3z9G;ua&cDb!?KDzC5CvUcMW;(YT_`H?`VhDbg~m;w); zy}Wtnko!yLV5|yd?mc>%BtL~*?F$jJWz0M(mJB2t2m*RjvAz4YU%`mofFCn`tMS{m zOqf6Bg>jI74CL19{SB%&Wiv{J(QoL|Gjoh=*#KXRZW}4;zf{pC(wZ~BOFrz0t(?p5Y)X93$LH6NtPWN~ zq=9zTyQI}~k~QWGy-y9r0fcNt4J^2o5exk|idYM~PW1uuZ7de4Fbd>lfdaA$*r_?N zRxVQ*Kl&hi%`15zc0T(R=4UTgk_5(WZZcCJLgTu2BPR_0u!BPR)0-?|j3lZ4F|l&} zG{}&HkDLUFeO57^E+{CnNtVmS0SZXwwR!rH^)g`XXg8ATA-%PR~0a%C+|O zy9=%i+@IJ(KB}MaS~7+Af~4o9+zZhYM*;HcHuO^cEUX6%0rCdM31+{=h7Gw=?nq9#GMfsUf2?#jRnlyrhYfye zy|g60SCTNu&E*lsz#0uHvp{IL^7)P8v)}RFLNYBa$M!LS(=-PjbIJOHR4j_c?Lt!T|lM+RtnSCZc;9W-({VDXJ;Q zP`XBb?-L>d?25v|E#c2{l9=`NPRJGT!Tr)g#BO@(z7Q(?B}&hU7fN~~hwds)hh|D0 zZc5&LObNY~3FG_OsNW1By!cyLuqK!_Gt7#dzhX_Pm{ z2y1*cPtF{vbL0H%?D*L7@-kH3WV9voUHats?C2O^T0Hw{VK!V}H&<8pwZ34>{a?nA zkVazVFtakJAN*0d{13*^COgYky9|2XhPY%HM&wz8YUeLv+L9MJ((UaK5bavM5cDTj zco^sx`Mw+T5bdm~1uMnv?fLE88`HQjjX^Abngwh^>scRJSy^>rE(MfEj$1q}SJ~P1 z$D+^yj${}tiWq6)y@s^dq-<+!CN?%Mt|gCkTR*5);owTFyzTD#UjMj)Vw#ha5f`U< zCubqT=OOfX&e>%d zK33!3-RgN3zeiu`K|X&Za8M+l2W2lryaD+m{`d8+Bwy~X@k>Y8HaUgM04gBD__qUC z!#J$XD$A@_1EVACRaiD&U5BtvePzL8?kk<642HcU-r2ed5YB_}&il{%R~zj}3*(*9 z)N1yk@yw5XTu|5}k%is1((IsxFJAA871E6hn24=3{PUNq-7`2ZzYI0kpZ7z@L;%~z zAS51};X!Z6uvXEfzc$ohw2eiqZa`H{Zr z!zq_4Y=#+oS*St}^*9~RY^G&~Q(e*cK&aKO$Vp&fGnh_(cJ5#CB)$T+m+(sgO|4ay z=)_qAw+g%VHEbXGDquek*tbAi=FoMoH8o4ME?7;0Mt$`85Z+e}Ajke6eD>UM+aN1?J$`d=y2d&3J7lh3=VryD2NNm#=t$)HZ z6Wp1Y8!Ud;n^G{ddFdL1y88J3@z%Q3kT5gZ+3qm#m-nceJGx&21l<$`70b}=38gBH z%tXVDd7#Ih#`VSv4%L9L{$hwmsq=W8dJPsgh=^?#ra*hdbTcMS5+3C+PJ3LZSq3iX zX-LNC+%J#*gCs^T(EI>U;`4Jq7_ z+lD)ffiigVO=X>dnQ+PeYz?jtI7_LXU}RiVQp3WJO{#htx5OU&RH)0TSV_b-i@rgV z{b%JFb8M?QOL@29^WY%F#&y$2nj_S4a#W)|Yk`)y!~=D8Q)JGGH;TK(xXEAn4qcGh zfsT3QT@hX9T|`~Ee_G>_(#^Iqx)i!tjikDNWwbZ8(*>UaaSWUtCN|orWZ^pvk0U$R zkKtI)L8lR(N06716FfaI(Uyz zHzF^U`<*!enNOh)>N_QN<&Da*^!s1eR#-E2;Cpe_2lhCKT^{cPqbTb-tGgb}m{a9YMl`2* zp7;(bE@VK85Pf*^8gIO#UNdXA5=VyllbdI=%Ub|2{A|?8^VWrF`rZV=22tnljyM;l z2=0yfM52pf8axp;<%Y;%;>-J`L)5AZ+!jf8e{yn%$>Ftm`OIenZ~J3lQr9W(7TDj6 zdq#M2F?#oU$4TK0;|hJH`na%^FZ4z``Oe|J=e<{6)Oc-!H~IBG@cj#K;MI|+hOgIu zssXx){&4d4Cyigs`P1DnWhhmLY}MWcjdk)PlqfFcqSg4L8&Jd;wtDapu%~jQ?gyTE zJ?4_fo)um|lEj{geBj25)&MUAvV_AI?eL%HoR2=g#GFr^KW|uLLj)Q|0@uZoxf{*b zUR3j!yTOS0y&*Gtg2!}f&w6%K8p_+iD(}RBeaI3-V8K3nyJKuZ$FMkS)r@}`Q@^-N z)l9Hzad3jQem<$Pc$Iu?0!-N*(c-d7%*xyoE<}$~`gpLVuA?1-8c?e1)Rf>KN zSO+d{52@C%h2b;T&Zdg!$BOZnEdxwMDFf9)yX7o?5VkE}BFs;5YQ_MII6@u3nQPG` zUZdr9_N~_w_wVgdkefS(sSxzI0s46F5EtB2^aEHUgbc(a7+TX3`fjz-6 zXFncch#wd;-5{6D`^IhXrpFL4eYusjBU1DO=h}>sm*GV@gJ+P3H_c1^5}_8z&O^zK zEH9wKMQ{>qAsPeYf^ff-P&^FJThN)Z7y3icFV@pQLQ9_FM-uB954hsez==3_EKO5}A?$Rm%xJQ5 z8FBy1i*$ol7|QsUchql8!$WpXnim6Q0UU-JGXI`<{k*8o9@R(**k2{W;OH&D#ZZS&q+no6ZPgRJ!;cw zX46n*%W3AdT}kNLg32tKCYI`3#XkvR&at|k`@+<)qm~;lqFg{@Bx*h)>DEuR2n&w_ z5cXuf`T9cb;jljS{Q)Z`!iHT>ZPdH<0;;>Q3PUcwh+&8wdk%je6Z;2+i4p&WDt)c>kt!*XQ4Ym#@f7{f^`k=r7y~@K zJgIz6t^9#y9x-4zVv#RO2o__=|F;UABu~_@KX2#&max2FXkCKPWt35E2{I*sVAst^ zZr~QiFtsGG*tdYx*>n79kL7xN9OwSDz=v}rtIbM9s@c)ux09v)+AF$=^Wb<~s~``( z5^iM{=5Lt=g#5_zW#`Zns~mT!w<-FS25ZaFYOBEFf6D-KAoJ-7SuV9+jR4V zHiyb!fdu+0akx{6L-}Rff7_ATv=j3b|E2nlD(%d$J@n1G{bX^7UH{eaS2h=RdTp1! z*1QZ0h@s@Rgflk6NW&Xi}T@iJ~PlsQePDcVm|LySQ#MoOq=B>HB zEl>|4L$2ZEi0xkpWscTcTW{BOTY}YU&`~Fn@O7Ueas~Gyzs|n%8XnxhPyYfyY?Q

NE-9@WoZvs2H$5@~^C$3H{{H}Z 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

;_qWWv49Jvy7cE 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 diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index c54d6ea..5b8e1f0 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -8,9 +8,9 @@ * Reference APIs needed to support Widevine's crypto algorithms. * * See the document "WV Modular DRM Security Integration Guide for Common - * Encryption (CENC) -- version 15.2" for a description of this API. You + * Encryption (CENC) -- version 16.2" for a description of this API. You * can find this document in the widevine repository as - * docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v15.pdf + * docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf * Changes between different versions of this API are documented in the files * docs/Widevine_Modular_DRM_Version_*_Delta.pdf * @@ -147,13 +147,22 @@ typedef struct { * OEMCrypto_SubSampleDescription Structure * * Description: - * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * This structure is used as parameters in the OEMCrypto_DecryptCENC + * function. In the DASH specification, a sample is composed of multiple + * samples, and each subsample is composed of two regions. The first region + * is clear unprotected data. We also call this clear data or unencrypted + * data. Immediately following the clear region is the protected region. The + * protected region is encrypted or encrypted with a pattern. The pattern and + * number of bytes that are encrypted in the protected region is discussed in + * this document when we talk about the function OEMCryptoDecryptCENC. For + * historic reasons, this document also calls the protected region the + * encrypted region. * * Fields: - * [in] num_bytes_clear: The number of clear bytes in this subsample. The - * clear bytes come before the encrypted bytes. - * [in] num_bytes_encrypted: The number of encrypted bytes in this subsample. - * The encrypted bytes come after the clear bytes. + * [in] num_bytes_clear: The number of unprotected bytes in this subsample. + * The clear bytes come before the encrypted bytes. + * [in] num_bytes_encrypted: The number of protected bytes in this subsample. + * The protected bytes come after the clear bytes. * [in] subsample_flags: bitwise flags indicating if this is the first, * middle, or last subsample in a sample. 1 = first subsample, 2 = last * subsample, 3 = both first and last subsample, 0 = neither first nor last @@ -231,14 +240,6 @@ typedef enum OEMCryptoCipherMode { OEMCrypto_CipherMode_CBC, } OEMCryptoCipherMode; -/** OEMCrypto_LicenseType is used in LoadKeys to indicate if the key objects - * are for content keys, or for entitlement keys. - */ -typedef enum OEMCrypto_LicenseType { - OEMCrypto_ContentLicense = 0, - OEMCrypto_EntitlementLicense = 1 -} OEMCrypto_LicenseType; - /* * OEMCrypto_EntitledContentKeyObject * Contains encrypted content key data for loading into the sessions keytable. @@ -358,12 +359,6 @@ typedef enum OEMCrypto_ProvisioningMethod { OEMCrypto_OEMCertificate = 3 // Device has factory installed OEM certificate. } OEMCrypto_ProvisioningMethod; -/* Private key type used in OEMCrypto_LoadDRMPrivateKey. */ -typedef enum OEMCrypto_PrivateKeyType { - OEMCrypto_RSA_Private_Key, - OEMCrypto_ECC_Private_Key, -} OEMCrypto_PrivateKeyType; - /* * Flags indicating public/private key types supported. */ @@ -833,6 +828,11 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( * the function ODK_SetNonceValue(&nonce_values, nonce). The ODK functions * are documented in "Widevine Core Message Serialization". * + * This function shall only be called at most once per open session. It shall + * only be called before signing either a provisioning request or a license + * request. If an attempt is made to generate a nonce while in the wrong + * state, an error of OEMCrypto_ERROR_INVALID_CONTEXT is returned. + * * Parameters: * [in] session: handle for the session to be used. * [out] nonce: pointer to memory to receive the computed nonce. @@ -957,10 +957,12 @@ OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( * key. The entire message is the buffer starting at message with length * message_length. * - * If nonce_values.api_level is 15, then OEMCrypto shall compute the + * If nonce_values.api_major_version is 15, then OEMCrypto shall compute the * signature of the message body using the session's client renewal mac key. * The message body is the buffer starting at message+core_message_size with - * length message_length-core_message_size. + * length message_length-core_message_size. If the session has not had a + * license loaded, it will use the usage entries client mac key to sign the + * message body. * * This function generates a HMAC-SHA256 signature using the mac_key[client] * for license request signing under the license server protocol for CENC. @@ -1162,7 +1164,8 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * The mac_key and encrypt_key were generated and stored by the previous call * to OEMCrypto_GenerateDerivedKeys() or * OEMCrypto_DeriveKeysFromSessionKey(). The nonce was generated and stored - * by the previous call to OEMCrypto_GenerateNonce(). + * in the session's nonce_values by the previous call to + * OEMCrypto_GenerateNonce(). * * This session's elapsed time clock is started at 0. The clock will be used * in OEMCrypto_DecryptCENC(). @@ -1492,10 +1495,13 @@ OEMCryptoResult OEMCrypto_LoadKeys( * OEMCrypto_ERROR_LICENSE_INACTIVE is returned. * 24. The data in enc_mac_keys_iv is not identical to the 16 bytes before * enc_mac_keys. If it is, return OEMCrypto_ERROR_INVALID_CONTEXT. + * * Usage Table and Provider Session Token (pst) + * The function ODK_ParseLicense takes several parameters that may need more + * explanation. * The parameter usage_entry_present shall be set to true if a usage entry - * was created or loaded for this session. This parameter is passed into - * ODK_ParseLicense and used for usage entry verification. + * was created or loaded for this session. This parameter is used by + * ODK_ParseLicense for usage entry verification. * The parameter initial_license_load shall be false if the usage entry was * loaded. If there is no usage entry or if the usage entry was created with * OEMCrypto_CreateNewUsageEntry, then initial_license_load shall be true. @@ -1712,6 +1718,29 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( * signature verification shall use a constant-time algorithm (a signature * mismatch will always take the same time as a successful comparison). * + * The key control from the first OEMCrypto_KeyRefreshObject in the key_array + * shall be extracted. If it is encrypted, as described below, it shall be + * decrypted. The duration from the key control shall be extracted and + * converted to host byte order. This duration shall be passed to the + * function ODK_RefreshV15Values as the parameter new_key_duration. + * + * If the KeyRefreshObject's key_control_iv has zero length, then the + * key_control is not encrypted. If the key_control_iv is specified, then + * key_control is encrypted with the first 128 bits of the corresponding + * content key. + * + * If the KeyRefreshObject's key_id has zero length, then it is an error for + * the key_control_iv to have nonzero length. OEMCrypto shall return an error + * of OEMCrypto_ERROR_INVALID_CONTEXT. + * + * If the session's license_type is OEMCrypto_ContentLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching content_key_id is used. + * + * If the session's license_type is OEMCrypto_EntitlementLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching entitlment_key_id is used. + * * The function ODK_RefreshV15Values shall be called to update the clock * values. See the document "Widevine Core Message Serialization" for the * documentation of the ODK library functions. @@ -1782,19 +1811,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys( * Updates the clock values and resets the renewal timer for the current * session. * - * OEMCrypto shall verify the signature of the message using the session's - * renewal mac key for the server. If the signature does not match, OEMCrypto - * returns OEMCrypto_ERROR_SIGNATURE_FAILURE. + * OEMCrypto shall verify the signature of the entire message using the + * session's renewal mac key for the server. The entire message is the buffer + * starting at message with length message_length. If the signature does not + * match, OEMCrypto returns OEMCrypto_ERROR_SIGNATURE_FAILURE. * - * If nonce_values.api_level is 16, then OEMCrypto shall verify the signature - * of the entire message using the session's server renewal mac key. The - * entire message is the buffer starting at message with length - * message_length. - * - * If nonce_values.api_level is 15, then OEMCrypto shall compute the - * signature of the message body using the session's server renewal mac key. - * The entire message is the buffer starting at message+core_message_size - * with length message_length-core_message_size. + * OEMCrypto shall verify that nonce_values.api_major_version is 16. If not, + * return the error OEMCrypto_ERROR_INVALID_CONTEXT. Legacy licenses will use + * the function OEMCrypto_RefreshKeys instead of OEMCrypto_LoadRenewal. * * If the signature passes, OEMCrypto shall use the function * ODK_ParseRenewal, as described in the document "Widevine Core Message @@ -2178,51 +2202,34 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * The 'cbcs' scheme is OEMCrypto_CipherMode_CBC with an encryption pattern. * Only some of the bytes in the encrypted portion of each subsample are * encrypted. In the pattern parameter, the encrypt and skip fields will - * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content - * and SAMPLE-AES HLS content. + * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content, + * SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme. * * The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If * the skip field is zero, then patterns are not in use and all crypto blocks - * in the encrypted subsample are encrypted. It is not valid for the encrypt - * field to be zero. + * in the encrypted part of the subsample are encrypted. It is not valid for + * the encrypt field to be zero. * * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, - * if an encrypted subsample has a length that is not a multiple of 16 bytes, - * then the final bytes that do not make up a full crypto block should be - * treated as clear and should not be decrypted. The following diagram - * provides an example: + * if the encrypted part of a subsample has a length that is not a multiple + * of 16 bytes, then the final bytes that do not make up a full crypto block + * are clear and should never be decrypted. The following diagram provides an + * example: * * (See drawing in "Widevine Modular DRM Security Integration Guide") * + * Whether any given protected block is actually encrypted also depends on + * the pattern. But the bytes at the end that do not make up a full crypto + * block will never be encrypted, regardless of what the pattern is. Even if + * the pattern says to decrypt every protected block, these bytes are clear + * and should not be decrypted. + * * Of course, if the encrypted subsample has a length that is a multiple of - * 16 bytes, the final bytes should be decrypted. The following diagram - * provides an example: + * 16 bytes, all the bytes in it are protected, and they may need to be + * decrypted following the pattern. The following diagram provides an example: * * (See drawing in "Widevine Modular DRM Security Integration Guide") * - * If the encrypted subsample has a length that is not an even multiple of - * the pattern length, then there may also be extra clear blocks at the end. - * - * If there are not enough bytes at the end of the encrypted subsample to - * complete an iteration of the encrypted part of the pattern, even if there - * are enough bytes to make a full crypto block, then the final bytes that do - * not fill the encrypted part of the pattern should be treated as clear and - * should not be decrypted. The following diagram provides an example: - * - * (See drawing in "Widevine Modular DRM Security Integration Guide") - * - * If there are enough bytes at the end of the encrypted subsample to - * complete an iteration of the encrypted part of the pattern, even if there - * are not enough bytes to complete the clear part of the pattern, then the - * bytes that fill the encrypted part of the pattern should be treated as - * encrypted. The following diagram provides an example: - * - * (See drawing in "Widevine Modular DRM Security Integration Guide") - * - * This behavior is specified by the ISO-CENC standard. Refer to the ISO-CENC - * standard, section 9.6, for full details of how patterns are to be applied - * to content. - * * INITIALIZATION VECTOR BETWEEN SUBSAMPLES: * * The IV is specified for the initial subsample in a sample in the iv field @@ -2491,7 +2498,6 @@ OEMCryptoResult OEMCrypto_DecryptCENC( * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support subsample sizes and total input buffer sizes as * specified by its resource rating tier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is @@ -3235,7 +3241,7 @@ uint32_t OEMCrypto_APIVersion(void); * number allows the calling application to avoid version mis-match errors, * because this API is part of a shared library. * - * The minor version specified in this document is 1. Any OEM that returns + * The minor version specified in this document is 2. Any OEM that returns * this version number guarantees it passes all unit tests associated with * this version. * @@ -3762,13 +3768,6 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * sessions with 4 keys each (80 total), but it does not need to support 20 * sessions with 20 keys each. * - * Living room devices refer to devices such as TVs, set top boxes, game - * consoles, blu-ray players etc. These devices tend to have reduced UI and - * frequently are dedicated to playing video. For these devices, we expect - * there to be more memory dedicated to video playback and video - * applications. The number of sessions required for living room devices is - * larger than for mobile devices. - * * The message size that is needed for a license with a large number of keys * is larger than in previous versions. The message size limit applies to all * functions that sign or verify messages. It also applies to the size of @@ -3780,11 +3779,14 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * should also support a higher frame rate. Platforms may enforce these * values. For example Android will enforce a frame rate via a GTS test. * + * Note on units: We will use KiB to mean 1024 bytes and MiB to mean 1024 KiB, + * as described at https://en.wikipedia.org/wiki/Kibibyte. + * * +--------------------------------+---------+----------+---------+---------+ * |Resource Rating Tier |1 - Low |2 - Medium|3 - High |4 - Very | * | | | | | High | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum Sample size |1 MB |2 MB |4 MB |16 MB | + * |Minimum Sample size |1 MiB |2 MiB |4 MiB |16 MiB | * +--------------------------------+---------+----------+---------+---------+ * |Minimum Number of Subsamples |10 |16 |32 |64 | * | (H264 or HEVC) | | | | | @@ -3795,16 +3797,12 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * |Minimum Number of Subsamples |72 |144 |288 |576 | * |(AV1) | | | | | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum subsample buffer size |100 KB |500 KB |1 MB |4 MB | + * |Minimum subsample buffer size |100 KiB |500 KiB |1 MiB |4 MiB | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum Generic crypto buffer |10 KB |100 KB |500 KB |1 MB | + * |Minimum Generic crypto buffer |10 KiB |100 KiB |500 KiB |1 MiB | * |size | | | | | * +--------------------------------+---------+----------+---------+---------+ - * |Minimum number of open sessions |10 |20 |20 |30 | - * |(mobile devices) | | | | | - * +--------------------------------+---------+----------+---------+---------+ - * |Minimum number of open sessions |10 |100 |100 |100 | - * |(living room devices) | | | | | + * |Minimum number of open sessions |10 |20 |30 |40 | * +--------------------------------+---------+----------+---------+---------+ * |Minimum number of keys per |4 |20 |20 |30 | * |session | | | | | @@ -4304,6 +4302,11 @@ OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session, * usage entry associated with it, the error * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. * + * Before version API 16, the usage entry stored the time that the license + * was loaded. This value is now interpreted as the time that the licence + * request was signed. This can be achieved by simply renaming the field and + * using the same value when reloading an older entry. + * * Parameters: * [in] session: handle for the session to be used. * [in] usage_entry_number: index of existing usage entry. @@ -4522,6 +4525,10 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * HMAC SHA1 signature is used to prevent a rogue application from using * OMECrypto_GenerateSignature to forge a Usage Report. * + * Before version 16 of this API, seconds_since_license_received was reported + * instead of seconds_since_license_signed. For any practical bookkeeping + * purposes, these events are essentially at the same time. + * * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. * @@ -4985,7 +4992,6 @@ OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, /****************************************************************************/ /****************************************************************************/ - #ifdef __cplusplus } #endif diff --git a/oemcrypto/odk/Android.bp b/oemcrypto/odk/Android.bp index bbbe288..7aa8eb0 100644 --- a/oemcrypto/odk/Android.bp +++ b/oemcrypto/odk/Android.bp @@ -1,4 +1,6 @@ - +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. // ---------------------------------------------------------------- // Builds libwv_odk.a, The ODK Library (libwv_odk) is used by @@ -16,6 +18,7 @@ cc_library_static { "src/odk_overflow.c", "src/odk_serialize.c", "src/odk_timer.c", + "src/odk_util.c", "src/serialization_base.c", ], proprietary: true, diff --git a/oemcrypto/odk/README b/oemcrypto/odk/README index b92f5d7..6bd1f54 100644 --- a/oemcrypto/odk/README +++ b/oemcrypto/odk/README @@ -1,6 +1,6 @@ The ODK Library is used to generate and parse core OEMCrypto messages. -This library is used by both OEMcrypto on a device, and by Widvine license and +This library is used by both OEMCrypto on a device, and by Widvine license and provisioning servers. The source of truth for these files is in the server code base on piper. Do not diff --git a/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/oemcrypto/odk/include/OEMCryptoCENCCommon.h index 53aba87..e8f2ad9 100644 --- a/oemcrypto/odk/include/OEMCryptoCENCCommon.h +++ b/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -106,6 +106,21 @@ typedef enum OEMCrypto_Usage_Entry_Status { kInactiveUnused = 4, } OEMCrypto_Usage_Entry_Status; +/* + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1 +} OEMCrypto_LicenseType; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + /* * OEMCrypto_Substring * diff --git a/oemcrypto/odk/include/core_message_serialize.h b/oemcrypto/odk/include/core_message_serialize.h index 38c5c51..ffa43f5 100644 --- a/oemcrypto/odk/include/core_message_serialize.h +++ b/oemcrypto/odk/include/core_message_serialize.h @@ -30,10 +30,12 @@ namespace serialize { * Parameters: * [in] parsed_lic * [in] core_request + * [in] core_request_sha256 * [out] oemcrypto_core_message */ bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, std::string* oemcrypto_core_message); /** @@ -41,9 +43,11 @@ bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, * * Parameters: * [in] core_request + * [in] renewal_duration_seconds * [out] oemcrypto_core_message */ bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, std::string* oemcrypto_core_message); /** diff --git a/oemcrypto/odk/include/core_message_serialize_proto.h b/oemcrypto/odk/include/core_message_serialize_proto.h index 9b7cdad..0f494de 100644 --- a/oemcrypto/odk/include/core_message_serialize_proto.h +++ b/oemcrypto/odk/include/core_message_serialize_proto.h @@ -30,13 +30,16 @@ namespace serialize { * * Parameters: * [in] serialized_license - * serialized video_widevine::License - * [in] core_request - * [out] oemcrypto_core_message + serialized video_widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. */ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, const ODK_LicenseRequest& core_request, const std::string& core_request_sha256, + const bool nonce_required, std::string* oemcrypto_core_message); /** diff --git a/oemcrypto/odk/include/core_message_types.h b/oemcrypto/odk/include/core_message_types.h index 2b53aab..488e5d2 100644 --- a/oemcrypto/odk/include/core_message_types.h +++ b/oemcrypto/odk/include/core_message_types.h @@ -64,7 +64,8 @@ namespace oemcrypto_core_message { * Input structure for CreateCoreLicenseResponse */ struct ODK_LicenseRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; }; @@ -74,7 +75,8 @@ struct ODK_LicenseRequest { * Input structure for CreateCoreRenewalResponse */ struct ODK_RenewalRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; uint64_t playback_time_seconds; @@ -85,7 +87,8 @@ struct ODK_RenewalRequest { * Input structure for CreateCoreProvisioningResponse */ struct ODK_ProvisioningRequest { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; std::string device_id; diff --git a/oemcrypto/odk/include/odk.h b/oemcrypto/odk/include/odk.h index c6db79d..6bbf36f 100644 --- a/oemcrypto/odk/include/odk.h +++ b/oemcrypto/odk/include/odk.h @@ -65,7 +65,7 @@ extern "C" { * [out] timer_limits: the session's timer limits. * [out] clock_values: the session's clock values. * [out] nonce_values: the session's ODK nonce values. - * [in] api_version: the API version of OEMCrypto. + * [in] api_major_version: the API version of OEMCrypto. * [in] session_id: the session id of the newly created session. * * Returns: @@ -78,7 +78,7 @@ extern "C" { OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id); /* @@ -127,7 +127,9 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, * * Description: * This function sets the values in the clock_values structure. It shall be - * called from OEMCrypto_LoadUsageEntry. + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_signed. * * Parameters: * [in/out] clock_values: the session's clock data. @@ -231,7 +233,7 @@ OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, * * Description: * This function modifies the session's clock values to indicate that the - * license has been deactiviated. It shall be called from + * license has been deactivated. It shall be called from * OEMCrypto_DeactivateUsageEntry * * Parameters: @@ -256,8 +258,8 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -271,7 +273,7 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -292,8 +294,14 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -303,31 +311,33 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * [in/out] core_message_size: length of the core message at the beginning of * the message. (in) size of buffer reserved for the core message, in * bytes. (out) actual length of the core message, in bytes. - * [in] nonce_values: pointer to the session's nonce data. - * [in] clock_values: the session's clock values. + * [in/out] nonce_values: pointer to the session's nonce data. + * [in/out] clock_values: the session's clock values. * [in] system_time_seconds: the current time on OEMCrypto's clock, in * seconds. * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: * This method is new in version 16 of the API. */ -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_size, - const ODK_NonceValues* nonce_values, ODK_ClockValues* clock_values, - uint64_t system_time_seconds); +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); /* * ODK_PrepareCoreProvisioningRequest * * Description: * Modifies the message to include a core provisioning request at the - * beginning of the message buffer. The values in nonce_values, clock_values - * and system_time_seconds are used to populate the message. + * beginning of the message buffer. The values in nonce_values are used to + * populate the message. * * This shall be called by OEMCrypto from * OEMCrypto_PrepAndSignProvisioningRequest. @@ -336,8 +346,8 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and * stable across reboots and factory resets for an L1 device. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -356,7 +366,7 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -373,7 +383,7 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( * Description: * This function sets all limits in the timer_limits struct to the * key_duration and initializes the other values. The field - * nonce_values.api_level will be set to 15. It shall be called from + * nonce_values.api_major_version will be set to 15. It shall be called from * OEMCrypto_LoadKeys when loading a legacy license. * * Parameters: @@ -403,8 +413,12 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * ODK_RefreshV15Values * * Description: - * This function updates the clock_values as needed if the renewal is - * accepted. The field nonce_values.api_level is verified to be 15. + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. * * Parameters: * [in] timer_limits: The session's timer limits. @@ -412,6 +426,8 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * [in] nonce_values: The session's ODK nonce values. * [in] system_time_seconds: The current time on the system clock, as * described in the document "License Duration and Renewal". + * [in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. * [out] timer_value: set to the new timer value. Only used if the return * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a * hardware timer. @@ -432,27 +448,43 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, const ODK_NonceValues* nonce_values, uint64_t system_time_seconds, + uint32_t new_key_duration, uint64_t* timer_value); /* * ODK_ParseLicense * * Description: - * The function ODK_ParseLicense will parse the message and verify - * - * 1. Either the nonce matches the one passed in or the license does not - * require a nonce. - * 2. The API version of the message matches. - * 3. The session id matches. - * The function ODK_ParseLicense will parse the message and set each - * substring pointer to point to a location in the message body. The message - * body is the buffer starting at message + core_message_length with size - * message_length - core_message_length. + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. * * If the message does not parse correctly, ODK_VerifyAndParseLicense will * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM - * layer above. If the api in the message is larger than 16, then - * ODK_UNSUPPORTED_API is returned. + * layer above. + * + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify that each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. * * Parameters: * [in] message: pointer to the message buffer. @@ -463,6 +495,8 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * false when called for OEMCrypto_ReloadLicense. * [in] usage_entry_present: true if the session has a new usage entry * associated with it created via OEMCrypto_CreateNewUsageEntry. + * [in] request_hash: the hash of the license request core message. This was + * computed by OEMCrypto when the license request was signed. * [in/out] timer_limits: The session's timer limits. These will be updated. * [in/out] clock_values: The session's clock values. These will be updated. * [in/out] nonce_values: The session's nonce values. These will be updated. @@ -470,7 +504,7 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or there * were other incorrect values. An error should be returned to the CDM * layer. * ODK_UNSUPPORTED_API @@ -490,9 +524,12 @@ OEMCryptoResult ODK_ParseLicense( * ODK_ParseRenewal * * Description: - * The function ODK_ParseRenewal will parse the message and verify that the - * nonce values match those in the license. If the message does not parse - * correctly, an error of ODK_ERROR_CORE_MESSAGE is returned. + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. * * After parsing the message, this function updates the clock_values based on * the timer_limits and the current system time. If playback may not @@ -504,8 +541,8 @@ OEMCryptoResult ODK_ParseLicense( * ODK_DISABLE_TIMER, then playback time is not limited. * * If OEMCrypto uses a hardware timer, and this function returns - * ODK_SET_TIMER, then the timer should be set to the value pointed to by - * timer_value. + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. * * Parameters: * [in] message: pointer to the message buffer. @@ -522,15 +559,16 @@ OEMCryptoResult ODK_ParseLicense( * hardware timer. * * Returns: - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_SET_TIMER: Success. The timer should be reset to the specified timer * value. * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is * allowed. * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. * ODK_UNSUPPORTED_API + * ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. * OEMCrypto_ERROR_INVALID_NONCE * * Version: @@ -551,14 +589,21 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * The function ODK_ParseProvisioning will parse the message and verify the * nonce values match those in the license. * - * The function ODK_ParseProvisioning will parse the message and set each - * substring pointer to point to a location in the message body. The message - * body is the buffer starting at message + core_message_length with size - * message_length - core_message_length. - * * If the message does not parse correctly, ODK_ParseProvisioning will return * an error that OEMCrypto should return to the CDM layer above. * + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - + * core_message_length. + * * Parameters: * [in] message: pointer to the message buffer. * [in] message_length: length of the entire message buffer. @@ -572,9 +617,8 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_UNSUPPORTED_API * OEMCrypto_ERROR_INVALID_NONCE * diff --git a/oemcrypto/odk/include/odk_assert.h b/oemcrypto/odk/include/odk_assert.h deleted file mode 100644 index e1a21fd..0000000 --- a/oemcrypto/odk/include/odk_assert.h +++ /dev/null @@ -1,27 +0,0 @@ - -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_ASSERT_H_ -#define ODK_ASSERT_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#if (__STDC_VERSION__ >= 201112L) -# include -# define odk_static_assert static_assert -#else -# define odk_static_assert(msg, e) \ - enum { odk_static_assert = 1 / (!!((msg) && (e))) }; -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ODK_ASSERT_H_ */ diff --git a/oemcrypto/odk/include/odk_overflow.h b/oemcrypto/odk/include/odk_overflow.h deleted file mode 100644 index 32aebe7..0000000 --- a/oemcrypto/odk/include/odk_overflow.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_OVERFLOW_H_ -#define ODK_OVERFLOW_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - -#if (defined(__GNUC__) && __GNUC__ >= 5) || \ - __has_builtin(__builtin_add_overflow) -# define odk_sub_overflow_u64 __builtin_sub_overflow -# define odk_add_overflow_u64 __builtin_add_overflow -# define odk_add_overflow_ux __builtin_add_overflow -#else -int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); -int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); -int odk_add_overflow_ux(size_t a, size_t b, size_t* c); -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ODK_OVERFLOW_H_ */ diff --git a/oemcrypto/odk/include/odk_serialize.h b/oemcrypto/odk/include/odk_serialize.h deleted file mode 100644 index c9488d7..0000000 --- a/oemcrypto/odk/include/odk_serialize.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/* - * This code is auto-generated, do not edit - */ -#ifndef ODKITEE_SERIALIZER_H_ -#define ODKITEE_SERIALIZER_H_ - -#include "odk_structs_priv.h" -#include "serialization_base.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* odk pack */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj); - -/* odk unpack */ -void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); -void Unpack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse* obj); - -/* kdo pack */ -void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); -void Pack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse const* obj); - -/* kdo unpack */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); -void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); - -#ifdef __cplusplus -} // extern "C" -#endif -#endif /* ODKITEE_SERIALIZER_H_ */ diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index 16b4de6..b24f7d9 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -8,63 +8,103 @@ #include #include "OEMCryptoCENCCommon.h" +#include "odk_target.h" -#define ODK_MAX_NUM_KEYS 32 +/* The version of this library. */ +#define ODK_MAJOR_VERSION 16 +#define ODK_MINOR_VERSION 2 + +/* Some useful constants. */ #define ODK_DEVICE_ID_LEN_MAX 64 #define ODK_SHA256_HASH_SIZE 32 /* - * ODK_TimerLimits is filled out by the function ODK_ParseLicense. + * ODK_TimerLimits Structure * - * The fields in this structure are defined in the core license response - * message. This structure should be kept as part of the session and used - * when calling the ODK timer functions described in the document "License - * Duration and Renewal" distributed as part of the OEMCrypto v16 design. + * Description: + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * Fields: + * soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * earliest_playback_start_seconds: The earliest time that the first playback + * is allowed. Measured in seconds since the license request was signed. For + * most use cases, this is zero. + * rental_duration_seconds: Window of time for the allowed first playback. + * Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this restricts + * any playback. A value of zero means no limit. + * total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start of + * playback for any session. If soft_enforce_playback_duration is false, then + * this restricts any playback. A value of zero means no limit. + * initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, the + * timer will be reset. A value of zero means no limit. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t /*boolean*/ soft_expiry; - uint64_t earliest_playback_start_seconds; /* since license signed. */ - uint64_t latest_playback_start_seconds; /* since license signed. */ - uint64_t initial_playback_duration_seconds; /* since playback start. */ - uint64_t renewal_playback_duration_seconds; /* since renewal signed. */ - uint64_t license_duration_seconds; /* since license signed. */ + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; } ODK_TimerLimits; /* - * ODK_ParsedLicense holds fields from the core license response. - */ -typedef struct { - OEMCrypto_Substring enc_mac_keys_iv; - OEMCrypto_Substring enc_mac_keys; - OEMCrypto_Substring pst; - OEMCrypto_Substring srm_restriction_data; - uint32_t /* OEMCrypto_LicenseType */ license_type; - uint32_t nonce_required; - ODK_TimerLimits timer_limits; - uint8_t request_hash[ODK_SHA256_HASH_SIZE]; - uint32_t key_array_length; /* num_keys */ - OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; -} ODK_ParsedLicense; - -/* - * ODK_ParsedProvisioning holds fields from the core provisioning response. - */ -typedef struct { - uint32_t key_type; - OEMCrypto_Substring enc_private_key; - OEMCrypto_Substring enc_private_key_iv; - OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ -} ODK_ParsedProvisioning; - -/* - * ODK_ClockValues keeps information about a session's current clock values - * and timers. + * ODK_ClockValues Structure * - * Most of the fields in this structure are saved in the usage entry for each - * session. This structure should be initialized when a usage entry is - * created or loaded, and should be used to save a usage entry. It is - * updated using ODK functions listed in the document "License Duration and - * Renewal". The time values are based on OEMCrypto’s system clock. + * Description: + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * Fields: + * time_of_license_signed: Time that the license request was signed, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry as time_of_license_received. + * time_of_first_decrypt: Time of the first decrypt or call select key, based + * on OEMCrypto's system clock. This is 0 if the license has not been used to + * decrypt any data. This value shall be stored and reloaded with usage entry. + * time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * time_of_renewal_request: Time of the most recent renewal request, based on + * OEMCrypto's system clock. This is used to verify that a renewal is not + * stale. + * time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the ODK + * library to determine if it has expired. + * timer_status: Used internally by the ODK library to indicate the current + * timer status. + * status: The license or usage entry status. This value shall be stored and + * reloaded with usage entry. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { uint64_t time_of_license_signed; @@ -77,20 +117,99 @@ typedef struct { } ODK_ClockValues; /* - * ODK_NonceValues are used to match a license or provisioning request to a - * license or provisioning response. For this reason, the api_version might be - * lower than that supported by OEMCrypto. The api_version matches the version - * of the license. Similarly the nonce and session_id match the session that - * generated the license request. For an offline license, these might not match - * the session that is loading the license. We use the nonce to prevent a - * license from being replayed. By also including a session_id in the license - * request and license response, we prevent an attack using the birthday paradox - * to generate nonce collisions on a single device. + * ODK_NonceValues Structure + * + * Description: + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * Fields: + * api_major_version: the API version of the license. This is initialized to + * the API version of the ODK library, but may be lower. + * api_minor_version: the minor version of the ODK library. This is used by + * the server to verify that device is not using an obsolete version of the + * ODK library. + * nonce: a randomly generated number used to prevent replay attacks. + * session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; } ODK_NonceValues; +/* + * ODK_ParsedLicense Structure + * + * Description: + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * Fields: + * enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is 512 + * bits. + * pst: the Provider Session Token. + * srm_restriction_data: optional data specifying the minimum SRM version. + * license_type: specifies if the license contains content keys or + * entitlement keys. + * nonce_required: indicates if the license requires a nonce. + * timer_limits: time limits of the for the license. + * key_array_length: number of keys present. + * key_array: set of keys to be installed. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/* + * ODK_ParsedProvisioning Structure + * + * Description: + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * Fields: + * key_type: indicates if this key is an RSA or ECC private key. + * enc_private_key: encrypted private key for the DRM certificate. + * enc_private_key_iv: IV for decrypting new private key. Size is 128 bits. + * encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + #endif /* WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ */ diff --git a/oemcrypto/odk/include/odk_structs_priv.h b/oemcrypto/odk/include/odk_structs_priv.h deleted file mode 100644 index 8b3ee35..0000000 --- a/oemcrypto/odk/include/odk_structs_priv.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_STRUCTS_PRIV_H_ -#define ODK_STRUCTS_PRIV_H_ - -#include -#include "OEMCryptoCENCCommon.h" -#include "odk_structs.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef struct { - uint32_t message_type; - uint32_t message_length; - ODK_NonceValues nonce_values; -} ODK_CoreMessage; - -typedef struct { - ODK_CoreMessage core_message; -} ODK_PreparedLicense; - -typedef struct { - ODK_CoreMessage core_message; - uint64_t playback_time; -} ODK_RenewalMessage; - -typedef struct { - ODK_CoreMessage core_message; - uint32_t device_id_length; - uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; -} ODK_ProvisioningMessage; - -typedef struct { - ODK_CoreMessage core_message; - ODK_ParsedLicense* parsed_license; -} ODK_LicenseResponse; - -typedef struct { - ODK_ProvisioningMessage core_provisioning; - ODK_ParsedProvisioning* parsed_provisioning; -} ODK_ProvisioningResponse; - -#endif // ODK_STRUCTS_PRIV_H_ diff --git a/oemcrypto/odk/include/odk_target.h b/oemcrypto/odk/include/odk_target.h new file mode 100644 index 0000000..9225210 --- /dev/null +++ b/oemcrypto/odk/include/odk_target.h @@ -0,0 +1,13 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file is distributed */ +/* under the Widevine Master License Agreement. */ + +/* Partners are expected to edit this file to support target specific code */ +/* and limits. */ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ + +/* Maximum number of keys can be modified to suit target's resource tier. */ +#define ODK_MAX_NUM_KEYS 32 + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ */ diff --git a/oemcrypto/odk/include/serialization_base.h b/oemcrypto/odk/include/serialization_base.h deleted file mode 100644 index cc1a3d1..0000000 --- a/oemcrypto/odk/include/serialization_base.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODKITEE_SERIALIZATION_BASE_H_ -#define ODKITEE_SERIALIZATION_BASE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#include "OEMCryptoCENCCommon.h" - -#define SIZE_OF_MESSAGE_STRUCT 64 - -/* - * Description: - * Point |msg| to stack-array |blk|. - * |blk| is guaranteed large enough to hold a |Message| struct. - * |blk| cannot be used in the same scope as a variable name. - * |msg| points to valid memory in the same scope |AllocateMessage| is used. - * Parameters: - * msg: pointer to pointer to |Message| struct - * blk: variable name for stack-array - */ -#define AllocateMessage(msg, blk) \ - uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \ - *(msg) = (Message*)(blk); - -typedef struct _Message Message; - -bool ValidMessage(Message* message); - -void Pack_uint32_t(Message* message, const uint32_t* value); -void Pack_uint64_t(Message* message, const uint64_t* value); -void PackArray(Message* message, const uint8_t* base, size_t size); -void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); - -void Unpack_uint32_t(Message* message, uint32_t* value); -void Unpack_uint64_t(Message* message, uint64_t* value); -void UnpackArray(Message* message, uint8_t* base, size_t size); /* copy out */ -void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); - -typedef enum { - MESSAGE_STATUS_OK, - MESSAGE_STATUS_UNKNOWN_ERROR, - MESSAGE_STATUS_OVERFLOW_ERROR, - MESSAGE_STATUS_UNDERFLOW_ERROR, - MESSAGE_STATUS_PARSE_ERROR, - MESSAGE_STATUS_NULL_POINTER_ERROR, - MESSAGE_STATUS_API_VALUE_ERROR -} MessageStatus; - -/* - * Create a message from a buffer. The message structure consumes the first - * sizeof(Message) bytes of the buffer. The caller is responsible for ensuring - * that the buffer remains allocated for the lifetime of the message. - */ -Message* CreateMessage(uint8_t* buffer, size_t buffer_size); - -/* - * Initialize a message structure to reference a separate buffer. The caller - * is responsible for ensuring that the buffer remains allocated for the - * lifetime of the message. - */ -void InitMessage(Message* message, uint8_t* buffer, size_t capacity); - -/* - * Reset an existing the message to an empty state - */ -void ResetMessage(Message* message); -uint8_t* GetBase(Message* message); -size_t GetCapacity(Message* message); -size_t GetSize(Message* message); -void SetSize(Message* message, size_t size); -MessageStatus GetStatus(Message* message); -void SetStatus(Message* message, MessageStatus status); -size_t GetOffset(Message* message); - -size_t SizeOfMessageStruct(); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ODKITEE_SERIALIZATION_BASE_H_ diff --git a/oemcrypto/odk/kdo/include/oec_util.h b/oemcrypto/odk/kdo/include/oec_util.h deleted file mode 100644 index 20bee97..0000000 --- a/oemcrypto/odk/kdo/include/oec_util.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -// clang-format off -/********************************************************************* - * oec_util.h - * - * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) - * - * For Widevine Modular DRM, there are six message types between a server and - * a client device: license request and response, provisioning request and - * response, and renewal request and response. - * - * In OEMCrypto v15 and earlier, messages from the server were parsed by the - * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of - * pointers to protected data within the message. However, the pointers - * themselves were not signed by the server. - * - * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these - * messages have been identified in the document "Widevine Core Message - * Serialization". These fields are called the core of the message. Core - * message fields are (de)serialized using the ODK, a C library provided by - * Widevine. OEMCrypto will parse and verify the core of the message with - * help from the ODK. - * - * The KDO library is the counterpart of ODK used in the CDM & Widevine - * servers. For each message type generated by the ODK, KDO provides a - * corresponding parser. For each message type to be parsed by the ODK, - * KDO provides a corresponding writer. - * - * Table: ODK vs KDO (s: serialize; d: deserialize) - * +----------------------------------------+------------------------------------+ - * | ODK | KDO | - * +---+------------------------------------+---+--------------------------------+ - * | s | ODK_PrepareCoreLicenseRequest | d | ParseLicenseRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreRenewalRequest | | ParseRenewalRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreProvisioningRequest | | ParseProvisioningRequest | - * +---+------------------------------------+---+--------------------------------+ - * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | - * +---+------------------------------------+---+--------------------------------+ - * - *********************************************************************/ -// clang-format on - -#ifndef OEC_UTIL_H_ -#define OEC_UTIL_H_ - -#include -#include - -#include "odk_structs.h" - -using namespace std; - -namespace oec_util { - -// @ input/output structs - -/** - * Output structure for ParseLicenseRequest - * Input structure for CreateCoreLicenseResponse - */ -struct ODK_LicenseRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; -}; - -/** - * Output structure for ParseRenewalRequest - * Input structure for CreateCoreRenewalResponse - */ -struct ODK_RenewalRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - uint64_t playback_time; -}; - -/** - * Output structure for ParseProvisioningRequest - * Input structure for CreateCoreProvisioningResponse - */ -struct ODK_ProvisioningRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - string device_id; -}; - -// @ public parse request (deserializer) functions - -/** - * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_license_request - */ -bool ParseLicenseRequest(const string& oemcrypto_core_message, - ODK_LicenseRequest* core_license_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_renewal_request - */ -bool ParseRenewalRequest(const string& oemcrypto_core_message, - ODK_RenewalRequest* core_renewal_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_provisioning_request - */ -bool ParseProvisioningRequest( - const string& oemcrypto_core_message, - ODK_ProvisioningRequest* core_provisioning_request); - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_lic - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseRenewal (deserializer) - * - * Parameters: - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_prov - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); -} // namespace oec_util - -#endif // OEC_UTIL_H_ diff --git a/oemcrypto/odk/kdo/include/oec_util_proto.h b/oemcrypto/odk/kdo/include/oec_util_proto.h deleted file mode 100644 index f9d8017..0000000 --- a/oemcrypto/odk/kdo/include/oec_util_proto.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/********************************************************************* - * oec_util_proto.h - * - * These functions are an extension of those found in oec_util.h. The - * difference is that these use the license and provisioning messages - * in protobuf format to create the core message. - *********************************************************************/ - -#ifndef OEC_UTIL_PROTO_H_ -#define OEC_UTIL_PROTO_H_ - -#include -#include - -#include "license_protocol.pb.h" -#include "oec_util.h" - -using namespace std; -using video_widevine::License; -using video_widevine::License_KeyContainer; - -namespace oec_util { - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * - * Parameters: - * [in] license - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const video_widevine::License& license, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * - * Parameters: - * [in] provisioning_response - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse( - const video_widevine::ProvisioningResponse& provisioning_response, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); - -} // namespace oec_util - -#endif // OEC_UTIL_PROTO_H_ diff --git a/oemcrypto/odk/src/core_message_deserialize.cpp b/oemcrypto/odk/src/core_message_deserialize.cpp index b3d0059..f2e909b 100644 --- a/oemcrypto/odk/src/core_message_deserialize.cpp +++ b/oemcrypto/odk/src/core_message_deserialize.cpp @@ -19,8 +19,8 @@ namespace oemcrypto_core_message { namespace deserialize { namespace { -const int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16; -const int LATEST_OEMCRYPTO_VERSION = 16; +constexpr int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16; +constexpr int LATEST_OEMCRYPTO_VERSION = 16; /** * Template for parsing requests @@ -53,29 +53,43 @@ bool ParseRequest(uint32_t message_type, } const auto& core_message = prepared->core_message; - core_request->api_version = core_message.nonce_values.api_version; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; core_request->nonce = core_message.nonce_values.nonce; core_request->session_id = core_message.nonce_values.session_id; + // Verify that the minor version matches the released version for the given + // major version. + if ((core_request->api_major_version < EARLIEST_OEMCRYPTO_VERSION_WITH_ODK) || + (core_request->api_major_version > LATEST_OEMCRYPTO_VERSION)) { + // Non existing and future versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + if (core_request->api_major_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + } return core_message.message_type == message_type && core_message.message_length == GetOffset(msg) && - core_request->api_version >= EARLIEST_OEMCRYPTO_VERSION_WITH_ODK && - core_request->api_version <= LATEST_OEMCRYPTO_VERSION; + core_request->api_major_version >= + EARLIEST_OEMCRYPTO_VERSION_WITH_ODK && + core_request->api_major_version <= LATEST_OEMCRYPTO_VERSION; } } // namespace bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, ODK_LicenseRequest* core_license_request) { - const auto unpacker = Unpack_ODK_PreparedLicense; - ODK_PreparedLicense prepared_license = {}; + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, core_license_request, &prepared_license, unpacker); } bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, ODK_RenewalRequest* core_renewal_request) { - const auto unpacker = Unpack_ODK_RenewalMessage; - ODK_RenewalMessage prepared_renewal = {}; + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, core_renewal_request, &prepared_renewal, unpacker)) { return false; @@ -87,8 +101,8 @@ bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, bool CoreProvisioningRequestFromMessage( const std::string& oemcrypto_core_message, ODK_ProvisioningRequest* core_provisioning_request) { - const auto unpacker = Unpack_ODK_ProvisioningMessage; - ODK_ProvisioningMessage prepared_provision = {}; + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, core_provisioning_request, &prepared_provision, unpacker)) { return false; diff --git a/oemcrypto/odk/src/core_message_serialize.cpp b/oemcrypto/odk/src/core_message_serialize.cpp index 1ff5a59..1ce827e 100644 --- a/oemcrypto/odk/src/core_message_serialize.cpp +++ b/oemcrypto/odk/src/core_message_serialize.cpp @@ -4,7 +4,6 @@ #include "core_message_serialize.h" -#include #include #include #include @@ -38,11 +37,12 @@ bool CreateResponse(uint32_t message_type, const S& core_request, auto* header = reinterpret_cast(&response); header->message_type = message_type; - header->nonce_values.api_version = core_request.api_version; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; header->nonce_values.nonce = core_request.nonce; header->nonce_values.session_id = core_request.session_id; - const size_t BUF_CAPACITY = 2048; + static constexpr size_t BUF_CAPACITY = 2048; std::vector buf(BUF_CAPACITY, 0); Message* msg = nullptr; AllocateMessage(&msg, message_block); @@ -63,16 +63,14 @@ bool CreateResponse(uint32_t message_type, const S& core_request, bool CopyDeviceId(const ODK_ProvisioningRequest& src, ODK_ProvisioningResponse* dest) { - auto& core_provisioning = dest->core_provisioning; + auto& request = dest->request; const std::string& device_id = src.device_id; - core_provisioning.device_id_length = device_id.size(); - if (core_provisioning.device_id_length > - sizeof(core_provisioning.device_id)) { + if (request.device_id_length > sizeof(request.device_id)) { return false; } - memset(core_provisioning.device_id, 0, sizeof(core_provisioning.device_id)); - memcpy(core_provisioning.device_id, device_id.data(), - core_provisioning.device_id_length); + request.device_id_length = device_id.size(); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); return true; } @@ -80,21 +78,28 @@ bool CopyDeviceId(const ODK_ProvisioningRequest& src, bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, std::string* oemcrypto_core_message) { ODK_LicenseResponse license_response{ - {}, const_cast(&parsed_lic)}; + {}, const_cast(&parsed_lic), {0}}; + if (core_request_sha256.size() != sizeof(license_response.request_hash)) + return false; + memcpy(license_response.request_hash, core_request_sha256.data(), + sizeof(license_response.request_hash)); return CreateResponse(ODK_License_Response_Type, core_request, oemcrypto_core_message, license_response, Pack_ODK_LicenseResponse); } bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, std::string* oemcrypto_core_message) { - ODK_RenewalMessage renewal{{}, core_request.playback_time_seconds}; - renewal.playback_time = core_request.playback_time_seconds; + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; return CreateResponse(ODK_Renewal_Response_Type, core_request, - oemcrypto_core_message, renewal, - Pack_ODK_RenewalMessage); + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); } bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp index 8520cd5..25f1887 100644 --- a/oemcrypto/odk/src/core_message_serialize_proto.cpp +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -67,6 +67,7 @@ OEMCrypto_KeyObject KeyContainerToOecKey( bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, const ODK_LicenseRequest& core_request, const std::string& core_request_sha256, + const bool nonce_required, std::string* oemcrypto_core_message) { video_widevine::License lic; if (!lic.ParseFromString(serialized_license)) { @@ -74,25 +75,33 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, } ODK_ParsedLicense parsed_lic{}; - if (core_request_sha256.size() != ODK_SHA256_HASH_SIZE) { - return false; - } - std::memcpy(parsed_lic.request_hash, core_request_sha256.data(), - ODK_SHA256_HASH_SIZE); + bool any_content = false; + bool any_entitlement = false; for (int i = 0; i < lic.key_size(); ++i) { const auto& k = lic.key(i); switch (k.type()) { case video_widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } parsed_lic.enc_mac_keys_iv = GetOecSubstring(serialized_license, k.iv()); - // Strip off PKCS#5 padding - const size_t MAC_KEY_SIZE = 32; - std::string mac_keys(k.key(), 2 * MAC_KEY_SIZE); + std::string mac_keys(k.key(), k.key().size()); parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, mac_keys); break; } case video_widevine::License_KeyContainer::CONTENT: { + any_content = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + case video_widevine::License_KeyContainer::ENTITLEMENT: { + any_entitlement = true; if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { return false; } @@ -105,7 +114,16 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, } } } - + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; const auto& lid = lic.id(); if (lid.has_provider_session_token()) { parsed_lic.pst = @@ -117,22 +135,25 @@ bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, GetOecSubstring(serialized_license, lic.srm_requirement()); } - parsed_lic.license_type = lid.type(); - // todo(robertshih): nonce_required + parsed_lic.nonce_required = nonce_required; const auto& policy = lic.policy(); ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; - timer_limits.soft_expiry = policy.soft_enforce_playback_duration(); + // TODO(b/148241181): add field to protobuf. + // timer_limits.soft_enforce_rental_duration = + // policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_rental_duration = true; + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); timer_limits.earliest_playback_start_seconds = 0; - timer_limits.latest_playback_start_seconds = - policy.license_duration_seconds(); - timer_limits.initial_playback_duration_seconds = + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = policy.playback_duration_seconds(); - timer_limits.renewal_playback_duration_seconds = - policy.playback_duration_seconds(); - timer_limits.license_duration_seconds = policy.license_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); return CreateCoreLicenseResponse(parsed_lic, core_request, - oemcrypto_core_message); + core_request_sha256, oemcrypto_core_message); } bool CreateCoreProvisioningResponseFromProto( @@ -145,7 +166,8 @@ bool CreateCoreProvisioningResponseFromProto( return false; } - parsed_prov.key_type = 0; // todo(robertshih): ECC or RSA + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA if (prov.has_device_rsa_key()) { parsed_prov.enc_private_key = GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c index 696f546..1ce0d3b 100644 --- a/oemcrypto/odk/src/odk.c +++ b/oemcrypto/odk/src/odk.c @@ -12,12 +12,12 @@ #include "odk_serialize.h" #include "odk_structs.h" #include "odk_structs_priv.h" +#include "odk_util.h" #include "serialization_base.h" #define ODK_LICENSE_REQUEST_SIZE 20 #define ODK_RENEWAL_REQUEST_SIZE 28 #define ODK_PROVISIONING_REQUEST_SIZE 88 -#define OEC_API_VERSION 16 /* @ private odk functions */ @@ -26,8 +26,8 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, uint32_t message_type, const ODK_NonceValues* nonce_values, ODK_CoreMessage* core_message) { - if (!nonce_values || !core_message_length || !core_message || - *core_message_length > buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + core_message == NULL || *core_message_length > buffer_length) { return ODK_ERROR_CORE_MESSAGE; } @@ -43,17 +43,20 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, switch (message_type) { case ODK_License_Request_Type: { core_message->message_length = ODK_LICENSE_REQUEST_SIZE; - Pack_ODK_PreparedLicense(msg, (ODK_PreparedLicense*)core_message); + Pack_ODK_PreparedLicenseRequest( + msg, (ODK_PreparedLicenseRequest*)core_message); break; } case ODK_Renewal_Request_Type: { core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; - Pack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Pack_ODK_PreparedRenewalRequest( + msg, (ODK_PreparedRenewalRequest*)core_message); break; } case ODK_Provisioning_Request_Type: { core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; - Pack_ODK_ProvisioningMessage(msg, (ODK_ProvisioningMessage*)core_message); + Pack_ODK_PreparedProvisioningRequest( + msg, (ODK_PreparedProvisioningRequest*)core_message); break; } default: { @@ -93,7 +96,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, break; } case ODK_Renewal_Response_Type: { - Unpack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)core_message); break; } case ODK_Provisioning_Response_Type: { @@ -114,7 +117,10 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, if (nonce_values) { /* always verify nonce_values for Renewal and Provisioning responses */ - if (nonce_values->api_version != core_message->nonce_values.api_version || + if (nonce_values->api_major_version != + core_message->nonce_values.api_major_version || + nonce_values->api_minor_version != + core_message->nonce_values.api_minor_version || nonce_values->nonce != core_message->nonce_values.nonce || nonce_values->session_id != core_message->nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; @@ -131,7 +137,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, OEMCryptoResult ODK_PrepareCoreLicenseRequest( uint8_t* message, size_t message_length, size_t* core_message_length, const ODK_NonceValues* nonce_values) { - ODK_PreparedLicense license_request = { + ODK_PreparedLicenseRequest license_request = { {0}, }; return ODK_PrepareRequest(message, message_length, core_message_length, @@ -139,13 +145,17 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( &license_request.core_message); } -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_size, - const ODK_NonceValues* nonce_values, ODK_ClockValues* clock_values, - uint64_t system_time_seconds) { - ODK_RenewalMessage renewal_request = { - {0}, - }; +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (nonce_values == NULL || clock_values == NULL) + return ODK_ERROR_CORE_MESSAGE; + ODK_PreparedRenewalRequest renewal_request = {{0}, 0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ if (clock_values->time_of_first_decrypt == 0) { /* It is OK to preemptively request a renewal before playback starts. * We'll treat this as asking for a renewal at playback time 0. */ @@ -158,7 +168,9 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( return ODK_ERROR_CORE_MESSAGE; } } - /* Save time for this request so that we can verify the response. */ + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ clock_values->time_of_renewal_request = renewal_request.playback_time; return ODK_PrepareRequest(message, message_length, core_message_size, ODK_Renewal_Request_Type, nonce_values, @@ -169,7 +181,9 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( uint8_t* message, size_t message_length, size_t* core_message_length, const ODK_NonceValues* nonce_values, const uint8_t* device_id, size_t device_id_length) { - ODK_ProvisioningMessage provisioning_request = { + ODK_PreparedProvisioningRequest provisioning_request = { + {0}, + 0, {0}, }; if (device_id_length > sizeof(provisioning_request.device_id)) { @@ -192,55 +206,61 @@ OEMCryptoResult ODK_ParseLicense( const uint8_t* request_hash, ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license) { - if (!message || !request_hash || !timer_limits || !clock_values || - !nonce_values || !parsed_license) { + if (message == NULL || request_hash == NULL || timer_limits == NULL || + clock_values == NULL || nonce_values == NULL || parsed_license == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_LicenseResponse license_response = {{0}, parsed_license}; - OEMCryptoResult err = ODK_ParseResponse( + ODK_LicenseResponse license_response = {{{0}}, parsed_license, {0}}; + const OEMCryptoResult err = ODK_ParseResponse( message, message_length, core_message_length, ODK_License_Response_Type, - NULL, &license_response.core_message); + NULL, &license_response.request.core_message); if (err != OEMCrypto_SUCCESS) { return err; } /* This function should not be used for legacy licenses. */ - if (license_response.core_message.nonce_values.api_version != - OEC_API_VERSION) { + if (license_response.request.core_message.nonce_values.api_major_version != + ODK_MAJOR_VERSION) { return ODK_UNSUPPORTED_API; } + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + if (parsed_license->nonce_required) { if (initial_license_load) { if (nonce_values->nonce != - license_response.core_message.nonce_values.nonce || + license_response.request.core_message.nonce_values.nonce || nonce_values->session_id != - license_response.core_message.nonce_values.session_id) { + license_response.request.core_message.nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; } } else { /* !initial_license_load */ - nonce_values->nonce = license_response.core_message.nonce_values.nonce; + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; nonce_values->session_id = - license_response.core_message.nonce_values.session_id; + license_response.request.core_message.nonce_values.session_id; } } /* For v16, in order to be backwards compatible with a v15 license server, * OEMCrypto stores a hash of the core license request and only signs the * message body. Here, when we process the license response, we verify that * the server has the same hash of the core request. */ - if (initial_license_load && memcmp(request_hash, parsed_license->request_hash, - ODK_SHA256_HASH_SIZE)) { - return ODK_ERROR_CORE_MESSAGE; - } - /* If the license has a provider session token (pst), then OEMCrypto should - * have a usage entry loaded. */ - if (usage_entry_present && parsed_license->pst.length == 0) { + if (initial_license_load && + crypto_memcmp(request_hash, license_response.request_hash, + ODK_SHA256_HASH_SIZE)) { return ODK_ERROR_CORE_MESSAGE; } *timer_limits = parsed_license->timer_limits; - return err; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; } OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, @@ -250,16 +270,18 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, uint64_t* timer_value) { - if (!message || !nonce_values || !timer_limits || !clock_values) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_RenewalMessage renewal_response = { - {0}, + ODK_RenewalResponse renewal_response = { + {{0}, 0}, + 0, }; OEMCryptoResult err = ODK_ParseResponse( message, message_length, core_message_length, ODK_Renewal_Response_Type, - nonce_values, &renewal_response.core_message); + nonce_values, &renewal_response.request.core_message); if (err) { return err; @@ -269,69 +291,51 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * Doc: License Duration and Renewal (Changes for OEMCrypto v16) * Section: Renewal Message */ - /* If a renewal request is lost in transit, we should throw it out and create * a new one. We use the timestamp to make sure we have the latest request. */ - if (clock_values->time_of_renewal_request < renewal_response.playback_time) { + if (clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { return ODK_STALE_RENEWAL; } - - /* The timer value should be set to the renewal duration. */ - if (timer_value) { - *timer_value = timer_limits->renewal_playback_duration_seconds; - } - - if (timer_limits->renewal_playback_duration_seconds == 0) { - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - if (odk_add_overflow_u64(system_time, - timer_limits->renewal_playback_duration_seconds, - &clock_values->time_when_timer_expires)) { - return ODK_ERROR_CORE_MESSAGE; - } - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); } OEMCryptoResult ODK_ParseProvisioning( const uint8_t* message, size_t message_length, size_t core_message_length, const ODK_NonceValues* nonce_values, const uint8_t* device_id, size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { - if (!message || !nonce_values || !device_id || !parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_ProvisioningResponse provisioning_response = {{ - {0}, - }, + ODK_ProvisioningResponse provisioning_response = {{{0}, 0, {0}}, parsed_response}; if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { return ODK_ERROR_CORE_MESSAGE; } - const OEMCryptoResult err = + OEMCryptoResult err = ODK_ParseResponse(message, message_length, core_message_length, ODK_Provisioning_Response_Type, nonce_values, - &provisioning_response.core_provisioning.core_message); + &provisioning_response.request.core_message); if (err) { return err; } - if (memcmp(device_id, provisioning_response.core_provisioning.device_id, + if (memcmp(device_id, provisioning_response.request.device_id, device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; /* check bytes beyond device_id_length are 0 */ - if (memcmp( - zero, - provisioning_response.core_provisioning.device_id + device_id_length, - ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + if (memcmp(zero, provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } diff --git a/oemcrypto/odk/src/odk.gypi b/oemcrypto/odk/src/odk.gypi index 6488b68..ec29751 100644 --- a/oemcrypto/odk/src/odk.gypi +++ b/oemcrypto/odk/src/odk.gypi @@ -3,13 +3,14 @@ # Agreement. # These files are built into the ODK library on the device. They are also used -# by the server and by test cocde. These files should compile on C98 compilers. +# by the server and by test cocde. These files should compile on C99 compilers. { 'sources': [ 'odk.c', 'odk_overflow.c', 'odk_serialize.c', 'odk_timer.c', + 'odk_util.c', 'serialization_base.c', ], } diff --git a/oemcrypto/odk/src/odk_overflow.h b/oemcrypto/odk/src/odk_overflow.h index 23cc440..3fcb32c 100644 --- a/oemcrypto/odk/src/odk_overflow.h +++ b/oemcrypto/odk/src/odk_overflow.h @@ -5,6 +5,9 @@ #ifndef WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ #define WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ +#include +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c index 5f77aee..e050050 100644 --- a/oemcrypto/odk/src/odk_serialize.c +++ b/oemcrypto/odk/src/odk_serialize.c @@ -14,7 +14,8 @@ /* @@ private serialize */ static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { - Pack_uint32_t(msg, &obj->api_version); + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); Pack_uint32_t(msg, &obj->nonce); Pack_uint32_t(msg, &obj->session_id); } @@ -35,12 +36,12 @@ static void Pack_OEMCrypto_KeyObject(Message* msg, } static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { - Pack_uint32_t(msg, &obj->soft_expiry); + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); - Pack_uint64_t(msg, &obj->latest_playback_start_seconds); - Pack_uint64_t(msg, &obj->initial_playback_duration_seconds); - Pack_uint64_t(msg, &obj->renewal_playback_duration_seconds); - Pack_uint64_t(msg, &obj->license_duration_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { @@ -53,10 +54,9 @@ static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); Pack_OEMCrypto_Substring(msg, &obj->pst); Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); - Pack_uint32_t(msg, &obj->license_type); - Pack_uint32_t(msg, &obj->nonce_required); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); Pack_ODK_TimerLimits(msg, &obj->timer_limits); - PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); Pack_uint32_t(msg, &obj->key_array_length); size_t i; for (i = 0; i < (size_t)obj->key_array_length; i++) { @@ -66,7 +66,7 @@ static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { static void Pack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning const* obj) { - Pack_uint32_t(msg, &obj->key_type); + Pack_enum(msg, obj->key_type); Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); @@ -74,17 +74,19 @@ static void Pack_ODK_ParsedProvisioning(Message* msg, /* @@ odk serialize */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj) { +void Pack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); } -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj) { +void Pack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); Pack_uint64_t(msg, &obj->playback_time); } -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj) { +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); Pack_uint32_t(msg, &obj->device_id_length); PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); @@ -93,13 +95,19 @@ void Pack_ODK_ProvisioningMessage(Message* msg, /* @@ kdo serialize */ void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { - Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); } void Pack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse const* obj) { - Pack_ODK_ProvisioningMessage(msg, &obj->core_provisioning); + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); Pack_ODK_ParsedProvisioning( msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); } @@ -109,7 +117,8 @@ void Pack_ODK_ProvisioningResponse(Message* msg, /* @@ private deserialize */ static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { - Unpack_uint32_t(msg, &obj->api_version); + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); Unpack_uint32_t(msg, &obj->nonce); Unpack_uint32_t(msg, &obj->session_id); } @@ -129,12 +138,12 @@ static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { } static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { - Unpack_uint32_t(msg, &obj->soft_expiry); + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); - Unpack_uint64_t(msg, &obj->latest_playback_start_seconds); - Unpack_uint64_t(msg, &obj->initial_playback_duration_seconds); - Unpack_uint64_t(msg, &obj->renewal_playback_duration_seconds); - Unpack_uint64_t(msg, &obj->license_duration_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { @@ -142,10 +151,9 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); Unpack_OEMCrypto_Substring(msg, &obj->pst); Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); - Unpack_uint32_t(msg, &obj->license_type); - Unpack_uint32_t(msg, &obj->nonce_required); + obj->license_type = Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); Unpack_ODK_TimerLimits(msg, &obj->timer_limits); - UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); Unpack_uint32_t(msg, &obj->key_array_length); if (obj->key_array_length > ODK_MAX_NUM_KEYS) { SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); @@ -159,7 +167,7 @@ static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { static void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* obj) { - Unpack_uint32_t(msg, &obj->key_type); + obj->key_type = Unpack_enum(msg); Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); @@ -167,12 +175,19 @@ static void Unpack_ODK_ParsedProvisioning(Message* msg, /* @ kdo deserialize */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj) { +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj) { Unpack_ODK_CoreMessage(msg, &obj->core_message); } -void Unpack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage* obj) { +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj) { Unpack_ODK_CoreMessage(msg, &obj->core_message); Unpack_uint32_t(msg, &obj->device_id_length); UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); @@ -181,17 +196,18 @@ void Unpack_ODK_ProvisioningMessage(Message* msg, /* @@ odk deserialize */ void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { - Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); Unpack_ODK_ParsedLicense(msg, obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); } -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj) { - Unpack_ODK_CoreMessage(msg, &obj->core_message); - Unpack_uint64_t(msg, &obj->playback_time); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); } void Unpack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse* obj) { - Unpack_ODK_ProvisioningMessage(msg, &obj->core_provisioning); + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); } diff --git a/oemcrypto/odk/src/odk_serialize.h b/oemcrypto/odk/src/odk_serialize.h index 1b57018..f35f178 100644 --- a/oemcrypto/odk/src/odk_serialize.h +++ b/oemcrypto/odk/src/odk_serialize.h @@ -16,25 +16,32 @@ extern "C" { #endif /* odk pack */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj); +void Pack_ODK_PreparedLicenseRequest(Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, const ODK_PreparedProvisioningRequest* obj); /* odk unpack */ void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj); void Unpack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse* obj); /* kdo pack */ -void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); +void Pack_ODK_LicenseResponse(Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_RenewalResponse(Message* msg, const ODK_RenewalResponse* obj); void Pack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse const* obj); + const ODK_ProvisioningResponse* obj); /* kdo unpack */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); -void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj); #ifdef __cplusplus } /* extern "C" */ diff --git a/oemcrypto/odk/src/odk_structs_priv.h b/oemcrypto/odk/src/odk_structs_priv.h index d21fbe2..00e31bf 100644 --- a/oemcrypto/odk/src/odk_structs_priv.h +++ b/oemcrypto/odk/src/odk_structs_priv.h @@ -10,6 +10,10 @@ #include "OEMCryptoCENCCommon.h" #include "odk_structs.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { ODK_License_Request_Type = 1, ODK_License_Response_Type = 2, @@ -27,27 +31,61 @@ typedef struct { typedef struct { ODK_CoreMessage core_message; -} ODK_PreparedLicense; +} ODK_PreparedLicenseRequest; typedef struct { ODK_CoreMessage core_message; uint64_t playback_time; -} ODK_RenewalMessage; +} ODK_PreparedRenewalRequest; typedef struct { ODK_CoreMessage core_message; uint32_t device_id_length; uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; -} ODK_ProvisioningMessage; +} ODK_PreparedProvisioningRequest; typedef struct { - ODK_CoreMessage core_message; + ODK_PreparedLicenseRequest request; ODK_ParsedLicense* parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; } ODK_LicenseResponse; typedef struct { - ODK_ProvisioningMessage core_provisioning; + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; ODK_ParsedProvisioning* parsed_provisioning; } ODK_ProvisioningResponse; +/* These are the possible timer status values. */ +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0 /* Should not happen. */ +/* When the structure has been initialized, but no license is loaded. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1 +/* After the license is loaded, before a successful decrypt. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2 +/* After the license is loaded, if a renewal has also been loaded. */ +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3 +/* The first decrypt has occurred and the timer is active. */ +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4 +/* The first decrypt has occurred and the timer is unlimited. */ +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5 +/* The timer has transitioned from active to expired. */ +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6 +/* The license has been marked as inactive. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7 + +/* A helper function for computing timer limits when a renewal is loaded. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + #endif /* WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ */ diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index a28547d..8bef3d9 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -6,41 +6,279 @@ #include #include "odk.h" +#include "odk_overflow.h" +#include "odk_structs_priv.h" +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. This also updates the + * timer_status if appropriate. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) return; /* should not happen. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_signed) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) return license_status; + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && (rental_status != ODK_SET_TIMER)) + return rental_status; + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) + return playback_status; + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = 0; - timer_limits->renewal_playback_duration_seconds = 0; - timer_limits->license_duration_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; - clock_values->time_of_license_signed = 0; - clock_values->time_of_first_decrypt = 0; - clock_values->time_of_last_decrypt = 0; - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; - clock_values->status = kUnused; + ODK_InitializeClockValues(clock_values, 0); - nonce_values->api_version = api_version; + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; nonce_values->nonce = 0; nonce_values->session_id = session_id; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, uint32_t nonce) { + if (nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } nonce_values->nonce = nonce; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto signs a license. */ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, uint64_t system_time_seconds) { if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -48,12 +286,12 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, clock_values->time_of_first_decrypt = 0; clock_values->time_of_last_decrypt = 0; clock_values->time_when_timer_expires = 0; - /* TODO(b/142415188): document this. */ - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = kUnused; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto reloads a usage entry. */ OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, uint64_t time_of_license_signed, uint64_t time_of_first_decrypt, @@ -65,114 +303,84 @@ OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, clock_values->time_of_first_decrypt = time_of_first_decrypt; clock_values->time_of_last_decrypt = time_of_last_decrypt; clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = status; return OEMCrypto_SUCCESS; } /* This is called on the first playback for a session. */ -uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, - const ODK_TimerLimits* timer_limits, - ODK_ClockValues* clock_values, - uint64_t* timer_value) { +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { if (clock_values == NULL || timer_limits == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; /* All times are relative to when the license was signed. */ - const uint64_t rental_time = - system_time_seconds - clock_values->time_of_license_signed; + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (rental_time < timer_limits->earliest_playback_start_seconds) { clock_values->timer_status = ODK_TIMER_EXPIRED; return ODK_TIMER_EXPIRED; } - /* If the clock status is already marked as inactive, then playback is - * not allowed. */ - /* TODO(b/142415188): add helper function. */ - if (clock_values->status > kActive) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - /* If this license is still inactive (never used) then we just look at the - * rental window. This is the first playback for the license, not just this - * session. */ - if (clock_values->status == kUnused) { - /* If the rental clock has expired, the license has expired. */ - if (rental_time > timer_limits->latest_playback_start_seconds && - timer_limits->latest_playback_start_seconds > 0) { - clock_values->timer_status = ODK_TIMER_EXPIRED; + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { return ODK_TIMER_EXPIRED; } - /* The timer should be limited by the playback duration. */ - uint64_t time_left = timer_limits->initial_playback_duration_seconds; - /* If there is a license duration, it also limits the timer. Remeber, a - * limit of 0 means no limit, or infinite. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* If the license duration has expired. This is unusual, because this - * can only happen if the license duration is less than the rental - * window. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; } - /* This is a new license, and we can start playback. */ - clock_values->status = kActive; + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { clock_values->time_of_first_decrypt = system_time_seconds; - clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - /* Set timer to limit playback. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + clock_values->status = kActive; } - /* Otherwise, this is the second loading of a persistent license. In this - * case, we ignore the rental window. */ - const uint64_t time_since_first_decrypt = - system_time_seconds - clock_values->time_of_first_decrypt; - uint64_t time_left = 0; - /* If there is an initial playback duration, the we use that as a limit. - * This ignores any license renewals. If renewals are allowed, then the last - * one can be reloaded to reset the timer. */ - if (timer_limits->initial_playback_duration_seconds > 0) { - if (timer_limits->initial_playback_duration_seconds <= - time_since_first_decrypt) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - time_left = timer_limits->initial_playback_duration_seconds - - time_since_first_decrypt; - } - /* If there is a license duration, it also limits the timer. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* The license duration has expired. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } - } - /* We can restart playback for this license. Update last playback time. */ + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; return ODK_DISABLE_TIMER; } - /* Set timer. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; return ODK_SET_TIMER; } @@ -181,27 +389,29 @@ uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values) { - if (clock_values == NULL || timer_limits == NULL) - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - if (clock_values->timer_status == ODK_TIMER_EXPIRED) { - return ODK_TIMER_EXPIRED; - } - /* If the clock status is already marked as inactive, then playback is - * not allowed. */ - /* TODO(b/142415188): add helper function. */ - if (clock_values->status > kActive) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (clock_values->time_when_timer_expires > 0 && - system_time_seconds > clock_values->time_when_timer_expires) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; } clock_values->time_of_last_decrypt = system_time_seconds; return OEMCrypto_SUCCESS; } +/* This is called from OEMCrypto_DeactivateUsageEntry. */ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { if (clock_values == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (clock_values->status == kUnused) { @@ -209,9 +419,12 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { } else if (clock_values->status == kActive) { clock_values->status = kInactiveUsed; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, @@ -219,67 +432,41 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, uint64_t system_time_seconds) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = key_duration; - timer_limits->renewal_playback_duration_seconds = key_duration; - timer_limits->license_duration_seconds = 0; - nonce_values->api_version = 15; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; if (key_duration > 0) { clock_values->time_when_timer_expires = system_time_seconds + key_duration; } else { clock_values->time_when_timer_expires = 0; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, const ODK_NonceValues* nonce_values, uint64_t system_time_seconds, + uint32_t new_key_duration, uint64_t* timer_value) { if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - if (nonce_values->api_version != 15) return OEMCrypto_ERROR_INVALID_NONCE; + if (nonce_values->api_major_version != 15) + return OEMCrypto_ERROR_INVALID_NONCE; if (clock_values->status > kActive) { clock_values->timer_status = ODK_TIMER_EXPIRED; return ODK_TIMER_EXPIRED; } - /* If this is before the license was signed, something is odd. Return an - * error. */ - if (system_time_seconds < clock_values->time_of_license_signed) - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - - /* All times are relative to when the license was signed. */ - const uint64_t rental_time = - system_time_seconds - clock_values->time_of_license_signed; - - /* The timer should be limited by the renewal playback duration. This is - * similar to code in AttemptFirstPlayback, above, except we use the - * renewal_playback_duration here, and we do not change clock_values->status. - */ - uint64_t time_left = timer_limits->renewal_playback_duration_seconds; - /* If there is a license duration, it also limits the timer. Remember, a - * limit of 0 means no limit, or infinite. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } - } - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - /* Set timer to limit playback. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); } diff --git a/oemcrypto/odk/src/odk_util.c b/oemcrypto/odk/src/odk_util.c new file mode 100644 index 0000000..7a85b1a --- /dev/null +++ b/oemcrypto/odk/src/odk_util.c @@ -0,0 +1,25 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#include "odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = in_a; + const uint8_t* b = in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} diff --git a/oemcrypto/odk/src/odk_util.h b/oemcrypto/odk/src/odk_util.h new file mode 100644 index 0000000..42d492b --- /dev/null +++ b/oemcrypto/odk/src/odk_util.h @@ -0,0 +1,24 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_UTIL_H_ +#define WIDEVINE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_UTIL_H_ */ diff --git a/oemcrypto/odk/src/serialization_base.c b/oemcrypto/odk/src/serialization_base.c index 3aff38b..505d648 100644 --- a/oemcrypto/odk/src/serialization_base.c +++ b/oemcrypto/odk/src/serialization_base.c @@ -51,6 +51,26 @@ static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { } } +void Pack_enum(Message* message, int value) { + uint32_t v32 = value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(Message* message, const bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(Message* message, const uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + data[0] = *value >> 8; + data[1] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + void Pack_uint32_t(Message* message, const uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -90,6 +110,27 @@ static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { } } +int Unpack_enum(Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return v32; +} + +void Unpack_bool(Message* message, bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = (0 != data[3]); +} + +void Unpack_uint16_t(Message* message, uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + void Unpack_uint32_t(Message* message, uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -195,7 +236,8 @@ size_t GetSize(Message* message) { void SetSize(Message* message, size_t size) { if (message == NULL) return; if (size > message->capacity) message->status = MESSAGE_STATUS_OVERFLOW_ERROR; - message->size = size; + else + message->size = size; } MessageStatus GetStatus(Message* message) { return message->status; } diff --git a/oemcrypto/odk/src/serialization_base.h b/oemcrypto/odk/src/serialization_base.h index e969cb3..109bd49 100644 --- a/oemcrypto/odk/src/serialization_base.h +++ b/oemcrypto/odk/src/serialization_base.h @@ -34,11 +34,17 @@ typedef struct _Message Message; bool ValidMessage(Message* message); +void Pack_enum(Message* message, int value); +void Pack_bool(Message* message, const bool* value); +void Pack_uint16_t(Message* message, const uint16_t* value); void Pack_uint32_t(Message* message, const uint32_t* value); void Pack_uint64_t(Message* message, const uint64_t* value); void PackArray(Message* message, const uint8_t* base, size_t size); void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); +int Unpack_enum(Message* message); +void Unpack_bool(Message* message, bool* value); +void Unpack_uint16_t(Message* message, uint16_t* value); void Unpack_uint32_t(Message* message, uint32_t* value); void Unpack_uint64_t(Message* message, uint64_t* value); void UnpackArray(Message* message, uint8_t* address, diff --git a/oemcrypto/odk/test/odk_fuzz.cpp b/oemcrypto/odk/test/odk_fuzz.cpp index 8d0244a..75dd709 100644 --- a/oemcrypto/odk/test/odk_fuzz.cpp +++ b/oemcrypto/odk/test/odk_fuzz.cpp @@ -115,7 +115,7 @@ static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args, static OEMCryptoResult odk_fun_RenewalResponse( const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce, uint32_t session_id, ODK_ParseRenewal_Args* a, - ODK_RenewalMessage& renewal_msg) { + ODK_PreparedRenewalRequest& renewal_msg) { uint64_t timer_value = 0; OEMCryptoResult err = ODK_ParseRenewal(buf, len, api_version, nonce, session_id, a->system_time, @@ -125,15 +125,16 @@ static OEMCryptoResult odk_fun_RenewalResponse( AllocateMessage(&msg, message_block); InitMessage(msg, const_cast(buf), len); SetSize(msg, len); - Unpack_ODK_RenewalMessage(msg, &renewal_msg); + Unpack_ODK_PreparedRenewalRequest(msg, &renewal_msg); assert(ValidMessage(msg)); } return err; } -static bool kdo_fun_RenewalResponse(const ODK_ParseRenewal_Args* args, - const ODK_RenewalMessage& renewal_msg, - std::string* oemcrypto_core_message) { +static bool kdo_fun_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message) { const auto& common = args->common; ODK_RenewalRequest core_request{common.api_version, common.nonce, common.session_id, renewal_msg.playback_time}; @@ -217,7 +218,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { odk_kdo( odk_fun_LicenseResponse, kdo_fun_LicenseResponse)); verify_roundtrip(data, size, - odk_kdo( + odk_kdo( odk_fun_RenewalResponse, kdo_fun_RenewalResponse)); verify_roundtrip( data, size, diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp index 77c4bfc..016b8bf 100644 --- a/oemcrypto/odk/test/odk_test.cpp +++ b/oemcrypto/odk/test/odk_test.cpp @@ -4,6 +4,8 @@ #include "odk.h" +#include // TODO(b/147944591): use this one? Or odk_endian.h? + #include #include #include @@ -37,6 +39,7 @@ using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; enum ODK_FieldType { + ODK_UINT16, ODK_UINT32, ODK_UINT64, ODK_SUBSTRING, @@ -59,6 +62,8 @@ struct ODK_Field { size_t ODK_FieldLength(ODK_FieldType type) { switch (type) { + case ODK_UINT16: + return sizeof(uint16_t); case ODK_UINT32: return sizeof(uint32_t); case ODK_UINT64: @@ -87,6 +92,11 @@ OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + uint16_t u16 = htobe16(*static_cast(field->value)); + memcpy(buf, &u16, sizeof(u16)); + break; + } case ODK_UINT32: { uint32_t u32 = htobe32(*static_cast(field->value)); memcpy(buf, &u32, sizeof(u32)); @@ -125,6 +135,12 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + memcpy(field->value, buf, sizeof(uint16_t)); + uint16_t* u16p = static_cast(field->value); + *u16p = be16toh(*u16p); + break; + } case ODK_UINT32: { memcpy(field->value, buf, sizeof(uint32_t)); uint32_t* u32p = static_cast(field->value); @@ -166,6 +182,14 @@ OEMCryptoResult ODK_DumpSingleField(const uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + uint16_t val; + memcpy(&val, buf, sizeof(uint16_t)); + val = be16toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } case ODK_UINT32: { uint32_t val; memcpy(&val, buf, sizeof(uint32_t)); @@ -230,14 +254,18 @@ OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } uint8_t* const buf_off = buf + off; - if (mode == ODK_WRITE) { - ODK_WriteSingleField(buf_off, &fields[i]); - } else if (mode == ODK_READ) { - ODK_ReadSingleField(buf_off, &fields[i]); - } else if (mode == ODK_DUMP) { - ODK_DumpSingleField(buf_off, &fields[i]); - } else { - return ODK_ERROR_CORE_MESSAGE; + switch (mode) { + case ODK_WRITE: + ODK_WriteSingleField(buf_off, &fields[i]); + break; + case ODK_READ: + ODK_ReadSingleField(buf_off, &fields[i]); + break; + case ODK_DUMP: + ODK_DumpSingleField(buf_off, &fields[i]); + break; + default: + return ODK_ERROR_CORE_MESSAGE; } off = off2; } @@ -280,14 +308,17 @@ void ValidateRequest(uint32_t message_type, const std::vector& extra_fields, const F& odk_prepare_func, const G& kdo_parse_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint16_t api_minor_version = ODK_MINOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; std::vector total_fields = { {ODK_UINT32, &message_type, "message_type"}, {ODK_UINT32, &message_size, "message_size"}, - {ODK_UINT32, &api_version, "api_version"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, {ODK_UINT32, &nonce, "nonce"}, {ODK_UINT32, &session_id, "session_id"}, }; @@ -317,7 +348,8 @@ void ValidateRequest(uint32_t message_type, std::string oemcrypto_core_message(reinterpret_cast(buf), message_size); EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); - nonce_values.api_version = t.api_version; + nonce_values.api_minor_version = t.api_minor_version; + nonce_values.api_major_version = t.api_major_version; nonce_values.nonce = t.nonce; nonce_values.session_id = t.session_id; EXPECT_EQ(OEMCrypto_SUCCESS, @@ -340,13 +372,15 @@ void ValidateResponse(uint32_t message_type, const std::vector& extra_fields, const F& odk_parse_func, const G& kdo_prepare_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = ODK_MAJOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; std::vector total_fields = { {ODK_UINT32, &message_type, "message_type"}, {ODK_UINT32, &message_size, "message_size"}, - {ODK_UINT32, &api_version, "api_version"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, {ODK_UINT32, &nonce, "nonce"}, {ODK_UINT32, &session_id, "session_id"}, }; @@ -367,7 +401,8 @@ void ValidateResponse(uint32_t message_type, size_t bytes_read = 0, bytes_written = 0; T t = {}; - t.api_version = api_version; + t.api_minor_version = api_minor_version; + t.api_major_version = api_major_version; t.nonce = nonce; t.session_id = session_id; @@ -383,7 +418,8 @@ void ValidateResponse(uint32_t message_type, bytes_written - bytes_read == header_size); // parse buf with odk - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{ODK_MINOR_VERSION, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_SUCCESS, odk_parse_func(buf, bytes_written, &nonce_values)); @@ -465,16 +501,16 @@ TEST(OdkTest, LicenseRequest) { } TEST(OdkTest, RenewalRequest) { - const uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; uint64_t playback_time = 0xCAFE00000000; const uint64_t playback_start = system_time_seconds - playback_time; - std::vector extra_fields = { + const std::vector extra_fields = { {ODK_UINT64, &playback_time, "playback_time"}, }; ODK_ClockValues clock_values = {0}; clock_values.time_of_first_decrypt = playback_start; auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, - const ODK_NonceValues* nonce_values) { + ODK_NonceValues* nonce_values) { return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, &clock_values, system_time_seconds); }; @@ -528,20 +564,17 @@ TEST(OdkTest, LicenseResponse) { .enc_mac_keys = {.offset = 2, .length = 3}, .pst = {.offset = 4, .length = 5}, .srm_restriction_data = {.offset = 6, .length = 7}, - .license_type = 8, - .nonce_required = 0xDEADC0DE, + .license_type = OEMCrypto_EntitlementLicense, + .nonce_required = true, .timer_limits = { - .soft_expiry = 9, + .soft_enforce_rental_duration = true, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 10, - .latest_playback_start_seconds = 11, - .initial_playback_duration_seconds = 12, - .renewal_playback_duration_seconds = 13, - .license_duration_seconds = 14, + .rental_duration_seconds = 11, + .total_playback_duration_seconds = 12, + .initial_renewal_duration_seconds = 13, }, - .request_hash = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, .key_array_length = 3, .key_array = { @@ -569,6 +602,11 @@ TEST(OdkTest, LicenseResponse) { }, }; + const uint8_t request_hash[ODK_SHA256_HASH_SIZE] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + uint8_t request_hash_read[ODK_SHA256_HASH_SIZE]; + memcpy(request_hash_read, request_hash, sizeof(request_hash)); std::vector extra_fields = { {ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv, ".enc_mac_keys_iv"}, {ODK_SUBSTRING, &parsed_license.enc_mac_keys, ".enc_mac_keys"}, @@ -577,20 +615,19 @@ TEST(OdkTest, LicenseResponse) { ".srm_restriction_data"}, {ODK_UINT32, &parsed_license.license_type, ".license_type"}, {ODK_UINT32, &parsed_license.nonce_required, ".nonce_required"}, - {ODK_UINT32, &parsed_license.timer_limits.soft_expiry, ".soft_expiry"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_rental_duration, + ".soft_enforce_rental_duration"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_playback_duration, + ".soft_enforce_playback_duration"}, {ODK_UINT64, &parsed_license.timer_limits.earliest_playback_start_seconds, ".earliest_playback_start_seconds"}, - {ODK_UINT64, &parsed_license.timer_limits.latest_playback_start_seconds, - ".latest_playback_start_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.rental_duration_seconds, + ".rental_duration_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.total_playback_duration_seconds, + ".total_playback_duration_seconds"}, {ODK_UINT64, - &parsed_license.timer_limits.initial_playback_duration_seconds, - ".initial_playback_duration_seconds"}, - {ODK_UINT64, - &parsed_license.timer_limits.renewal_playback_duration_seconds, - ".renewal_playback_duration_seconds"}, - {ODK_UINT64, &parsed_license.timer_limits.license_duration_seconds, - ".license_duration_seconds"}, - {ODK_HASH, &parsed_license.request_hash, ".request_hash"}, + &parsed_license.timer_limits.initial_renewal_duration_seconds, + ".initial_renewal_duration_seconds"}, {ODK_UINT32, &parsed_license.key_array_length, ".key_array_length"}, {ODK_SUBSTRING, &parsed_license.key_array[0].key_id, ".key_id"}, {ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv, ".key_data_iv"}, @@ -610,21 +647,24 @@ TEST(OdkTest, LicenseResponse) { {ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv, ".key_control_iv"}, {ODK_SUBSTRING, &parsed_license.key_array[2].key_control, ".key_control"}, + {ODK_HASH, request_hash_read, ".request_hash"}, }; - - uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {}; - memcpy(request_hash, parsed_license.request_hash, ODK_SHA256_HASH_SIZE); + const std::string request_hash_string( + reinterpret_cast(request_hash), sizeof(request_hash)); auto odk_parse_func = [&](const uint8_t* buf, size_t size, ODK_NonceValues* nonce_values) { ODK_TimerLimits timer_limits; ODK_ClockValues clock_values; - return ODK_ParseLicense(buf, size + 128, size, true, false, request_hash, - &timer_limits, &clock_values, nonce_values, - &parsed_license); + constexpr bool initial_license_load = true; + constexpr bool usage_entry_present = true; + return ODK_ParseLicense(buf, size + 128, size, initial_license_load, + usage_entry_present, request_hash, &timer_limits, + &clock_values, nonce_values, &parsed_license); }; auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, std::string* oemcrypto_core_message) { return CreateCoreLicenseResponse(parsed_license, core_request, + request_hash_string, oemcrypto_core_message); }; ValidateResponse(ODK_License_Response_Type, extra_fields, @@ -636,26 +676,29 @@ TEST(OdkTest, RenewalResponse) { uint64_t playback_clock = 11; uint64_t playback_timer = 12; uint64_t message_playback_clock = 10; + constexpr uint64_t renewal_duration = 130; + uint64_t var_renewal_duration = renewal_duration; std::vector extra_fields = { {ODK_UINT64, &message_playback_clock, "message_playback_clock"}, + {ODK_UINT64, &var_renewal_duration, "renewal_duration"}, }; ODK_TimerLimits timer_limits = { - .soft_expiry = 0, + .soft_enforce_rental_duration = false, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 0, - .latest_playback_start_seconds = 100, - .initial_playback_duration_seconds = 10, - .renewal_playback_duration_seconds = 20, - .license_duration_seconds = 100, + .rental_duration_seconds = 1000, + .total_playback_duration_seconds = 2000, + .initial_renewal_duration_seconds = 30, }; ODK_ClockValues clock_values = { - .time_of_license_signed = 0, + .time_of_license_signed = system_time - playback_clock - 42, .time_of_first_decrypt = system_time - playback_clock, .time_of_last_decrypt = 0, .time_of_renewal_request = message_playback_clock, .time_when_timer_expires = system_time + playback_timer, - .timer_status = 0, + .timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED, .status = kUnused, }; @@ -666,7 +709,7 @@ TEST(OdkTest, RenewalResponse) { &timer_limits, &clock_values, &playback_timer); EXPECT_EQ(ODK_SET_TIMER, err); - EXPECT_EQ(timer_limits.renewal_playback_duration_seconds, playback_timer); + EXPECT_EQ(renewal_duration, playback_timer); EXPECT_EQ(clock_values.time_when_timer_expires, system_time + playback_timer); @@ -678,7 +721,8 @@ TEST(OdkTest, RenewalResponse) { auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, std::string* oemcrypto_core_message) { core_request.playback_time_seconds = message_playback_clock; - return CreateCoreRenewalResponse(core_request, oemcrypto_core_message); + return CreateCoreRenewalResponse(core_request, renewal_duration, + oemcrypto_core_message); }; ValidateResponse(ODK_Renewal_Response_Type, extra_fields, odk_parse_func, kdo_prepare_func); @@ -732,10 +776,12 @@ TEST(OdkSizeTest, LicenseRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreLicenseRequest(message, message_length, &core_message_length, &nonce_values)); @@ -748,13 +794,15 @@ TEST(OdkSizeTest, RenewalRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; ODK_ClockValues clock_values = {}; clock_values.time_of_first_decrypt = 10; uint64_t system_time_seconds = 15; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreRenewalRequest(message, message_length, &core_message_length, &nonce_values, @@ -768,11 +816,13 @@ TEST(OdkSizeTest, ProvisioningRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; uint32_t device_id_length = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreProvisioningRequest( message, message_length, &core_message_length, &nonce_values, diff --git a/oemcrypto/odk/test/odk_test.h b/oemcrypto/odk/test/odk_test.h deleted file mode 100644 index a8114a1..0000000 --- a/oemcrypto/odk/test/odk_test.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_TEST_H_ -#define ODK_TEST_H_ - -#include "OEMCryptoCENCCommon.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef enum { - ODK_UINT32, - ODK_UINT64, - ODK_SUBSTRING, - ODK_DEVICEID, - ODK_HASH, - ODK_NUMTYPES, -} ODK_FieldType; - -typedef enum { - ODK_READ, - ODK_WRITE, -} ODK_FieldMode; - -typedef struct { - ODK_FieldType type; - void* value; -} ODK_Field; - -#define DEVICE_ID_MAX (64) - -#ifdef __cplusplus -extern "C" { -#endif - -size_t ODK_FieldLength(ODK_FieldType type); -OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, - const ODK_Field* const field); -OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, - const ODK_Field* const field); - -OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -#ifdef __cplusplus -} -#endif - -#endif // ODK_TEST_H_ diff --git a/oemcrypto/odk/test/odk_timer_test.cpp b/oemcrypto/odk/test/odk_timer_test.cpp index 9259220..c7f00ce 100644 --- a/oemcrypto/odk/test/odk_timer_test.cpp +++ b/oemcrypto/odk/test/odk_timer_test.cpp @@ -6,17 +6,22 @@ #include "OEMCryptoCENCCommon.h" #include "gtest/gtest.h" #include "odk.h" - -using ::testing::Values; -using ::testing::WithParamInterface; +#include "odk_structs_priv.h" namespace { -constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff. -struct ServerExpiry { - bool soft_rental; - bool soft_playback; -}; +// The rental clock starts when the request is signed. If any test fails +// because a time is off by exactly 1000, that is a strong indication that we +// confused rental and system clocks. The system clock should only be used +// internally on the device, because it might not be synchronized from one +// device to another. Rental clock is used in the license message from the +// server. +constexpr uint64_t kRentalClockStart = 1000u; + +// The renewal grace period is used by the server and the CDM layer to allow +// time between when the renewal is requested and when playback is cutoff if the +// renewal is not loaded. +constexpr uint64_t kGracePeriod = 5u; TEST(OdkTimerBasicTest, NullTest) { // Assert that nullptr does not cause a core dump. @@ -33,10 +38,11 @@ TEST(OdkTimerBasicTest, Init) { uint64_t time = 42; ODK_InitializeClockValues(&clock_values, time); EXPECT_EQ(clock_values.time_of_license_signed, time); - EXPECT_EQ(clock_values.time_of_first_decrypt, 0); - EXPECT_EQ(clock_values.time_of_last_decrypt, 0); - EXPECT_EQ(clock_values.time_when_timer_expires, 0); - EXPECT_EQ(clock_values.timer_status, 0); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, kUnused); } @@ -55,866 +61,1176 @@ TEST(OdkTimerBasicTest, Reload) { EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); EXPECT_EQ(clock_values.time_when_timer_expires, 0u); - EXPECT_EQ(clock_values.timer_status, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, status); } -// ************************************************************************ -// Test that we can only start playback within the rental window. This -// simulates requesting a license at rental_clock_start_, and a rental window of -// 50-150s relative to license request, or 100-200s absolute. -class OdkTimerRentalWindow : public ::testing::Test { +// All of the following test cases are derived from this base class. It +// simulates loading a license, attempting playback, and reloading a license. +class ODKTimerTest : public ::testing::Test { public: - OdkTimerRentalWindow() { - rental_clock_start_ = 10000u; - rental_window_start_ = 50u; - rental_window_duration_ = 100u; + ODKTimerTest() { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the ODK library behaves correctly. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); } protected: void SetUp() override { ::testing::Test::SetUp(); - // Start rental clocks at rental_clock_start_. - ODK_InitializeClockValues(&clock_values_, rental_clock_start_); - // Simple license values: - timer_limits_.soft_expiry = false; - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - timer_limits_.latest_playback_start_seconds = - rental_window_start_ + rental_window_duration_; - timer_limits_.initial_playback_duration_seconds = 0; - timer_limits_.renewal_playback_duration_seconds = 0; - timer_limits_.license_duration_seconds = 0; + // Start rental clock at kRentalClockStart. This happens when the license + // request is signed. + ODK_InitializeClockValues(&clock_values_, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); } - // Simulate reloading a license in a new session. An offline license should - // have several of the clock_value's fields saved into the usage table. When - // it is reloaded those values should be reloaded. From these fields, the - // ODK function can tell if this is the first playback for the license, or - // just the first playback for this session. The key fields that should be - // saved are the status, and the times for license signed, and first and - // last playback times. - void ReloadClock(uint64_t system_time) { + // Simulate loading or reloading a license in a new session. An offline + // license should have several of the clock_value's fields saved into the + // usage table. When it is reloaded those values should be reloaded. From + // these fields, the ODK function can tell if this is the first playback for + // the license, or just the first playback for this session. The key fields + // that should be saved are the status, and the times for license signed, and + // first and last playback times. + void ReloadLicense(uint64_t system_time) { ODK_ClockValues old_clock_values = clock_values_; // First clear out the old clock values. - ODK_InitializeClockValues(&clock_values_, 0u); + memset(&clock_values_, 0, sizeof(clock_values_)); + // When the session is opened, the clock values are initialized. + ODK_InitializeClockValues(&clock_values_, 0); + // When the usage entry is reloaded, the clock values are reloaded. ODK_ReloadClockValues(&clock_values_, old_clock_values.time_of_license_signed, old_clock_values.time_of_first_decrypt, old_clock_values.time_of_last_decrypt, old_clock_values.status, system_time); + EXPECT_EQ(clock_values_.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + // These shall not change: + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + // ODK_ParseLicense sets the new timer state. + clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; } - uint64_t system_time(uint64_t rental_clock) { - return rental_clock_start_ + rental_clock; + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ReloadLicense(start); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + uint64_t timer_value; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + start, &timer_limits_, &clock_values_, &timer_value); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + ReloadLicense(start); + TerminatePlayback(start, cutoff, cutoff + 10); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + // times do not change if playback was not allowed. + CheckClockValues(cutoff); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + ReloadLicense(start); + ODK_ClockValues old_clock_values = clock_values_; + uint64_t timer_value; + EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, + &timer_value), + ODK_TIMER_EXPIRED); + // These should not have changed. In particular, if the license was unused + // before, it should reamin unused. + EXPECT_EQ(clock_values_.time_of_license_signed, + old_clock_values.time_of_license_signed); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, old_clock_values.status); + } + + // Verify that the clock values are correct. + void CheckClockValues(uint64_t time_of_last_decrypt) { + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); + EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, kActive); + } + + // Convert from rental time to system time. By "system time", we mean + // OEMCrypto's montonic clock. The spec does not specify a starting time for + // this clock. + uint64_t GetSystemTime(uint64_t rental_clock) { + return kRentalClockStart + rental_clock; + } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0. + uint64_t EndOfPlaybackWindow() { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + + timer_limits_.rental_duration_seconds; } ODK_TimerLimits timer_limits_; ODK_ClockValues clock_values_; - - // The rental clock starts when the request is signed. - uint64_t rental_clock_start_; - // start of rental window in seconds since rental clock start. - uint64_t rental_window_start_; - // The "width" of window. - uint64_t rental_window_duration_; + // The start of playback. This is set to the planned start at the beginning of + // the test. (using system clock) + uint64_t start_of_playback_; }; -TEST_F(OdkTimerRentalWindow, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); +TEST_F(ODKTimerTest, SimplePlayback) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); } -TEST_F(OdkTimerRentalWindow, NullTimer) { - // If OEMCrypto passes in a nullpointer, then the ODK should allow this. +// This tests that we are not allowed to start playback before the rental window +// opens. +TEST_F(ODKTimerTest, EarlyTest) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is earlier than we are allowed. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + ForbidPlayback(bad_start_time); + // And times were not updated: + EXPECT_EQ(clock_values_.status, kUnused); + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This runs the same test as above, but explicitly gives the ODK library a null +// pointer for the timer value. A null pointer is allowed for OEMCrypto +// implementations that do not manage their own timer. +TEST_F(ODKTimerTest, NullTimer) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is the earlier, invalid start time. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + timer_limits_.rental_duration_seconds = 300; + timer_limits_.soft_enforce_rental_duration = false; + ReloadLicense(GetSystemTime(bad_start_time)); + + // If OEMCrypto passes in a null pointer, then the ODK should allow this. uint64_t* timer_value_pointer = nullptr; - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); + EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_TIMER_EXPIRED); + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_SET_TIMER); + CheckClockValues(start_of_playback_); } -// Verify that an attempt to start playback outside the rental window fails. -TEST_F(OdkTimerRentalWindow, LateTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t start_time = system_time(201); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); -} +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 700 rental duration. +/*****************************************************************************/ -// ************************************************************************ -// The Hard Rental use case is a strict time limit on playback. -class OdkTimerHardTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = false; // Hard expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerHardTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); -} - -TEST_F(OdkTimerHardTest, NormalTest) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerHardTest, Reload) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -TEST_F(OdkTimerHardTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} - -// ************************************************************************ -// The Soft Rental use case is a strict time limit on playback. -class OdkTimerSoftTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = true; // Soft expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerSoftTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, NormalTest) { - uint64_t timer_value = 0; - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - // Try to play after the cutoff time is still valid for soft expiry. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerSoftTest, Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play after the cutoff time is still valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} -// ************************************************************************ - -// ************************************************************************ -// From the server's point of view, there are two flags that decide soft or -// hard limits: the rental duration, and the playback duration. From -// OEMCrypto's point of view, there is only playback duration. A soft or hard -// rental duration is translated into different rental and license durations. -// The four test classes below all have a 700 rental window and a 200 playback -// duration. We'll use the server description, and then set the OEMCrypto -// restraints in the test SetUp() function. Note, it's easier to use the word -// "day" but really the rental window is 700 seconds, not 7 days. These tests -// have some coverage overlap with the ones above, but it is better to have -// these all grouped together to make sure we cover all of the server team's -// needs. -// ************************************************************************ -class Odk7DayTest : public OdkTimerRentalWindow, - public WithParamInterface { +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class ODKUseCase_Streaming : public ODKTimerTest { public: - Odk7DayTest() { - rental_window_duration_ = 700; - rental_window_start_ = 100u; + ODKUseCase_Streaming() { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 300; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_F(ODKUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_F(ODKUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class ODKUseCase_StreamingQuickStart : public ODKTimerTest { + public: + ODKUseCase_StreamingQuickStart() { + // Rental duration = 30 seconds, soft. + // Playback duration = 3 hours (use 300 for readability) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30; + timer_limits_.total_playback_duration_seconds = 300; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = + GetSystemTime(timer_limits_.rental_duration_seconds - 10); + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoHard_Start6 + : public ODKUseCase_SevenHardTwoHard_Start3 { + public: + ODKUseCase_SevenHardTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoSoft_Start6 + : public ODKUseCase_SevenHardTwoSoft_Start3 { + public: + ODKUseCase_SevenHardTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 300, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoHard_Start6 + : public ODKUseCase_SevenSoftTwoHard_Start3 { + public: + ODKUseCase_SevenSoftTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoSoft_Start6 + : public ODKUseCase_SevenSoftTwoSoft_Start3 { + public: + ODKUseCase_SevenSoftTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); + LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + // But forbid restart after playback window. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +class RenewalTest : public ODKTimerTest { + protected: + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + uint64_t timer_value; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + &timer_value); + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + // This does the work of the function above, except it allows the caller to + // explicitly set the timer_value_pointer. The timer_value_pointer can be a + // nullptr. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds, + uint64_t* timer_value_pointer) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + const OEMCryptoResult result = ODK_ComputeRenewalDuration( + &timer_limits_, &clock_values_, start, renewal_duration_seconds, + timer_value_pointer); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + if (timer_value_pointer != nullptr) + EXPECT_EQ(*timer_value_pointer, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|. Verify that we are allowed playback from |start| to + // |cutoff|, but playback not allowed after |cutoff|. If you refer to the + // diagrams in "License Duration and Renewal", this tests a cyan bar with a + // black X. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|. Verify that we are allowed playback from |start| to |cutoff|, but + // playback not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. This + // assumes that |cutoff| is between |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues( + cutoff); // times do not change if playback was not allowed. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that a renewal can be processed and playback may start from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. This is different from the previous functions, + // because the renewal is loaded before the first playback. + void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + uint64_t timer_value; + const OEMCryptoResult result = + ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, + renewal_duration_seconds, &timer_value); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + AllowPlayback(start, stop, cutoff); + } +}; + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests) These tests are parameterized. If the parameter is 0, we +// limit the playback duration. If the parameter is 1, we limit the rental +// duration. The behavior is basically the same. +class ODKUseCase_LicenseWithRenewal + : public RenewalTest, + public ::testing::WithParamInterface { + public: + ODKUseCase_LicenseWithRenewal() { + // Either Playback or rental duration = 2 days hard + if (GetParam() == 0) { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 2000; + } else { + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; + } + timer_limits_.initial_renewal_duration_seconds = 50; + } + + void SetUp() override { + RenewalTest::SetUp(); + renewal_interval_ = + timer_limits_.initial_renewal_duration_seconds - kGracePeriod; + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, next_renewal, + next_renewal + kGracePeriod); + } + + uint64_t playback_end_restriction() { + if (GetParam() == 0) { + return EndOfRentalWindow(); + } else { + return EndOfPlaybackWindow(); + } } protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - server_expiry_ = GetParam(); - const uint64_t playback_duration = 200; - // Beginning of rental window. it is unusual to start it in the future, - // but it is a supported use case, so we'll test it here. - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - // The rental window is 700 long. - timer_limits_.latest_playback_start_seconds = - timer_limits_.earliest_playback_start_seconds + rental_window_duration_; - // Playback duration is 200 starting from first playback. - timer_limits_.initial_playback_duration_seconds = playback_duration; - if (server_expiry_.soft_rental) { - // The license duration limits any restart. For soft rental window, the - // server will set this to the rental end + playback duration. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds + playback_duration; - } else { - // The license duration limits any restart. For hard rental window, the - // server will set this to the rental end. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds; - } - timer_limits_.soft_expiry = server_expiry_.soft_playback; - } - ServerExpiry server_expiry_; + // How long to wait before we load the next renewal. i.e. cutoff - + // kGracePeriod. This is because the renewal_duration includes the grace + // period. + uint64_t renewal_interval_; }; -TEST_P(Odk7DayTest, StartDay3) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback within rental duration and renewal duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay3Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); +// Playback within rental duration, last renewal_interval_ exceeds renewal +// duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback interrupted by late renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + const uint64_t late_renewal = next_renewal + kGracePeriod + 10; + next_renewal = late_renewal + renewal_interval_; + RenewAndContinue(late_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 1; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + // Reload license after timer expired. + uint64_t reload_time = next_renewal + kGracePeriod + 100; + next_renewal = reload_time + renewal_interval_; + ReloadLicense(reload_time); + RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, + timer_limits_.initial_renewal_duration_seconds); + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay6Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - const uint64_t cutoff_time = - server_expiry_.soft_rental - ? - // If the rental expiry is soft, we can continue playing and - // reloading for the full playback duration. - start_time + timer_limits_.initial_playback_duration_seconds - // If the rental expiry is hard, we can reload only within the - // rental window. - : system_time(timer_limits_.latest_playback_start_seconds); - // Let's double check that math: - if (server_expiry_.soft_rental) { - // If that was not clear, the cutoff = 100 start of window + 600 start time - // + 200 playback duration... - EXPECT_EQ(system_time(900u), cutoff_time); - } else { - // ...and for hard rental, the cutoff = 100 start of window + 700 rental - // duration. - EXPECT_EQ(system_time(800u), cutoff_time); - } - - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - } - - // Try to play before the cutoff time is valid. - valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 2; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - } +// Playback within renewal duration, but exceeds playback duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } while ((next_renewal + renewal_interval_ + kGracePeriod) < + playback_end_restriction()); + // Attempt playing beyond the playback window. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } -// This test explicitly shows the difference between hard and soft rental -// expiry. This is not an OEMCrypto concept, but it is used on the serer, and -// this test verifies the logic is handled correctly when we translate it into -// OEMCrypto concepts. -TEST_P(Odk7DayTest, StartDay6ReloadDay7) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); +// Renewal duration increases over time. +TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attempt playing beyond the playback window: + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} - // Reload time is 1 second after end of rental window. - const uint64_t restart_time = - system_time(timer_limits_.latest_playback_start_seconds + 1); - ReloadClock(restart_time); - if (server_expiry_.soft_rental) { - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, - kTolerance); - EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance); - } - } else { - // For hard rental expiry, reloading after the rental window fails. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); +// Increasing renewal duration, playback exceeds last renewal. +// This is a mix between case 2 and case 6. +TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; } + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); } -TEST_P(Odk7DayTest, StartDay8) { - uint64_t timer_value = 0; - // Starting playback after the rental window should not work. - const uint64_t eight_days = 800u; - const uint64_t start_time = system_time(rental_window_start_ + eight_days); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime( - start_time, &timer_limits_, &clock_values_)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); +// This is just like Case1, except we use a null pointer for the timer values in +// RenewAndContinue. It is not shown in the use case document. +TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + const uint64_t start = next_renewal; + const uint64_t stop = start + renewal_interval_; + const uint64_t cutoff = stop + kGracePeriod; + const uint64_t renewal_duration_seconds = + timer_limits_.initial_renewal_duration_seconds; + uint64_t* timer_value_pointer = nullptr; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + timer_value_pointer); } -INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest, - Values(ServerExpiry({true, true}), - ServerExpiry({true, false}), - ServerExpiry({false, true}), - ServerExpiry({false, false}))); +INSTANTIATE_TEST_CASE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, + ::testing::Values(0, 1)); +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class ODKUseCase_LimitedDurationLicense : public RenewalTest { + public: + ODKUseCase_LimitedDurationLicense() { + renewal_delay_ = 30u; + time_of_renewal_ = start_of_playback_ + renewal_delay_; + + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 150; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; // Two hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + kGracePeriod; + } + uint64_t renewal_delay_; + uint64_t time_of_renewal_; +}; + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); +} + +// Playback started after rental duration. +TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); +} + +// Playback started within rental window, renewal is received, and playback +// continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. + EndOfPlaybackWindow(), // stop: when timer expires. + renewal_duration); +} + +// Playback may be restarted. +TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); + + const uint64_t reload_time = play_for_one_hour + 100; + ReloadLicense(reload_time); + // Simulate reloading the license, and then reloading the renewal, and then + // restarting playback. That is allowed, and playback shall be terminated at + // the end of the original playback window. + RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), + renewal_duration); + // But not one second more. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, + &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(EndOfPlaybackWindow()); +} + +// Verify that the backwards compatible function, ODK_InitializeV15Values, can +// set up the clock values and timer limits. +TEST_F(RenewalTest, V15Test) { + const uint32_t key_duration = 25; + ODK_NonceValues nonce_values; + const uint64_t license_loaded = GetSystemTime(10); + EXPECT_EQ( + ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, + key_duration, license_loaded), + OEMCrypto_SUCCESS); + const uint64_t later_on = GetSystemTime(200); + TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); + const uint32_t new_key_duration = 100; + RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); +} } // namespace diff --git a/oemcrypto/ref/src/oemcrypto_engine_ref.cpp b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp index b313fb4..4b401ab 100644 --- a/oemcrypto/ref/src/oemcrypto_engine_ref.cpp +++ b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp @@ -42,11 +42,15 @@ CryptoEngine::~CryptoEngine() { } bool CryptoEngine::Initialize() { + std::string file_path = GetUsageTimeFileFullPath(); + LoadOfflineTimeInfo(file_path); usage_table_.reset(MakeUsageTable()); return true; } void CryptoEngine::Terminate() { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); std::unique_lock lock(session_table_lock_); ActiveSessions::iterator it; for (it = sessions_.begin(); it != sessions_.end(); ++it) { @@ -102,44 +106,53 @@ int64_t CryptoEngine::OnlineTime() { } int64_t CryptoEngine::RollbackCorrectedOfflineTime() { - struct TimeInfo { - // The max time recorded through this function call. - int64_t previous_time; - // If the wall time is rollbacked to before the previous_time, this member - // is updated to reflect the offset. - int64_t rollback_offset; - // Pad the struct so that TimeInfo is a multiple of 16. - uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; - }; + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + // Write time info to disk if kTimeInfoUpdateWindowInSeconds has elapsed since + // last write. + if (current_time - offline_time_info_.previous_time > + kTimeInfoUpdateWindowInSeconds) { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); + } + return current_time; +} - std::vector encrypted_buffer(sizeof(TimeInfo)); - std::vector clear_buffer(sizeof(TimeInfo)); - TimeInfo time_info; - memset(&time_info, 0, sizeof(time_info)); - // Use the device key for encrypt/decrypt. - const std::vector& key = DeviceRootKey(); - - std::unique_ptr file; - std::string path; - // Note: this path is OK for a real implementation, but using security level 1 - // would be better. +std::string CryptoEngine::GetUsageTimeFileFullPath() const { + std::string file_path; + // Note: file path is OK for a real implementation, but using security + // level 1 would be better. // TODO(fredgc, jfore): Address how this property is presented to the ref. - // For now, the path is empty. + // For now, the file path is empty. /*if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, - &path)) { - LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); + &file_path)) { + LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); }*/ - std::string filename = path + "StoredUsageTime.dat"; + return file_path + kStoredUsageTimeFileName; +} +bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) { + memset(&offline_time_info_, 0, sizeof(TimeInfo)); wvcdm::FileSystem* file_system = file_system_.get(); - if (file_system->Exists(filename)) { - // Load time info from previous call to this function. - file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly); + if (file_system->Exists(file_path)) { + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::unique_ptr file = + file_system->Open(file_path, wvcdm::FileSystem::kReadOnly); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return OnlineTime(); + file_path.c_str()); + return false; } + // Load time info from previous call. file->Read(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); // Decrypt the encrypted TimeInfo buffer. AES_KEY aes_key; @@ -147,42 +160,65 @@ int64_t CryptoEngine::RollbackCorrectedOfflineTime() { std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&encrypted_buffer[0], &clear_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_DECRYPT); - memcpy(&time_info, &clear_buffer[0], sizeof(TimeInfo)); - } + memcpy(&offline_time_info_, &clear_buffer[0], sizeof(TimeInfo)); - int64_t current_time; - // Add any time offsets in the past to the current time. - current_time = OnlineTime() + time_info.rollback_offset; - if (time_info.previous_time > current_time) { - // Time has been rolled back. - // Update the rollback offset. - time_info.rollback_offset += time_info.previous_time - current_time; - // Keep current time at previous recorded time. - current_time = time_info.previous_time; + // Detect offline time rollback after loading from disk. + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + if (offline_time_info_.previous_time > current_time) { + // Current time is earlier than the previously saved time. Time has been + // rolled back. Update the rollback offset. + offline_time_info_.rollback_offset += + offline_time_info_.previous_time - current_time; + // Keep current time at previous recorded time. + current_time = offline_time_info_.previous_time; + } + // The new previous_time will either stay the same or move forward. + offline_time_info_.previous_time = current_time; } + return true; +} + +bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) { + // Add any time offsets in the past to the current time. If there was an + // earlier offline rollback, the rollback offset will be updated in + // LoadOfflineTimeInfo(). It guarantees that the current time to be saved + // will never go back. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; // The new previous_time will either stay the same or move forward. - time_info.previous_time = current_time; + if (current_time > offline_time_info_.previous_time) + offline_time_info_.previous_time = current_time; + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); // Copy updated data and encrypt the buffer. - memcpy(&clear_buffer[0], &time_info, sizeof(TimeInfo)); + memcpy(&clear_buffer[0], &offline_time_info_, sizeof(TimeInfo)); AES_KEY aes_key; AES_set_encrypt_key(&key[0], 128, &aes_key); std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&clear_buffer[0], &encrypted_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_ENCRYPT); + std::unique_ptr file; + wvcdm::FileSystem* file_system = file_system_.get(); // Write the encrypted buffer to disk. file = file_system->Open( - filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); + file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return OnlineTime(); + file_path.c_str()); + return false; } file->Write(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); - - // Return time with offset. - return current_time; + return true; } bool CryptoEngine::NonceCollision(uint32_t nonce) { diff --git a/oemcrypto/ref/src/oemcrypto_engine_ref.h b/oemcrypto/ref/src/oemcrypto_engine_ref.h index d111b73..dd39e16 100644 --- a/oemcrypto/ref/src/oemcrypto_engine_ref.h +++ b/oemcrypto/ref/src/oemcrypto_engine_ref.h @@ -29,10 +29,23 @@ namespace wvoec_ref { typedef std::map ActiveSessions; +static const std::string kStoredUsageTimeFileName = "StoredUsageTime.dat"; + +typedef struct { + // The max time recorded + int64_t previous_time; + // If the wall time is rollbacked to before the previous_time, this member + // is updated to reflect the offset. + int64_t rollback_offset; + // Pad the struct so that TimeInfo is a multiple of 16. + uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; +} TimeInfo; + class CryptoEngine { public: static const uint32_t kApiVersion = 16; static const uint32_t kMinorApiVersion = 0; + static const int64_t kTimeInfoUpdateWindowInSeconds = 300; // This is like a factory method, except we choose which version to use at // compile time. It is defined in several source files. The build system @@ -217,6 +230,10 @@ class CryptoEngine { // System clock with antirollback protection, measuring time in seconds. int64_t RollbackCorrectedOfflineTime(); + bool LoadOfflineTimeInfo(const std::string& file_path); + bool SaveOfflineTimeInfo(const std::string& file_path); + std::string GetUsageTimeFileFullPath() const; + explicit CryptoEngine(std::unique_ptr&& file_system); virtual SessionContext* MakeSession(SessionId sid); virtual UsageTable* MakeUsageTable(); @@ -226,6 +243,7 @@ class CryptoEngine { std::mutex session_table_lock_; std::unique_ptr file_system_; std::unique_ptr usage_table_; + TimeInfo offline_time_info_; CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine); }; diff --git a/oemcrypto/ref/src/oemcrypto_ref.cpp b/oemcrypto/ref/src/oemcrypto_ref.cpp index fb18138..cfede60 100644 --- a/oemcrypto/ref/src/oemcrypto_ref.cpp +++ b/oemcrypto/ref/src/oemcrypto_ref.cpp @@ -467,14 +467,17 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( return OEMCrypto_ERROR_INVALID_CONTEXT; } - // Range check - for (size_t i = 0; i < num_keys; i++) { - if (!RangeCheck(message_length, key_array[i].key_id, true) || - !RangeCheck(message_length, key_array[i].key_control, false) || - !RangeCheck(message_length, key_array[i].key_control_iv, true)) { - LOGE("Range Check %zu", i); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + // We only use the first key object to update the entire license. Since we + // know num_keys > 0 after the last if statement, we can assume index is not + // out of bounds. + constexpr size_t kIndex = 0; + + // Range check. + if (!RangeCheck(message_length, key_array[kIndex].key_id, true) || + !RangeCheck(message_length, key_array[kIndex].key_control, false) || + !RangeCheck(message_length, key_array[kIndex].key_control_iv, true)) { + LOGE("Range Check %zu", kIndex); + return OEMCrypto_ERROR_INVALID_CONTEXT; } // Validate message signature @@ -489,37 +492,31 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( std::vector key_id; std::vector key_control; std::vector key_control_iv; - for (size_t i = 0; i < num_keys; i++) { - if (key_array[i].key_id.length != 0) { - key_id.assign( - message + key_array[i].key_id.offset, - message + key_array[i].key_id.offset + key_array[i].key_id.length); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - if (key_array[i].key_control_iv.length == 0) { - key_control_iv.clear(); - } else { - key_control_iv.assign( - message + key_array[i].key_control_iv.offset, - message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE); - } - } else { - // key_id could be null if special control key type - // key_control is not encrypted in this case - key_id.clear(); + if (key_array[kIndex].key_id.length != 0) { + key_id.assign(message + key_array[kIndex].key_id.offset, + message + key_array[kIndex].key_id.offset + + key_array[kIndex].key_id.length); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); + if (key_array[kIndex].key_control_iv.length == 0) { key_control_iv.clear(); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - } - - status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); - if (status != OEMCrypto_SUCCESS) { - LOGE("error %d in key %zu", status, i); - break; + } else { + key_control_iv.assign(message + key_array[kIndex].key_control_iv.offset, + message + key_array[kIndex].key_control_iv.offset + + wvoec::KEY_IV_SIZE); } + } else { + // key_id could be null if special control key type + // key_control is not encrypted in this case + key_id.clear(); + key_control_iv.clear(); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); } + + status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); if (status != OEMCrypto_SUCCESS) { return status; } diff --git a/oemcrypto/ref/src/oemcrypto_session.cpp b/oemcrypto/ref/src/oemcrypto_session.cpp index 736adea..4e74cf6 100644 --- a/oemcrypto/ref/src/oemcrypto_session.cpp +++ b/oemcrypto/ref/src/oemcrypto_session.cpp @@ -430,7 +430,7 @@ OEMCryptoResult SessionContext::PrepAndSignRenewalRequest( } // If we are talking to an old license server, then we only sign the message // body. - if (nonce_values_.api_version < 16) { + if (nonce_values_.api_major_version < 16) { const uint8_t* message_body = message + *core_message_length; const size_t message_body_length = message_length - *core_message_length; return GenerateSignature(message_body, message_body_length, signature, @@ -1100,10 +1100,40 @@ OEMCryptoResult SessionContext::RefreshKey( if (session_keys_ == nullptr) { return OEMCrypto_ERROR_INVALID_CONTEXT; } + if (key_control.empty()) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector decrypted_key_control; + if (key_id.empty()) { + // Key control is not encrypted if key id is NULL + decrypted_key_control = key_control; + } else { + Key* content_key = session_keys_->Find(key_id); + if (nullptr == content_key) { + LOGE("Key ID not found."); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector content_key_value = content_key->value(); + // Decrypt encrypted key control block + if (key_control_iv.empty()) { + decrypted_key_control = key_control; + } else { + if (!DecryptMessage(content_key_value, key_control_iv, key_control, + &decrypted_key_control, 128 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + } + KeyControlBlock key_control_block(decrypted_key_control); + if (!key_control_block.valid()) { + LOGE("Parse key control error."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint32_t new_key_duration = key_control_block.duration(); uint64_t* timer_value = nullptr; const OEMCryptoResult result = ODK_RefreshV15Values(&timer_limits_, &clock_values_, &nonce_values_, - ce_->SystemTime(), timer_value); + ce_->SystemTime(), new_key_duration, timer_value); if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) return OEMCrypto_SUCCESS; if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; diff --git a/oemcrypto/ref/src/oemcrypto_usage_table_ref.h b/oemcrypto/ref/src/oemcrypto_usage_table_ref.h index 661ba3a..e8d4360 100644 --- a/oemcrypto/ref/src/oemcrypto_usage_table_ref.h +++ b/oemcrypto/ref/src/oemcrypto_usage_table_ref.h @@ -56,7 +56,7 @@ class UsageTableEntry { virtual OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, size_t* buffer_length); virtual void UpdateAndIncrement(ODK_ClockValues* clock_values); - // Save all data to the give buffer. This should be called after updating the + // Save all data to the given buffer. This should be called after updating the // data. OEMCryptoResult SaveData(CryptoEngine* ce, SessionContext* session, uint8_t* signed_buffer, size_t buffer_size); @@ -72,8 +72,8 @@ class UsageTableEntry { recent_decrypt_ = recent_decrypt; } static size_t SignedEntrySize(); - const uint8_t* mac_key_server() { return data_.mac_key_server; } - const uint8_t* mac_key_client() { return data_.mac_key_client; } + const uint8_t* mac_key_server() const { return data_.mac_key_server; } + const uint8_t* mac_key_client() const { return data_.mac_key_client; } protected: UsageTable* usage_table_; // Owner of this object. diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index dfd4475..bf9d61a 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -63,7 +63,6 @@ bool CanChangeTime() { void DeviceFeatures::Initialize() { if (initialized_) return; uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -100,10 +99,6 @@ void DeviceFeatures::Initialize() { loads_certificate = false; } printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); - uses_certificate = (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_GenerateRSASignature(session, buffer, 0, buffer, - &size, kSign_RSASSA_PSS)); - printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_Generic_Encrypt(session, buffer, 0, buffer, @@ -145,7 +140,6 @@ void DeviceFeatures::Initialize() { printf("NO_METHOD: Cannot derive known session keys.\n"); // Note: cast_receiver left unchanged because set by user on command line. uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -174,7 +168,6 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { std::string filter = initial_filter; // clang-format off if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); - if (!uses_certificate) FilterOut(&filter, "OEMCrypto*Cert*"); if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); diff --git a/oemcrypto/test/oec_device_features.h b/oemcrypto/test/oec_device_features.h index 9d2db5a..9cef66f 100644 --- a/oemcrypto/test/oec_device_features.h +++ b/oemcrypto/test/oec_device_features.h @@ -38,7 +38,6 @@ class DeviceFeatures { enum DeriveMethod derive_key_method; bool uses_keybox; // Device uses a keybox to derive session keys. - bool uses_certificate; // Device uses a certificate to derive session keys. bool loads_certificate; // Device can load a certificate from the server. bool generic_crypto; // Device supports generic crypto. bool cast_receiver; // Device supports alternate rsa signature padding. diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index 776e822..6c10c32 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -137,26 +138,6 @@ class boringssl_ptr { CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr); }; -OEMCrypto_Substring GetSubstring(const std::string& message, - const std::string& field, bool set_zero) { - OEMCrypto_Substring substring; - if (set_zero || field.empty() || message.empty()) { - substring.offset = 0; - substring.length = 0; - } else { - size_t pos = message.find(field); - if (pos == std::string::npos) { - LOGW("GetSubstring : Cannot find offset for %s", field.c_str()); - substring.offset = 0; - substring.length = 0; - } else { - substring.offset = pos; - substring.length = field.length(); - } - } - return substring; -} - Test_PST_Report::Test_PST_Report(const std::string& pst_in, OEMCrypto_Usage_Entry_Status status_in) : status(status_in), pst(pst_in) { @@ -169,23 +150,29 @@ void RoundTrip::SignAndVerifyRequest() { // In the real world, a message should be signed by the client and // verified by the server. This simulates that. - vector data(message_size_); - for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; - OEMCryptoResult sts; size_t gen_signature_length = 0; size_t core_message_length = 0; - sts = + constexpr size_t small_size = 42; // arbitrary. + size_t message_size = + std::max(required_message_size_, core_message_length + small_size); + vector data(message_size, 0); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + ASSERT_EQ( PrepAndSignRequest(session()->session_id(), data.data(), data.size(), - &core_message_length, nullptr, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - // If this fails, either the test needs to be modified, or the core message is - // very very large. - ASSERT_LT(core_message_length, message_size_); + &core_message_length, nullptr, &gen_signature_length), + OEMCrypto_ERROR_SHORT_BUFFER); + // Make the message buffer a little bigger than the core message, or the + // required size, whichever is larger. + message_size = + std::max(required_message_size_, core_message_length + small_size); + data.resize(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + vector gen_signature(gen_signature_length); - sts = PrepAndSignRequest(session()->session_id(), data.data(), data.size(), - &core_message_length, gen_signature.data(), - &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(), + data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length), + OEMCrypto_SUCCESS); if (global_features.api_version >= kCoreMessagesAPI) { ASSERT_GT(data.size(), core_message_length); std::string core_message(reinterpret_cast(data.data()), @@ -247,7 +234,7 @@ void ProvisioningRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(global_features.api_version, core_request_.api_version); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); EXPECT_EQ(session()->nonce(), core_request_.nonce); EXPECT_EQ(session()->session_id(), core_request_.session_id); size_t device_id_length = core_request_.device_id.size(); @@ -282,7 +269,7 @@ void ProvisioningRoundTrip::CreateDefaultResponse() { } else { response_data_.enc_message_key_length = 0; } - core_response_.key_type = OEMCrypto_Supports_RSA_2048bit; + core_response_.key_type = OEMCrypto_RSA_Private_Key; core_response_.enc_private_key = FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length); core_response_.enc_private_key_iv = FindSubstring( @@ -296,12 +283,20 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() { &encrypted_response_data_); core_response_.enc_private_key.length = encrypted_response_data_.rsa_key_length; - ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( - core_response_, core_request_, &serialized_core_message_)); + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_TRUE( + oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( + core_response_, core_request_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); // Stripe the encrypted message. - encrypted_response_.resize(message_size_); + encrypted_response_.resize(message_size); for (size_t i = 0; i < encrypted_response_.size(); i++) { - encrypted_response_[i] = i % 0x100; + encrypted_response_[i] = i & 0xFF; } ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); memcpy(encrypted_response_.data(), serialized_core_message_.data(), @@ -322,19 +317,83 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() { OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); size_t wrapped_key_length = 0; - const OEMCryptoResult sts = OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), nullptr, - &wrapped_key_length); + const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length); if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts; wrapped_rsa_key_.clear(); wrapped_rsa_key_.assign(wrapped_key_length, 0); - return OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), - wrapped_rsa_key_.data(), &wrapped_key_length); + return LoadResponseNoRetry(session, &wrapped_key_length); +} + +#ifdef TEST_OEMCRYPTO_V15 +// If this platform supports v15 functions, then will test with them: +# define OEMCrypto_RewrapDeviceRSAKey_V15 OEMCrypto_RewrapDeviceRSAKey +# define OEMCrypto_RewrapDeviceRSAKey30_V15 OEMCrypto_RewrapDeviceRSAKey30 + +#else +// If this platform does not support v15 functions, we just need to stub these +// out so that the tests compile. +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30_V15( + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey_V15( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} +#endif + +template +const T* ProvisioningRoundTrip::RemapPointer(const T* response_pointer) const { + const uint8_t* original_pointer = + reinterpret_cast(response_pointer); + size_t delta = + original_pointer - reinterpret_cast(&response_data_); + // Base offset should be 0 if this is a v15 message, which is the only time + // this function is called. + size_t base_offset = serialized_core_message_.size(); + const uint8_t* new_pointer = encrypted_response_.data() + delta + base_offset; + return reinterpret_cast(new_pointer); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry( + Session* session, size_t* wrapped_key_length) { + EXPECT_NE(session, nullptr); + if (global_features.api_version >= kCoreMessagesAPI) { + return OEMCrypto_LoadProvisioning( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size(), + wrapped_rsa_key_.data(), wrapped_key_length); + } else if (global_features.provisioning_method == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey_V15( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), RemapPointer(&response_data_.nonce), + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } else { + return OEMCrypto_RewrapDeviceRSAKey30_V15( + session->session_id(), &encrypted_response_data_.nonce, + RemapPointer(response_data_.enc_message_key), + response_data_.enc_message_key_length, + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } } void ProvisioningRoundTrip::VerifyLoadFailed() { @@ -346,10 +405,17 @@ void ProvisioningRoundTrip::VerifyLoadFailed() { void LicenseRoundTrip::VerifyRequestSignature( const vector& data, const vector& generated_signature, size_t core_message_length) { - std::vector subdata(data.begin() + core_message_length, data.end()); + const std::vector subdata(data.begin() + core_message_length, + data.end()); session()->VerifyRSASignature(subdata, generated_signature.data(), generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, core_response_.request_hash); + SHA256(data.data(), core_message_length, request_hash_); + // If the api version was not set by the test, then we record the api version + // from the request. Also, if the api was set to be higher than oemcrypto + // supports, then we lower it. This version will be used in the response. + if (api_version_ == 0) api_version_ = core_request_.api_major_version; + if (api_version_ > global_features.api_version) + api_version_ = global_features.api_version; } void LicenseRoundTrip::FillAndVerifyCoreRequest( @@ -357,12 +423,16 @@ void LicenseRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(global_features.api_version, core_request_.api_version); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + // If we are testing the latest OEMCrypto version, make sure it is built with + // the latest ODK version, too: + if (global_features.api_version == ODK_MAJOR_VERSION) { + EXPECT_EQ(ODK_MINOR_VERSION, core_request_.api_minor_version); + } if (expect_request_has_correct_nonce_) { EXPECT_EQ(session()->nonce(), core_request_.nonce); } EXPECT_EQ(session()->session_id(), core_request_.session_id); - if (api_version_ == 0) api_version_ = core_request_.api_version; } void LicenseRoundTrip::CreateDefaultResponse() { @@ -371,10 +441,12 @@ void LicenseRoundTrip::CreateDefaultResponse() { memset(response_data_.padding, 0, sizeof(response_data_.padding)); EXPECT_EQ(1, GetRandBytes(response_data_.mac_keys, sizeof(response_data_.mac_keys))); - // For backwards compatibility, we use the license duration for each key's - // duration. - uint32_t duration = static_cast( - core_response_.timer_limits.license_duration_seconds); + // For backwards compatibility, we use the largest limit in timer_limits for + // each key's duration. + uint32_t key_duration = static_cast( + std::max({core_response_.timer_limits.rental_duration_seconds, + core_response_.timer_limits.total_playback_duration_seconds, + core_response_.timer_limits.initial_renewal_duration_seconds})); // The key data for an entitlement license is an AES-256 key, otherwise the // default is an AES_128 key. uint32_t default_key_size = @@ -393,7 +465,7 @@ void LicenseRoundTrip::CreateDefaultResponse() { sizeof(response_data_.keys[i].control_iv))); std::string kcVersion = "kc" + std::to_string(api_version_); memcpy(response_data_.keys[i].control.verification, kcVersion.c_str(), 4); - response_data_.keys[i].control.duration = htonl(duration); + response_data_.keys[i].control.duration = htonl(key_duration); response_data_.keys[i].control.nonce = htonl(session_->nonce()); response_data_.keys[i].control.control_bits = htonl(control_); response_data_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; @@ -493,12 +565,20 @@ void LicenseRoundTrip::EncryptAndSignResponse() { if (api_version_ < kCoreMessagesAPI) { serialized_core_message_.resize(0); } else { + std::string request_hash_string( + reinterpret_cast(request_hash_), sizeof(request_hash_)); ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse( - core_response_, core_request_, &serialized_core_message_)); + core_response_, core_request_, request_hash_string, + &serialized_core_message_)); } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); // Stripe the encrypted message. - encrypted_response_.resize(message_size_); + encrypted_response_.resize(message_size); for (size_t i = 0; i < encrypted_response_.size(); i++) { encrypted_response_[i] = i % 0x100; } @@ -522,15 +602,19 @@ void LicenseRoundTrip::EncryptAndSignResponse() { OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); // Some tests adjust the offset to be beyond the length of the message. Here, - // we create a duplicate of the message buffer so that these offsets do not - // point to garbage data. The goal is to make sure OEMCrypto is verifying that - // the offset points outside of the message -- we don't want OEMCrypto to look - // at what offset points to and return an error if the data is garbage. Since - // the memory after the message buffer is an exact copy of the message, we can - // increment the offset by the message size and get valid data. + // we create a duplicate of the main message buffer so that these offsets do + // not point to garbage data. The goal is to make sure OEMCrypto is verifying + // that the offset points outside of the message -- we don't want OEMCrypto to + // look at what offset points to and return an error if the data is + // garbage. Since the memory after the message buffer is an exact copy of the + // message, we can increment the offset by the message size and get valid + // data. std::vector double_message = encrypted_response_; - double_message.insert(double_message.end(), encrypted_response_.begin(), - encrypted_response_.end()); + double_message.insert( + double_message.end(), + reinterpret_cast(&encrypted_response_data_), + reinterpret_cast(&encrypted_response_data_) + + sizeof(encrypted_response_data_)); OEMCryptoResult result; if (api_version_ < kCoreMessagesAPI) { result = OEMCrypto_LoadKeys( @@ -584,11 +668,9 @@ void LicenseRoundTrip::VerifyTestKeys() { if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing + // Note: we do not assume that duration is stored with each key after v16. + // control bits stored in network byte order. For printing // we change to host byte order. - ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.duration), - htonl_fnc(block.duration)) - << "For key " << i; ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.control_bits), htonl_fnc(block.control_bits)) << "For key " << i; @@ -758,8 +840,8 @@ void RenewalRoundTrip::FillAndVerifyCoreRequest( EXPECT_TRUE( oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( core_message_string, &core_request_)); - EXPECT_EQ(license_messages_->core_request().api_version, - core_request_.api_version); + EXPECT_EQ(license_messages_->core_request().api_major_version, + core_request_.api_major_version); if (!is_release_) { // For a license release, we do not expect the nonce to be correct. That // is because a release might be sent without loading the license first. @@ -786,12 +868,17 @@ void RenewalRoundTrip::CreateDefaultResponse() { constexpr size_t index = 0; response_data_.keys[index].key_id_length = 0; response_data_.keys[index].key_id[0] = '\0'; - std::string kcVersion = "kc" + std::to_string(core_request_.api_version); + std::string kcVersion = + "kc" + std::to_string(core_request_.api_major_version); + if (global_features.api_version < kCoreMessagesAPI) { + // For v15 or earlier devices, we use the api of the device. + kcVersion = "kc" + std::to_string(global_features.api_version); + } memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), 4); const uint32_t duration = static_cast( license_messages_->core_response() - .timer_limits.renewal_playback_duration_seconds); + .timer_limits.initial_renewal_duration_seconds); response_data_.keys[index].control.duration = htonl(duration); response_data_.keys[index].control.nonce = htonl(nonce); response_data_.keys[index].control.control_bits = htonl(control); @@ -801,12 +888,6 @@ void RenewalRoundTrip::CreateDefaultResponse() { void RenewalRoundTrip::EncryptAndSignResponse() { // Renewal messages are not encrypted. encrypted_response_data_ = response_data_; - - // Stripe the encrypted message. - encrypted_response_.resize(message_size_); - for (size_t i = 0; i < encrypted_response_.size(); i++) { - encrypted_response_[i] = i % 0x100; - } // Either create a KeyRefreshObject for a call to RefreshKeys or a core // response for a call to LoadRenewal. if (license_messages_->api_version() < kCoreMessagesAPI) { @@ -818,7 +899,17 @@ void RenewalRoundTrip::EncryptAndSignResponse() { serialized_core_message_.resize(0); } else { ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse( - core_request_, &serialized_core_message_)); + core_request_, renewal_duration_seconds_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; } // Concatenate the core message and the response. ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index 2e8a58e..e3fa696 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -133,13 +133,6 @@ uint32_t htonl_fnc(uint32_t x); // Prints error string from BoringSSL void dump_boringssl_error(); -// Given a message and field, returns an OEMCrypto_Substring with the field's -// offset into the message and its length. If |set_zero| is true, both the -// offset and length will be zero. -OEMCrypto_Substring GetSubstring(const std::string& message = "", - const std::string& field = "", - bool set_zero = false); - class Session; // The prototype of the OEMCrypto function to prepare and sign a request. typedef OEMCryptoResult (*PrepAndSignRequest_t)( @@ -159,7 +152,7 @@ class RoundTrip { core_response_(), response_data_(), encrypted_response_data_(), - message_size_(sizeof(ResponseData) + kMaxCoreMessage){}; + required_message_size_(0) {} virtual ~RoundTrip() {} // Have OEMCrypto sign a request message and then verify the signature and the @@ -190,9 +183,7 @@ class RoundTrip { } // Set the size of the buffer used the encrypted license. - void set_message_size(size_t size) { message_size_ = size; } - // The size of the encrypted message. - size_t message_size() { return message_size_; } + void set_message_size(size_t size) { required_message_size_ = size; } std::vector& response_signature() { return response_signature_; } const std::string& serialized_core_message() const { return serialized_core_message_; @@ -218,7 +209,9 @@ class RoundTrip { CoreRequest core_request_; CoreResponse core_response_; ResponseData response_data_, encrypted_response_data_; - size_t message_size_; // How much of the padded message to use. + // Message buffers will be at least this big. Tests for loading and signing + // messages will increase all buffers to this size. + size_t required_message_size_; std::vector response_signature_; std::string serialized_core_message_; std::vector encrypted_response_; @@ -257,6 +250,15 @@ class ProvisioningRoundTrip // Verify the values of the core response. virtual void FillAndVerifyCoreRequest( const std::string& core_message_string) override; + // Load the response, without the retry. Called by LoadResponse. + OEMCryptoResult LoadResponseNoRetry(Session* session, + size_t* wrapped_key_length); + // This takes a pointer in the response_data_ and remaps it to the same + // pointer within the encrypted message. This is used for backwards + // compatibliity testing, so that a v15 oemcrypto will accept range checks. + template + const T* RemapPointer(const T* response_pointer) const; + uint32_t allowed_schemes_; Encryptor encryptor_; // The message key used for Prov 3.0. @@ -317,7 +319,7 @@ class LicenseRoundTrip } // Change the hash of the core request. This should cause the response to be // rejected. - void BreakRequestHash() { core_response_.request_hash[3] ^= 42; } + void BreakRequestHash() { request_hash_[3] ^= 42; } // Set the API version for the license itself. This will be used in // CreateDefaultResponse. void set_api_version(uint32_t api_version) { api_version_ = api_version; } @@ -363,6 +365,7 @@ class LicenseRoundTrip // Whether this is a content license or an entitlement license. Used in // CreateDefaultResponse. OEMCrypto_LicenseType license_type_; + uint8_t request_hash_[ODK_SHA256_HASH_SIZE]; }; class RenewalRoundTrip @@ -377,11 +380,20 @@ class RenewalRoundTrip : RoundTrip(license_messages->session()), license_messages_(license_messages), refresh_object_(), + renewal_duration_seconds_( + license_messages->core_response() + .timer_limits.initial_renewal_duration_seconds), is_release_(false) {} void CreateDefaultResponse() override; void EncryptAndSignResponse() override; OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; + uint64_t renewal_duration_seconds() const { + return renewal_duration_seconds_; + } + void set_renewal_duration_seconds(uint64_t renewal_duration_seconds) { + renewal_duration_seconds_ = renewal_duration_seconds; + } void set_is_release(bool is_release) { is_release_ = is_release; } protected: @@ -393,6 +405,7 @@ class RenewalRoundTrip const std::string& core_message_string) override; LicenseRoundTrip* license_messages_; OEMCrypto_KeyRefreshObject refresh_object_; + uint64_t renewal_duration_seconds_; bool is_release_; // If this is a license release, and not a real renewal. }; diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 51b717f..9e2bc7e 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -71,7 +71,6 @@ void SessionUtil::EnsureTestKeys() { // This makes sure that the derived keys (encryption key and two mac keys) // are installed in OEMCrypto and in the test session. void SessionUtil::InstallTestRSAKey(Session* s) { - ASSERT_TRUE(global_features.uses_certificate); if (global_features.loads_certificate) { if (wrapped_rsa_key_.size() == 0) { // If we don't have a wrapped key yet, create one. diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 27b023b..066e831 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -214,7 +214,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { } // The resource rating is a number from 1 to 4. The first three levels were -// initiallly defined in API 15 and they were expaneded in API 16. +// initially defined in API 15 and they were expanded in API 16. TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) { ASSERT_GE(OEMCrypto_ResourceRatingTier(), 1u); ASSERT_LE(OEMCrypto_ResourceRatingTier(), 4u); @@ -298,7 +298,7 @@ TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { TEST_F(OEMCryptoClientTest, CheckUsageTableSizeAPI16) { const size_t maximum = OEMCrypto_MaximumUsageTableHeaderSize(); printf(" Max Usage Table Size: %zu.\n", maximum); - // A maximum of 0 means the table is constrained my dynamic memory allocation. + // A maximum of 0 means the table is constrained by dynamic memory allocation. if (maximum > 0) ASSERT_GE(maximum, RequiredUsageSize()); } @@ -875,7 +875,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonce) { ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// Verify that a second license may be not be loaded in a session. +// Verify that a second license may not be loaded in a session. TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); license_messages_.set_control(0); @@ -886,8 +886,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); } -// Verify that a second license may be not be loaded in a session. -TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwice) { +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -985,7 +985,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().enc_mac_keys.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -997,7 +997,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().enc_mac_keys_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1009,7 +1009,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_id) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[0].key_id.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1021,7 +1021,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[1].key_data.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1033,7 +1033,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[1].key_data_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1045,7 +1045,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[2].key_control.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1057,7 +1057,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control_iv) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().key_array[2].key_control_iv.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -1071,7 +1071,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { // See the comment in LicenseRoundTrip::LoadResponse for why we increment by // the message size. license_messages_.core_response().pst.offset += - license_messages_.message_size(); + sizeof(license_messages_.response_data()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); // If we have a pst, then we need a usage entry. ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); @@ -1081,7 +1081,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { //---------------------------------------------------------------------------// // The IV should not be identical to the data right before the encrypted mac -// keys. +// keys. This requirement was added in 15.2, so it frequently fails on +// production devices. TEST_F(OEMCryptoLicenseTestAPI15, LoadKeyWithSuspiciousIV) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); @@ -1272,7 +1273,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadLicense( session_.session_id(), unaligned_message, - license_messages_.message_size(), + license_messages_.encrypted_response_buffer().size(), license_messages_.serialized_core_message().size(), license_messages_.response_signature().data(), license_messages_.response_signature().size())); @@ -1293,6 +1294,10 @@ TEST_P(OEMCryptoLicenseTest, LoadLicenseAgainFailureAPI16) { TEST_P(OEMCryptoLicenseTestRangeAPI, LoadKeys) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + // Re-set the API version. The function VerifyRequestSignature sets the api to + // be a sane value. But in this test, we want to verify an unsupported version + // is rejected. + license_messages_.set_api_version(license_api_version_); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); @@ -1626,6 +1631,7 @@ TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + EXPECT_EQ(global_features.api_version, license_messages.api_version()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // Reject any future patch levels. @@ -1716,22 +1722,16 @@ class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { // playback right away. All times are in seconds since the license was // signed. // Soft expiry false means timers are strictly enforce. - timer_limits_.soft_expiry = false; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = false; // Playback may begin immediately. timer_limits_.earliest_playback_start_seconds = 0; // First playback may be within the first two seconds. - timer_limits_.latest_playback_start_seconds = kDuration; + timer_limits_.rental_duration_seconds = kDuration; // Once started, playback may last two seconds without a renewal. - timer_limits_.initial_playback_duration_seconds = kDuration; - // Playback may continue for four seconds after a renewal is loaded. - timer_limits_.renewal_playback_duration_seconds = 2 * kDuration; - if (license_api_version_ < kCoreMessagesAPI) { - // For legacy licenses, only license duration is enforced. - timer_limits_.license_duration_seconds = kDuration; - } else { - // Total playback is not limited. - timer_limits_.license_duration_seconds = 0; - } + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; } void LoadLicense() { @@ -1902,8 +1902,8 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { TEST_P(OEMCryptoLicenseTest, Decrypt) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); @@ -1914,7 +1914,8 @@ TEST_P(OEMCryptoLicenseTest, Decrypt) { TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = 0; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = 0; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); @@ -2179,8 +2180,8 @@ class OEMCryptoSessionTestsDecryptTests ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.initial_renewal_duration_seconds = kDuration; memcpy(license_messages_.response_data().keys[0].key_data, key_, sizeof(key_)); license_messages_.response_data().keys[0].cipher_mode = cipher_mode_; @@ -2424,7 +2425,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) { // // 1) The maximum total sample size // 2) The maximum number of subsamples multiplied by the maximum subsample size -TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSample) { +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { const size_t max_sample_size = GetResourceValue(kMaxSampleSize); const size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize); const size_t max_num_subsamples = GetResourceValue(kMaxNumberSubsamples); @@ -2589,8 +2590,8 @@ TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) { TEST_P(OEMCryptoLicenseTest, KeyDuration) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); @@ -2669,14 +2670,14 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); provisioning_messages.core_response().enc_private_key.offset = - provisioning_messages.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2684,14 +2685,14 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); provisioning_messages.core_response().enc_private_key_iv.offset = - provisioning_messages.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2699,7 +2700,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2708,7 +2709,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { // If the offset is before the end, but the offset+length is bigger, then // the message should be rejected. provisioning_messages.core_response().enc_private_key.offset = - provisioning_messages.message_size() - 5; + provisioning_messages.encrypted_response_buffer().size() - 5; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2716,7 +2717,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2725,7 +2726,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { // If the offset is before the end, but the offset+length is bigger, then // the message should be rejected. provisioning_messages.core_response().enc_private_key_iv.offset = - provisioning_messages.message_size() - 5; + provisioning_messages.encrypted_response_buffer().size() - 5; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2733,7 +2734,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4) { // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -2742,7 +2743,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30) { // If the offset is before the end, but the offset+length is bigger, then // the message should be rejected. provisioning_messages.core_response().encrypted_message_key.offset = - provisioning_messages.message_size() + 1; + provisioning_messages.encrypted_response_buffer().size() + 1; ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); provisioning_messages.VerifyLoadFailed(); @@ -2764,7 +2765,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignatureKeyboxTest) { } // Test that RewrapDeviceRSAKey verifies the nonce is current. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -3199,7 +3200,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, DisallowForbiddenPaddingAPI09) { // The alternate padding is only required for cast receivers, but if a device // does load an alternate certificate, it should NOT use it for generating // a license request signature. -TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { +TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1_API16) { // Try to load an RSA key with alternative padding schemes. This signing // scheme is used by cast receivers. LoadWithAllowedSchemes(kSign_PKCS1_Block1, false); @@ -4587,8 +4588,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { // Test Generic_Encrypt when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); vector expected_encrypted; @@ -4622,8 +4623,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { // Test Generic_Decrypt when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4656,8 +4657,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { // Test Generic_Sign when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4692,8 +4693,8 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { // Test Generic_Verify when the key duration has expired. TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) { - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); @@ -4732,8 +4733,8 @@ class OEMCryptoGenericCryptoKeyIdLengthTest OEMCryptoGenericCryptoTest::SetUp(); license_messages_.set_num_keys(5); license_messages_.set_control(wvoec::kControlAllowDecrypt); - license_messages_.core_response().timer_limits.license_duration_seconds = - kDuration; + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); SetUniformKeyIdLength(16); // Start with all key ids being 16 bytes. // But, we are testing that the key ids do not have to have the same length. @@ -5127,7 +5128,7 @@ TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { // Sessions should have at most one entry at a time. This tests different // orderings of CreateNewUsageEntry and LoadUsageEntry calls. -TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntries) { +TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { // Entry Count: we start each test with an empty header. uint32_t usage_entry_number; LicenseWithUsageEntry entry; diff --git a/util/test/test_clock.cpp b/util/test/test_clock.cpp index 86e9017..151bb28 100644 --- a/util/test/test_clock.cpp +++ b/util/test/test_clock.cpp @@ -1,4 +1,4 @@ -// Copyright 2013 Google Inc. All Rights Reserved. +// Copyright 2019 Google Inc. All Rights Reserved. // // Clock - A fake clock just for running tests. diff --git a/util/test/test_sleep.h b/util/test/test_sleep.h index 4f2dcf5..832f1ad 100644 --- a/util/test/test_sleep.h +++ b/util/test/test_sleep.h @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. //