From 44b58174902740cf533430b7f2168bcbe86e62e2 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Sep 2020 23:05:09 -0400 Subject: [PATCH 1/3] asymmetric: add tests for key restrictions --- src/keytypes/asymmetric.rs | 63 ++++++++++++++++++++++++++++++++++++++ src/keytypes/mod.rs | 1 + 2 files changed, 64 insertions(+) diff --git a/src/keytypes/asymmetric.rs b/src/keytypes/asymmetric.rs index 1af2653..ab6dc70 100644 --- a/src/keytypes/asymmetric.rs +++ b/src/keytypes/asymmetric.rs @@ -117,3 +117,66 @@ impl KeyRestriction for AsymmetricRestriction { impl RestrictableKeyType for Asymmetric { type Restriction = AsymmetricRestriction; } + +#[cfg(test)] +mod tests { + use crate::keytypes::{AsymmetricRestriction, User}; + use crate::tests::utils; + use crate::KeyRestriction; + + #[test] + fn test_restriction_str() { + let mut keyring = utils::new_test_keyring(); + let description = &b"description"[..]; + let key = keyring + .add_key::("test_restriction_str", description) + .unwrap(); + + let cases = [ + ( + AsymmetricRestriction::BuiltinTrusted, + "builtin_trusted".into(), + ), + ( + AsymmetricRestriction::BuiltinAndSecondaryTrusted, + "builtin_and_secondary_trusted".into(), + ), + ( + AsymmetricRestriction::Key { + key: key.clone(), + chained: false, + }, + format!("key_or_keyring:{}", key.serial()), + ), + ( + AsymmetricRestriction::Key { + key: key.clone(), + chained: true, + }, + format!("key_or_keyring:{}:chain", key.serial()), + ), + ( + AsymmetricRestriction::Keyring { + keyring: keyring.clone(), + chained: false, + }, + format!("key_or_keyring:{}", keyring.serial()), + ), + ( + AsymmetricRestriction::Keyring { + keyring: keyring.clone(), + chained: true, + }, + format!("key_or_keyring:{}:chain", keyring.serial()), + ), + ( + AsymmetricRestriction::Chained, + "key_or_keyring:0:chain".into(), + ), + ]; + + for (restriction, expected) in cases.iter() { + assert_eq!(restriction.restriction(), expected.as_ref()); + } + } +} diff --git a/src/keytypes/mod.rs b/src/keytypes/mod.rs index ae74301..e1b0773 100644 --- a/src/keytypes/mod.rs +++ b/src/keytypes/mod.rs @@ -33,6 +33,7 @@ use std::fmt; pub mod asymmetric; pub use self::asymmetric::Asymmetric; +pub use self::asymmetric::AsymmetricRestriction; pub mod big_key; pub use self::big_key::BigKey; From b9a5bfb3311938634bd77e37ff92d11bd43e2867 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Sep 2020 23:23:18 -0400 Subject: [PATCH 2/3] keytypes/asymmetric: add code to generate test certificates --- src/keytypes/data/ca/.gitignore | 7 +++ src/keytypes/data/ca/Makefile | 37 ++++++++++++++++ src/keytypes/data/ca/openssl.cnf | 75 ++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/keytypes/data/ca/.gitignore create mode 100644 src/keytypes/data/ca/Makefile create mode 100644 src/keytypes/data/ca/openssl.cnf diff --git a/src/keytypes/data/ca/.gitignore b/src/keytypes/data/ca/.gitignore new file mode 100644 index 0000000..97b6d30 --- /dev/null +++ b/src/keytypes/data/ca/.gitignore @@ -0,0 +1,7 @@ +*.key +*.crt +*.csr +*.pem + +certindex* +serial* diff --git a/src/keytypes/data/ca/Makefile b/src/keytypes/data/ca/Makefile new file mode 100644 index 0000000..f75528f --- /dev/null +++ b/src/keytypes/data/ca/Makefile @@ -0,0 +1,37 @@ +PROJECT := rust-keyutils +OPENSSL_CONF := openssl.cnf + +MAIN_CERT_NAME := ca-1 +MAIN_ROOT_CERT := $(MAIN_CERT_NAME).root.crt +ROOT_CERTS := $(MAIN_ROOT_CERT) ca-2.root.crt +INTERMEDIATE_CERTS := ca.intermediate.crt +CA_CERTS := $(ROOT_CERTS) $(INTERMEDIATE_CERTS) +SERVER_CERTS := intermediate.term.crt $(MAIN_CERT_NAME).term.crt self.term.crt + +SIGNING_BITS := certindex serial + +all: $(foreach cert,$(CA_CERTS) $(SERVER_CERTS),$(cert).der) + +certindex: + touch certindex + +serial: + echo 1000 > serial + +%.root.crt %.key: $(OPENSSL_CONF) + openssl req -config $< -new -subj "/CN=$(PROJECT) CA $*" -x509 -passout pass:$(PROJECT) -newkey rsa:4096 -keyout $*.key -out $@ -extensions v3_root_ca + +%.intermediate.crt %.key: $(OPENSSL_CONF) $(SIGNING_BITS) $(MAIN_ROOT_CERT) + openssl req -config $< -new -subj "/CN=$(PROJECT) Intermediate CA" -passout pass:$(PROJECT) -newkey rsa:4096 -keyout $*.key -out $@.csr -extensions v3_intermediate_ca + openssl ca -config $< -notext -passin pass:$(PROJECT) -batch -in $@.csr -out $@ + +self.term.crt %.key: $(OPENSSL_CONF) $(SIGNING_BITS) + openssl req -config $< -new -subj "/CN=$(PROJECT) self-signed Certificate" -passout pass:$(PROJECT) -newkey rsa:4096 -keyout $*.term.key -out $@.csr -extensions v3_server_cert + openssl x509 -req -signkey $*.term.key -passin pass:$(PROJECT) -days 3650 -in $@.csr -out $@ + +%.term.crt %.key: $(OPENSSL_CONF) $(SIGNING_BITS) $(CA_CERTS) + openssl req -config $< -new -subj "/CN=$(PROJECT) $*-signed Certificate" -passout pass:$(PROJECT) -newkey rsa:4096 -keyout $*.term.key -out $@.csr -extensions v3_server_cert + openssl ca -config $< -notext -passin pass:$(PROJECT) -batch -in $@.csr -out $@ -name $* + +%.crt.der: %.crt + openssl x509 -outform der -in $< -out $@ diff --git a/src/keytypes/data/ca/openssl.cnf b/src/keytypes/data/ca/openssl.cnf new file mode 100644 index 0000000..e1bd80e --- /dev/null +++ b/src/keytypes/data/ca/openssl.cnf @@ -0,0 +1,75 @@ +[ca] +default_ca = ca-1 + +[ca_policy] + +[ca-1] +dir = ./ +new_certs_dir = $dir +certificate = $dir/ca-1.root.crt +private_key = $dir/ca-1.key + +database = $dir/certindex +serial = $dir/serial +default_days = 3650 +default_md = sha512 +policy = ca_policy + +[intermediate] +dir = ./ +new_certs_dir = $dir +certificate = $dir/ca.intermediate.crt +private_key = $dir/ca.key + +database = $dir/certindex +serial = $dir/serial +default_days = 3650 +default_md = sha512 +policy = ca_policy + +[self] +dir = ./ +new_certs_dir = $dir +#certificate = $dir/ca.intermediate.crt +private_key = $dir/self.term.key + +database = $dir/certindex +serial = $dir/serial +default_days = 3650 +default_md = sha512 +policy = ca_policy + +[req] +distinguished_name = req_distinguished_name +prompt = no + +[req_distinguished_name] +countryName = US +stateOrProvinceName = New York +localityName = Nowhere +organizationName = Github +emailAddress = mathstuf+keyutils@gmail.com + +[v3_root_ca] +# Extensions for a typical CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[v3_intermediate_ca] +# Extensions for a typical intermediate CA (`man x509v3_config`). +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign + +[v3_server_cert] +# Extensions for server certificates (`man x509v3_config`). +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth From 6ff53f5706480495807b405fd8ef98e1878f0432 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 26 Sep 2020 23:24:27 -0400 Subject: [PATCH 3/3] keytypes/asymmetric: add tests for chained certificates --- src/keytypes/asymmetric.rs | 65 +++++++++++++++++- src/keytypes/data/ca/ca-1.root.crt.der | Bin 0 -> 1333 bytes src/keytypes/data/ca/ca-1.term.crt.der | Bin 0 -> 1177 bytes src/keytypes/data/ca/ca-2.root.crt.der | Bin 0 -> 1333 bytes src/keytypes/data/ca/ca.intermediate.crt.der | Bin 0 -> 1177 bytes .../data/ca/intermediate.term.crt.der | Bin 0 -> 1145 bytes src/keytypes/data/ca/self.term.crt.der | Bin 0 -> 1259 bytes 7 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/keytypes/data/ca/ca-1.root.crt.der create mode 100644 src/keytypes/data/ca/ca-1.term.crt.der create mode 100644 src/keytypes/data/ca/ca-2.root.crt.der create mode 100644 src/keytypes/data/ca/ca.intermediate.crt.der create mode 100644 src/keytypes/data/ca/intermediate.term.crt.der create mode 100644 src/keytypes/data/ca/self.term.crt.der diff --git a/src/keytypes/asymmetric.rs b/src/keytypes/asymmetric.rs index ab6dc70..cc39787 100644 --- a/src/keytypes/asymmetric.rs +++ b/src/keytypes/asymmetric.rs @@ -120,7 +120,7 @@ impl RestrictableKeyType for Asymmetric { #[cfg(test)] mod tests { - use crate::keytypes::{AsymmetricRestriction, User}; + use crate::keytypes::{Asymmetric, AsymmetricRestriction, User}; use crate::tests::utils; use crate::KeyRestriction; @@ -179,4 +179,67 @@ mod tests { assert_eq!(restriction.restriction(), expected.as_ref()); } } + + #[test] + fn test_restrict_keyring_chain() { + let mut keyring = utils::new_test_keyring(); + + // Create and populate a keyring for root certificates. + let mut root = keyring.add_keyring("root-certs").unwrap(); + let root1_certificate = &include_bytes!("data/ca/ca-1.root.crt.der")[..]; + let root2_certificate = &include_bytes!("data/ca/ca-2.root.crt.der")[..]; + root.add_key::("root1", root1_certificate) + .unwrap(); + root.add_key::("root2", root2_certificate) + .unwrap(); + + // Create a keyring to restrict. + let mut chain = keyring.add_keyring("chain").unwrap(); + let restriction = AsymmetricRestriction::Keyring { + keyring: root, + chained: true, + }; + chain + .restrict_by_type::(restriction) + .unwrap(); + + // Add certificates in order. + let intermediate_a = &include_bytes!("data/ca/ca.intermediate.crt.der")[..]; + chain + .add_key::("intermediate_a", intermediate_a) + .unwrap(); + let intermediate_b = &include_bytes!("data/ca/intermediate.term.crt.der")[..]; + chain + .add_key::("intermediate_b", intermediate_b) + .unwrap(); + let terminal = &include_bytes!("data/ca/ca-1.term.crt.der")[..]; + chain + .add_key::("terminal", terminal) + .unwrap(); + } + + #[test] + fn test_restrict_keyring_fail() { + let mut keyring = utils::new_test_keyring(); + + // Create and populate a keyring for root certificates. + let root = keyring.add_keyring("root-certs").unwrap(); + + // Create a keyring to restrict. + let mut chain = keyring.add_keyring("chain").unwrap(); + let restriction = AsymmetricRestriction::Keyring { + keyring: root, + chained: true, + }; + chain + .restrict_by_type::(restriction) + .unwrap(); + + // Add certificates in order. + let terminal = &include_bytes!("data/ca/self.term.crt.der")[..]; + let err = chain + .add_key::("self", terminal) + .unwrap_err(); + assert_eq!(err, errno::Errno(libc::ENOKEY)); + } } diff --git a/src/keytypes/data/ca/ca-1.root.crt.der b/src/keytypes/data/ca/ca-1.root.crt.der new file mode 100644 index 0000000000000000000000000000000000000000..6150a0aecc4a147afb76f19cee9255a6fa378d04 GIT binary patch literal 1333 zcmXqLVl^~qVwPOM%*4pVBob0p!aKvWY1Ok6e_v~|^!LR4eNkz^%f_kI=F#?@mywa1 zmBB#4P|iSxjX9KsnMbs!w75h!JGHX3Br~U2!P!wEIZ@ZpKu(<3$iTqT$lSo#*wWA- z3dA)ufOCm3p@~Te*;+Z>F=wU)$CX2@XXzBdoU|;)#REp!Uh}-H{Sj(DUx_}B=JbkOhb98 zq=H}tkG`q9IvehuPCdMC(PGnXLyokbF8djZhnbel;AzQX7PokD zIkmtAn_^bVJ!`obc=FKVb=8fJRvf!w8qmF9kz-EC0Y(n@KE}kYsi%MKQkuD4@>+?* z2L_iX*Ydm;1QdsJ9!^qm-7;6gm2tvb){NTi^^5Nde3|wzaK0FqRV^F41H0q5clsR# zdrDSMm0F|f{-;w=aYa;b*=wWC_VsHt4=rr^^V?VB>VFBH zA1SF?p5W;CWWS)8gEIH|pNC~$Gn>uh+q^YT%=qcwOD1d2&eyzna$4i0yW5zWPZah( zDdU^XdnwriL!hwVk{zePITt2X~=%?K=ANC>x@&;_u4Jm4CF!5$}AEFVhz|8 z@PiZxGcx{XVKra|QU-h=0e+AG3ouW#Atys%>IWu6Mg~VC4^gKhI;$(Da=ut-lcsyH z?T~pam+B#=Z4G<@7dW0U7r3zY2%cs#%I#b8@%{9OP|3QNmpePcUl>05%G0^YwRPo0 zF6JXMZRhz*t`@P2OL;fbS5QX#yPw*FjI)Q{|Ly*u=G5>)Lumc1Z?Dx`r4Mh}?VNl6 zyMfuX@*iImPs`7hE2&*{_6z&V23D=pNfN96O6HVH+=w^z*dMT?>~7z1lHQ87 zIa}^ana%no>veiv_uDP6R~<6^U7I@H{08%t`x97r!VlMm+8umVXkP04%IM{HtEM-M z-qXwrzbsW*x#8XEQir}kCmk-EdAsL!XGq_cTi+tFIz2A%)cg7~{^#?>&sSXCF2Ci# zR9BUr`*F5^J700yKl}G5e)5hDrXie*On)`G=+a>?kcN${IZ7@H*P35&-|rU=@?YmWxDkY zw|qdbrn+r}&?Sc!E}yp@XV$-(s&#Say3@Ijsx$d2FCADE)n{K_@9omU_&VI|r~Rkn zGncddPZrZK`6#FKZff=$uIouRnI0WHx${U}Z1(rvxmlgnO|d@%Jf+rm{E4ZL>5iFp G!x;eb%2Y=H literal 0 HcmV?d00001 diff --git a/src/keytypes/data/ca/ca-1.term.crt.der b/src/keytypes/data/ca/ca-1.term.crt.der new file mode 100644 index 0000000000000000000000000000000000000000..31f25ef5c7c1226ebc90f8bff71e8dd5e9fad24f GIT binary patch literal 1177 zcmV;K1ZMj%f&`T?f&zU40uTZ)4F(A+hDe6@4FLfS1pqK0F&;1+1_M&LNQU&O+pOV-OO7 z@$k6sBgUD=7yNurMn)iW7z( z*^eK`7C-H)&?#4{OF0|2wIg_?dEuaQx60e^3@m4YT~av02>6(If8-;WQyQ{Z-KxDC znIX(2j-l=MRMjh{+s4`Qa-vY_xmwXIAb+*zY}@}-Gv-pZ*TcNeGWNw?DjeNQZKb43 zE)$RPP^2=q${)>>St%B=a(I?etV~e3gTx?Ab}8JGzz%^z;Dmc(Dza)*T)%`pOU`dFEI+n9`J3oYF#hJ-Z3c2*BHk(sni?FkCcC#O8u>!0RWzj z4Be*?*|?yST5uGSv(}SY5F*Z@ZXNTs7eyoG5a~jEUM{T0bY+y|hq`^YEZq?U^gPbC z5q)kDXA>KCl}{nf3|9O2q83loMcMqY{|DHL^v$Rlv?|8YJ3wnqmqLG0L`~e2?WI0d zwo;~ zE>K=iK>@4t9r1Q(RIb)`A+4p8ptJ7+0|5X5FbxI?Duzgg_YDC74Fv!Lf&u{m;F#u2 z7C{7B@~U}4P+(Pu$2xf4enW|Y=VrH*1EvZ+t`JtU;EY@XRf?NT0$}IjM|KfAD*EKy z#7tl*F~=Smf@NIphZ%YYlM`~2-rg@3$kCV?_E*hhr)y-X+jj(z4*E-rz^C9n9|N&( zC8U-gL>!wiRGs);F3nc9QmD_Az~EE6=5E8CqTNGTXPh;|!k|7wf&VS!yZEAgfAmyu zq>yUv&qV<0XP9T#@%)-9+$ZSLuD56e!((lgmqNTV^Y=Pmq|JZwOUF{xD~=VP$=~5s zX#oRNf77s;ghsREv1{W#ZzC?CcY#xJ_lUOY=Q6Yi^C zeq*H9mr>yU7d3?r-v1r^_RITCS@h!DU(`fxR}YZk?}Y_A)*qmQ0O0%kFuT|saID44 zM5IY}a69vv7n?>3UFoN(B?stwJlEo~ec4}p5NoG#5g0rJSa-y!0ft)z6k%57wk-34fv8ACw z6o_kR0Ot~6LKBk`vbBt?49rbT{0s(7Ok7M&OpFZkHRBVrt)7TAmuzmkbhRw}#=BVu zpV&qlty$*(^90ALk10!Sei|;BA+E%9LYwoI_Wp$%PfA|tm!B*uut4kg!WBh*Ctm4u z-s8GaD`{a;`R%t|%ags*_gCJ{Fq-c8d*y-yveK-q@=J{N-hX8x7_4t+Bz0$XWa=;b zo=<<+S5C3{n=o~o{@IE5o^6zHd&iU6wsco@Nf=)(-!VSN_g5-I8lU&3%zx_0l(7Fo zy7znCEa#n(|G%<){{87m8q2qK5z$5_!~eT(NKOt7`*1E>Bw^E+^rDs8k6KQBf7E$5 zJoET27Q>eE8vEB@TQn!R#f$Vws+3`brbR`_@+nJwv{gk?tGiKc3+_U z?EHmZPF?GotCr^7X3rNhonbM9&*Nn#mqAdK{v2C%u7bhDe8pr}uqAVYa7>kIS<#VUwfj_Qwt}J-U*WzCz`7`akfjmfBnMJ}ttO2_M zevkrTM#ldvtOm?L%770fzz-5&0p^J|pO-RUH}{hhtyioIQrptQ`CgfQXI{bTjoJmXqxf&fhj8BJmE1V3q&0Cn`?bc@ zpZ^~EizcNX^FaR)u z0wORC1_>&LNQUcH>G(>wKWimAyB}ou&+NJMSe6BYYkBW!kk?e!5fp6(oL zsURpp0=>I(&9ln+2>V*QJ{4bd8DdZsLjwdsV?A9)QVVC0gAXS? zC&VrKwx0JuZ$(fCv3|nr6~e~QMgOQVLE9|})pN?(l&LnP{)sHqc{<_8BQyv+J}PcJ zlK#>00OXT+h9rEm9;{9Oj`_^#p6mVh*o?O^2qXrm7HC+2qL1>PgdSNagl1V9 zvOw$Tipt7IR9%dsf6`bqQ;ma*_rta{Cge@{toNQ=MKn4Els#JF7!^alo<-y{{6?D= z7yf+bse~z4Y&h5+FUPPl{s6nWTV}MNdd^tN^*Qn<=x(!G9CgPt#-kGoq+-6I31fG` zkgF^^)Pjja^A$J6h_Zep@VyqGQ8^Q#@C(y>MS}wBQgKBy84luDI6ZdPS(i^yq<7`L z^?UZ#T237AuS?nysB$FmzLadH-sMjM0|5X5FbxI?Duzgg_YDC74Fv!Lf&u{m3!6(D zm-CB0tJ`YqAgkI%k?jlF8w+01bCs_xo@=sQ<)V*@F?W0* zkl_xNReg-i3lG&Fci9AFB0K169dvX#i+0F^P^wd8b99D0x`4Qpp%-RqbPF}Xv+xu_ zc({=s$?tU(I0QA`4x&ZG3ninWrntiAK7L!~sKm=LLYF{t5==0K5sLRVHyTWBNIC%$`iUpr#0YWhCDh=@1DO zm!H(_w#8F6DwUZ_ZVnbpZ-dmJL-F>Ndf_z5QVu;?XX~38jE&ZMQQ^tUP++E{ds$fr zs|z)HLX|lf!pF-y;PBCEuMX zz(l?PH!PVwnNFW@S3Q)e1O5HvmXkeOM@FTN0OYbR$NzOlU{qiZV2t~6lU{tK8`uiP rx--zbC|^s(bPtM?Q1h=%Zd&OYhNTPP(&HP2PJTl<0{;Basm3u7D%Tkz literal 0 HcmV?d00001 diff --git a/src/keytypes/data/ca/intermediate.term.crt.der b/src/keytypes/data/ca/intermediate.term.crt.der new file mode 100644 index 0000000000000000000000000000000000000000..5e19b2e7220865edb0126218665fcef4fda41299 GIT binary patch literal 1145 zcmV-<1cv)Cf&_Iif&yIv0uTW(4F(A+hDe6@4FLfS1pqJrFdi2TGB7YXGB+?YGdVFb zS{Ds7FfchXH83+XIWaO?FaR)u0wORC1_>&LNQUwzxxTMe{w+x!<2UH24HT7SB;kqwYl9Uc5#U^&uL`r` zWsvVGt7ao;@+9pO#WaR6|3$*4=B22?QWT}G;ou2p!2Q0gUHFuPnrV9dGmuV3@1k>uvCmG>!Jo zfsJcXZTA0@Rs@0#De>tH3JO|?JL^II*Ds0O{X!tjfv97H6nbP4 zs^Fhq+_yb$G}z}u_zR{x2%}v74>#&UgU%+H<+!xG4VKu|2Vz|IA>xq&0|5X5FbxI? zDuzgg_YDC74Fv!Lf&u{mfIiAxcUCzNM)9r($ohy#38&{x_K`#|i!IW@W+4f2Zwy^a z$>xY}2s;AC?3W-Ib3uE>X?SA0Pow&R+OZRpy|68)-;u@ZYGEPSB&=q~1Pn7aiY(&7 z67#W@MU-8}aTvP>lMyMbXcGo6108IC7(-MsZ=kE#774Lyq_trpNqr$CA-~4c>lg+! zMa-eA&ZJ>D57N7qaE)g)o)<4;57gb|&OmsiN@IC)#_^cQWg@2C!`y+QIGK9_7aW3e zHPdjQS*+6^u5@(qVJ&4&CO<*uEjjMBxT?5ooG}+Gk@B`T)1}N(!r3tNtp$HR)jh0O zUwuf%1ZNLEbaAo}5}ujZOV^(61y*r9mE4_>k#^br6$%7}dac{lHp>^q0=?Y^F*I;m zdJuD~)!`{U8H z_K3&alNmy!h2c~v9b7Gr)d-SscV>ZtQmD_)u6RyK1PXzfV__d;(dG6Wq7|+yqcV Ls04*L8r3+njCTpM literal 0 HcmV?d00001 diff --git a/src/keytypes/data/ca/self.term.crt.der b/src/keytypes/data/ca/self.term.crt.der new file mode 100644 index 0000000000000000000000000000000000000000..3c1c96b31b5349b6a2eb1eab5d745452de8f281c GIT binary patch literal 1259 zcmXqLVtH=R#B`oXq%!s5cc1+x4okN;&v<7jyKiRF_PqwYY@Awc9&O)w85y}*84L^z z^$c{_m_u2Zc~px^i%WE~Q!7hLGINR*ic@pabc-|7^HNh3oKuTRGSf1X6H8JJW`@h_gQ(d_e-EWH-GO~3!$`d2K=>p#yUAOD}YeTs*%(35N}an<+7dW?d$ z+*&ViJ=?70CWH3N6CS-?BDGTH5j!I}k6N@WTs%S2UdS$IwlRCzZ?Vh*#_bL7lCE2Q z+OX-&afzZAb~bj+8gKtp&5l)gHgATkgYD}+m)M?JeH<|sO6#2kH$ObjnHBwLQRh;L zQyl>x9D)>6zS>+iEmBGSmzcWR|9zqiU)K>0-kimsHZJ<;|6pD0kD8blnZ-N(UJ2#} zp0=NQ;$)e_Y7fp<$u-tL+mm|@CEjPPtNgh7NK@n@zDe7rrOkVvo#@)Q%rth&zB9+Q zqvk7~IJ{udy@Q!Sx?gVzgxyx~H)!~)oOW@F;yT9GJ7F%BH5YZ?ia!t$4pO^U<6`;t zsZUwP;>Eqe+ovzRVDf@#Q%|5@*6p4UCehalKC{E*kFPWwRN6JQ3oFi1xOyY02n`V*0r7=C>DX zD<0KJohdvhUF>_o(V%*B;7YB0|9LVG7yj0n&(p+T%k?KdxBt}oS$|8mzCAP9{am?8 zy6v=YQAY}GF1+VnFWT3(rroZ7{r5*ofBX%%FSX_?+kS8TrcGTl7Fr6YX3g2AZsK&y zrt0a5>=}L$mml6_VrFDuL{1^VgbPd|j12Q;vgRqyo%`Wr{CCeMJuH7x&xOl7PH+A! zt8te5(Cgjl@3w~DekZ@x%P3;%qY0Y|=5o$l-4GSGR2JxlgUZ<)~YN#uFamCW;hwLi}|#M3mtU2WdQ zJu9ZAv8%1>N%2s0Ntu2pZPQ+~HjNk|^@VHJmHmFXPgQxrho=6|*`1or zTzMzGF0m|`Q03Rzt#`nZ_nz*jmI4ue$27%Tc85Y#UN4O?F#Oi_Oa12({x zoALPG@BP|);{{jctZkVv_sxo*uHn{GwX*tWy=9p4MT4<5{Z#VzbCN7h+w>f{OIfEB z=RfP#Z|MG!!6b5pQ|E$Vxkhe0>#L9K-zOQn*u0w{v+aEqdx5K4fMc*ou=|fo>z??Y znmYe&N1dtk=Gk{jwrt{`x+L+-KekEV%8##2yX`l>MWNn&m0-k@$0=`=boELB`(Q$R literal 0 HcmV?d00001