From 36ff777984fbd728737b264d7aa3933794716519 Mon Sep 17 00:00:00 2001 From: vasraz Date: Thu, 29 Jul 2021 14:41:18 +0100 Subject: [PATCH] Implement 'Signed Large CSAR' support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I33cc381b86c6a10e20d521c0d3dcc76c28344b8f Signed-off-by: Vasyl Razinkov Issue-ID: SDC-3652 Issue-ID: SDC-3653 Signed-off-by: André Schmid --- .../sdc/be/csar/storage/CsarSizeReducer.java | 153 ++++++++++++++--- .../sdc/be/csar/storage/CsarSizeReducerTest.java | 55 +++++- .../csarSizeReducer/dummyToNotReduce.csar | Bin 0 -> 24301 bytes .../{dummy.csar => dummyToReduce.csar} | Bin .../resources/csarSizeReducer/dummyToReduce.zip | Bin 0 -> 23905 bytes .../OrchestrationTemplateCandidateImpl.java | 24 ++- .../OrchestrationTemplateCandidateImplTest.java | 12 +- .../OrchestrationTemplateCSARHandler.java | 10 +- .../csar/validation/CsarSecurityValidator.java | 34 ++-- .../security/SecurityManager.java | 188 +++++++++++++++------ .../csar/validation/CsarSecurityValidatorTest.java | 112 +++++++++--- .../security/SecurityManagerTest.java | 66 ++++++-- .../2-file-signed-package.zip | Bin 0 -> 3154 bytes .../3-file-signed-package.zip | Bin 0 -> 3446 bytes 14 files changed, 509 insertions(+), 145 deletions(-) create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar rename common-be/src/test/resources/csarSizeReducer/{dummy.csar => dummyToReduce.csar} (100%) create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip create mode 100644 openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip create mode 100644 openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java index cf35c8c4d7..1fef373362 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java @@ -20,14 +20,24 @@ package org.openecomp.sdc.be.csar.storage; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import lombok.Getter; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.FilenameUtils; import org.openecomp.sdc.be.csar.storage.exception.CsarSizeReducerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +45,12 @@ import org.slf4j.LoggerFactory; public class CsarSizeReducer implements PackageSizeReducer { private static final Logger LOGGER = LoggerFactory.getLogger(CsarSizeReducer.class); + private static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); + private static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); + private static final String CSAR_EXTENSION = "csar"; + private static final String UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR = "An unexpected problem happened while reading the CSAR '%s'"; + @Getter + private final AtomicBoolean reduced = new AtomicBoolean(false); private final CsarPackageReducerConfiguration configuration; @@ -44,38 +60,31 @@ public class CsarSizeReducer implements PackageSizeReducer { @Override public byte[] reduce(final Path csarPackagePath) { + if (hasSignedPackageStructure(csarPackagePath)) { + return reduce(csarPackagePath, this::signedZipProcessingConsumer); + } else { + return reduce(csarPackagePath, this::unsignedZipProcessingConsumer); + } + } + + private byte[] reduce(final Path csarPackagePath, final ZipProcessFunction zipProcessingFunction) { final var reducedCsarPath = Path.of(csarPackagePath + "." + UUID.randomUUID()); try (final var zf = new ZipFile(csarPackagePath.toString()); final var zos = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(reducedCsarPath)))) { - - zf.entries().asIterator().forEachRemaining(entry -> { - final var entryName = entry.getName(); - try { - if (!entry.isDirectory()) { - zos.putNextEntry(new ZipEntry(entryName)); - if (isCandidateToRemove(entry)) { - // replace with EMPTY string to avoid package description inconsistency/validation errors - zos.write("".getBytes()); - } else { - zos.write(zf.getInputStream(entry).readAllBytes()); - } - } - zos.closeEntry(); - } catch (final IOException ei) { - final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); - throw new CsarSizeReducerException(errorMsg, ei); - } - }); - + zf.entries().asIterator().forEachRemaining(zipProcessingFunction.getProcessZipConsumer(csarPackagePath, zf, zos)); } catch (final IOException ex1) { rollback(reducedCsarPath); - final var errorMsg = String.format("An unexpected problem happened while reading the CSAR '%s'", csarPackagePath); + final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath); throw new CsarSizeReducerException(errorMsg, ex1); } final byte[] reducedCsarBytes; try { - reducedCsarBytes = Files.readAllBytes(reducedCsarPath); + if (reduced.get()) { + reducedCsarBytes = Files.readAllBytes(reducedCsarPath); + } else { + reducedCsarBytes = Files.readAllBytes(csarPackagePath); + } } catch (final IOException e) { final var errorMsg = String.format("Could not read bytes of file '%s'", csarPackagePath); throw new CsarSizeReducerException(errorMsg, e); @@ -90,6 +99,51 @@ public class CsarSizeReducer implements PackageSizeReducer { return reducedCsarBytes; } + private Consumer signedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) { + return zipEntry -> { + final var entryName = zipEntry.getName(); + try { + zos.putNextEntry(new ZipEntry(entryName)); + if (!zipEntry.isDirectory()) { + if (entryName.toLowerCase().endsWith(CSAR_EXTENSION)) { + final var internalCsarExtractPath = Path.of(csarPackagePath + "." + UUID.randomUUID()); + Files.copy(zf.getInputStream(zipEntry), internalCsarExtractPath, REPLACE_EXISTING); + zos.write(reduce(internalCsarExtractPath, this::unsignedZipProcessingConsumer)); + Files.delete(internalCsarExtractPath); + } else { + zos.write(zf.getInputStream(zipEntry).readAllBytes()); + } + } + zos.closeEntry(); + } catch (final IOException ei) { + final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, ei); + } + }; + } + + private Consumer unsignedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) { + return zipEntry -> { + final var entryName = zipEntry.getName(); + try { + zos.putNextEntry(new ZipEntry(entryName)); + if (!zipEntry.isDirectory()) { + if (isCandidateToRemove(zipEntry)) { + // replace with EMPTY string to avoid package description inconsistency/validation errors + zos.write("".getBytes()); + reduced.set(true); + } else { + zos.write(zf.getInputStream(zipEntry).readAllBytes()); + } + } + zos.closeEntry(); + } catch (final IOException ei) { + final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, ei); + } + }; + } + private void rollback(final Path reducedCsarPath) { if (Files.exists(reducedCsarPath)) { try { @@ -106,4 +160,59 @@ public class CsarSizeReducer implements PackageSizeReducer { || zipEntry.getSize() > configuration.getSizeLimit(); } + private boolean hasSignedPackageStructure(final Path csarPackagePath) { + final List packagePathList; + try (final var zf = new ZipFile(csarPackagePath.toString())) { + packagePathList = zf.stream() + .filter(zipEntry -> !zipEntry.isDirectory()) + .map(ZipEntry::getName).map(Path::of) + .collect(Collectors.toList()); + } catch (final IOException e) { + final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath); + throw new CsarSizeReducerException(errorMsg, e); + } + + if (CollectionUtils.isEmpty(packagePathList)) { + return false; + } + final int numberOfFiles = packagePathList.size(); + if (numberOfFiles == 2) { + return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList); + } + if (numberOfFiles == 3) { + return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList) && hasOneCertificateFile(packagePathList); + } + return false; + } + + private boolean hasOneInternalPackageFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(extension -> extension.endsWith(CSAR_EXTENSION)).count() == 1; + } + + private boolean hasOneSignatureFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(ALLOWED_SIGNATURE_EXTENSIONS::contains).count() == 1; + } + + private boolean hasOneCertificateFile(final List packagePathList) { + return packagePathList.parallelStream() + .map(Path::toString) + .map(FilenameUtils::getExtension) + .map(String::toLowerCase) + .filter(ALLOWED_CERTIFICATE_EXTENSIONS::contains).count() == 1; + } + + @FunctionalInterface + private interface ZipProcessFunction { + + Consumer getProcessZipConsumer(Path csarPackagePath, ZipFile zf, ZipOutputStream zos); + } + } diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java index c7586446f7..eaa5ffeda2 100644 --- a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java @@ -23,7 +23,9 @@ package org.openecomp.sdc.be.csar.storage; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; @@ -32,7 +34,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -51,15 +54,16 @@ class CsarSizeReducerTest { MockitoAnnotations.openMocks(this); } - @Test - void reduceByPathAndSizeTest() throws ZipException { + @ParameterizedTest + @ValueSource(strings = {"dummyToReduce.zip", "dummyToReduce.csar", "dummyToNotReduce.csar"}) + void reduceByPathAndSizeTest(String fileName) throws ZipException { final var pathToReduce1 = Path.of("Files/images"); final var pathToReduce2 = Path.of("Files/Scripts/my_script.sh"); final var sizeLimit = 150000L; when(csarPackageReducerConfiguration.getSizeLimit()).thenReturn(sizeLimit); when(csarPackageReducerConfiguration.getFoldersToStrip()).thenReturn(Set.of(pathToReduce1, pathToReduce2)); - final var csarPath = Path.of("src/test/resources/csarSizeReducer/dummy.csar"); + final var csarPath = Path.of("src/test/resources/csarSizeReducer/" + fileName); final Map originalCsar = ZipUtils.readZip(csarPath.toFile(), false); @@ -74,14 +78,47 @@ class CsarSizeReducerTest { assertTrue(reducedCsar.containsKey(originalFilePath), String.format("No file should be removed, but it is missing original file '%s'", originalFilePath)); - if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) - || originalBytes.length > sizeLimit) { - assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), - String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + final String extention = fileName.substring(fileName.lastIndexOf('.') + 1); + switch (extention.toLowerCase()) { + case "zip": + verifyZIP(pathToReduce1, pathToReduce2, sizeLimit, reducedCsar, originalFilePath, originalBytes); + break; + case "csar": + verifyCSAR(pathToReduce1, pathToReduce2, sizeLimit, reducedCsar, originalFilePath, originalBytes); + break; + default: + fail("Unexpected file extention"); + break; + } + } + } + + private void verifyCSAR(final Path pathToReduce1, final Path pathToReduce2, final long sizeLimit, final Map reducedCsar, + final String originalFilePath, final byte[] originalBytes) { + if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) + || originalBytes.length > sizeLimit) { + assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + } else { + assertArrayEquals(originalBytes, reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be equal", originalFilePath)); + } + } + + private void verifyZIP(final Path pathToReduce1, final Path pathToReduce2, final long sizeLimit, final Map reducedCsar, + final String originalFilePath, final byte[] originalBytes) { + if (originalFilePath.startsWith(pathToReduce1.toString()) || originalFilePath.startsWith(pathToReduce2.toString()) + || originalBytes.length > sizeLimit) { + assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), + String.format("File '%s' expected to be reduced to empty string", originalFilePath)); + } else { + if (originalFilePath.endsWith(".csar") && csarSizeReducer.getReduced().get()) { + assertNotEquals(originalBytes.length, reducedCsar.get(originalFilePath).length, + String.format("File '%s' expected to be NOT equal", originalFilePath)); } else { assertArrayEquals(originalBytes, reducedCsar.get(originalFilePath), String.format("File '%s' expected to be equal", originalFilePath)); } } } -} \ No newline at end of file +} diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar b/common-be/src/test/resources/csarSizeReducer/dummyToNotReduce.csar new file mode 100644 index 0000000000000000000000000000000000000000..d44041382edc9fbf0d7cb66cb0c211549270087f GIT binary patch literal 24301 zcmb5V1CTG>lP27@Z`-zQ+_r7o_OET5w{6?D?c26(W8eSm?##r*&hGbBM4qaMipV?} zSrz%5bDl>*8Wao_=wFwN(Vg7CJ^Zf$4u}v)#MI2v&eFxw-p-jpRRtah5(tbz*Z%m} z!1f<=^?(He20H}>0{XW>;om$#0YL$=I<6`FZ%@_KKtN3Y#gnOvv!%YBnVY_|y$vHH zlfHwUnTbBLKAS$1zKf@WsWZK&p{>oog+nWN)T282KZd)~(zf5=ME9MoC7cVJF~F6l zUz{nh7hRaaJ}p`!Urg&4%p;p1r%u34bV~H=-65jVY#}>QVyHt@MvWbw+-vtf5nHoP zd#FMxR#}j))@eam*_-4!9!ySZE*E+3yP)Qoo{5FobE1hsl;+>uo?2ZVt-U;cpm1|? znuw-`Jtl~Ds1i>i%eb_8Hv<4%z!KL1FW2Gq#^hZ|f2h^}ETT!6(8Q0;G;nazA!RJg zi;IK5@FL8$^lc=M>|Nh=GYRZ>P-ZF-P z`BJ5;6t}RIQicAoK7M?_jvPofNi?<_Y;v_L6y%A|I8`T)4dNFT-!D2u)~`cdm?ryw zFZ0f#6w@k|h1_4!!bWf zM`FrNW=>MVQ+wO+nVMODGyro8j&)V6Zk3{9fPrVU7mYjJb4$sFHCJoix*cTu?l+^W z%D;z|lF%P(SDGw!EyI*90YPDJT}5?J^x7Bmu~eN`Ye{11+UF(Izb(p*Oxlxt+YMjZ zYM4-^nFRcx8qt6`L4mqwT#o<>q_}f1eQC5Wswo85Um;lWO@}GJwm1v0urN9tt%azZ zYWuVj_8Ng_)^g`2&eNcMdQAO_Ln_Ko48+scv`jbFK@97W+%$B1Ny3(7v!N287*8}F zecCBuX@Fod-^fPuDU9z<`k>7?J|8c={PP~1qMwxw`u=Q3bIDHtfR{0!z*>W?^i&zZ zpIirAXikMEm!A7Sd|t5pua8b7XHR%s)XjAQrA4ABHE=9R#F&PB93)pn>d`?eZNNGZ^gHoH zY_&&v3P!p*O!LOrgT6@U`S;;*3VhYFE5$iZ6U8o^H{QsmOEDj5Utls62jnre;5^dl zGa4lsqGZw?3#cCN@K&$0>$9sD9K1j3D~498u6p~hbr zk>=N_Pwku?NO=@dvvCu+N_3}As@2QVC|yV#r}*C z8%1=hKGq1>AN`b??`i^14k3IYL|C@h1cl1Q9sSa{F;ffP8EW*ZDqADCYlSJCPdsuE zjat6-cs`F@qzl&C$(-Tu5#Zu8txP(rU33m6cmRjZoiBe%#-{hJ>#SUz6PnlTu^qVf z;Q?2iMA_@Edc?z)mRg=@woZ~R(hjW59Na?G5Of`&?w^!n@G;CGatpgQRd#lbKJJG4 zW>q-n1wT4ih)G%b$^7k{*-o$-M|i*+7BiTQ)-Iq{5V+dj(jg;b@!`{R=RuOv;$QNf zx(u4KyY1D%=Uz*h^g9UE`dV=uUR43;f{v*#9Qb*#S8k@H^oM9O6#rsW{3hiu5qka4 zIFlO(E_(JlON6i%i3kOz_aly=$^m9vXkX~JL zEl%^k+Cavj7WegWT(7u3%-$1O^z%k>RkbI}TC=gWRa~>Z*PtXe?ALE;Ohx7uFwKyH zyqnmI3fnqHTuKT+<_Gw!175Rsz*EZwuRZO)A3o^IcyM-T{8wcD3)Icyoci;y=FVlv zU*CF33!I!KuEXwvr?6cclfS2{o&0Yk1^F?xNJhg2<+#!lMnzRBgVe~N7BD%FPu#k; zokD){NpW00aNeE8sz^p(PDzBDiCNc7EDH<`w3nYh#y2E|G=FYf=nb3r?!|zhch5K~ zaUP1-{tfoYVcemmQ~Qp@ zhs)b~9z4wg>(ZQ&9>rfhO4ZK1d%&x%*>k9*;}tR*tNi)m+Ft9ab9>`Rp{=6P1UTEq z>?fwK1}+WJK0dC(+Xdu*wr9nQcEl9pb7H=yE7g7WmgR-+iA;yj;O)4dsx6-%;X zaTwW~SRFbl3fa)-yGrdU|Lk;=%z#pzZqy%`0#pNTGPq7w64{@qRiy}&gi&}Jg(uoS zxN8}R%?PLx5s9`gxyOfVLaK`?hKe%JTuy*b{4{>8N(nT+sNeieI`va*-GEHD>i`Oe zmh{uvH+H+@;SZTbEbA#|U(ee~@U*>c&zl_{XzDH)OrE_9UR5|Lgr2;C4BE~|9T=s@JN=sCnTmJN98XwR)9R>$fBHkRg|Tg^!2PS*8^B>S-Z zIHF}YW<~nmUt3@{fqr-4et^i@&+`=hvZG=?IwkZVhT$?juZousgM?H2oc?*45EOelqO&ZlW+I2Fy5$lKy zxQ!;kNZYbjQar&F$Y;P@N>~%1ZUv=aw*$|k48+wr>2k*=4i%-}V-w3EB9mOld~++n)Z&_fiOFb#>TOq{Rx<<0hKre42^yXaO= zLH1pBaR_=z(p!2*1uw#@y9#zp9#3chPmMX=AIH;gnYysqaI0ZEy-WO`-vl4HEBqIG z9)8=M+&fo)cLRUG6{I1d%B;7T^*7%z$zOGDrmPpzZjI(UsN*vgQi*8N&z=&t^_-;QgCq#!WLzZEzF%i{I#BUI z3@pi)q}x&^5v2ZNg`M*jkHr%ceJ=wRSf!3==9Xu+g$2R-zNTp1)CaCPBHJ_i6x%pA zSB;F+LBumO?}uO0PgtFww;{TIyyms~y@neuSPfQb;$(~OCI#}(Zr_Wa(~ai&`fvC? zPZumBqkj%q0Ic$98*gtL2aL1@4ZI26Ae<{CH?MFezZUt zR%rE{x@N=cqv~+qVR7`a^69hU198kP;pzIRcedCeucfnrw6cZ%>xY_IYR+PVhoY(C zqG%6PyMERJw<7BJH%55HDc+M%G`$U?sbmSc6{Qeue?3u$q)(HEr9tE$N6)`CrcOZO zocnCL!jsz8>usP9O22JIc3v&V8i+MSO;vBt=sBozh1KdQ?bv> z@D6;3J(>N^Cs!5mv)LzxG5nLKEs}2iXGgYSN?o)iMpz`XW#F55L*;UFM-<#^G|>Ww z3?FKs zfOk!#YsEBwY0o*j;Gns===RmrZC8nkbAoHgm%?rP(YK0%nJ9WJPlPGz3pj`8MZElJ z0b4-#fN5_ONIBOgQq(ew%r@*KSYYF=j^72-{g7;6(A0;nNd2`XnU?|T?tBB4xYBa= zXXGieC#h8mDL9hc3iXAW#_h5a&x)I`D2yf1{ov&8de_sXzQGmS4#NHRBCV~A-PZ|A z^+nUs9U@qR_KWa+Nc#X?O|E}5WQ9O(#$219QTlC+^w7crqqL-N)0k6bZ^gV4{&EZ` zh#*m-F9&m1>l6g0f$Mo*81fw{38>JA{EsSRIV3I-1n(&dIth(ip-W&RD;Sj~u*ORz zlo-7rmt?AAVx9?_k#IQh68sY3<?9vkU0gQwHEUReAUgl8>PYbO|nV=b!WHeY2-EU;rNfw$RfrmR5hq(ZVxYv zt5}J-{uvaE{mQxc51L-68zF52&wcf=?y5s8wLV&fV8t909wk_K|My@fZ z>0^Ay!JI)&d(e?k(pwb167L8hTnGrQp$;IoV;)pf+X_B)evuQdj| zK=7Er-kuJ`lb-a6$0eqM=h+cF2>jQYdqKVJNRXTqCqR@q2Do_Q7I}nX$s~GV%sN*I z$3cVwPeB(R0thFg-HCr};br6(!zX1?3kkIYWq)VX%r?V-I{EfN`zyCkO$Vm;sCGJX zBxq)4>9nt{5#=W2f~FsUVS0Mi!iB>cxNQ41w4 zMffq;pWGLpC`Fr^{~}suQVB*u!1#g}IEDn4!`?hYkI&c>%XZ-M6hHpM zDs~}n!U@5QFFd~SUE*9<^Q}L7Zs7B9dnZK#m@93ENPRW&62F6mhc3b&qb&w(4lBcpsMO}%7F#oq&+XKaUDgiOD#aJFbj*tZvL(31n0;vE-IznhWpPQf z^?C6w$DDC>2 zEY5fLBiD#3>|(vKIyV>v`%oFmYO(eB1Y=P!mgNnbV6ZEk2wEi)#EUFVUv_7XE(4E z1NR5kteIr*3SaharjeXZwm2#x zM?~_;s)OfX&Fw}M#}h}7-$AKBhAeTqjj)W%_j>tKG$-No^iqrrTX1Z7Xq5U*O~wX^ z<}xI6i!7iDwq}c+#$T#RFSJV-;0J2ITk=4nD7RJP7iie@$RWu#744j8|D1;M7g91J zsdAQKkFqr`8R|af1f!q2MsRBbwGb5zGxf}N*tP!ylHf*DdZCNJ7x?Jubx{rKN#A<|^>TdbbOR^0Y=ei{A19O<7;l{Ar<<5uT!xky6zv3yrc3H7JPQLB)a7CptlK(vzwjX*Tu46zEBfXF~u zw<<9!&^wQPmC`_7Ib{#Fp(T~53YzV)7$u*GVW{w2Q}51%wDSE^qMm4JBq`Z)mi5zx zz}s?DD>jSkFiMm4jH~`Sfh7v21zpaxeJzVQC*=r_u+XN7-Y86gaFWYeX+d3FUUB2Q z2M0(Bl~-egFy7a;q7BxkWPQKo%<8C*V(?mDOp;D8i}r*U6a5+b<4ZCrRE_K?oacM& zLj$EZ%Vb+AH{jk8aSl>u1BBXWGJxrCiamQEj0;FFLmN1tjQFbbe|FU-6>JhMz6V`G z5fJdRtT<6phDX?*9X7HZtwVB=>Fgak$&y0)z-8Q5sU?y5B3zu9T|409NPqkxJeGH3 zzOGx_Dg*1(%6oA5!TCR&-u8l{rZLbg?A{^%$7ER`-DNN_NYu#)D7NT?L z&lV6yhD8Va!6x3Ei=9@>3WV-A?IKz}l21*pMI_pQS5qCCpSmGV(>KTuClOoTp4yz@IV(&;AnCq}_-m@Z!Uhu#1U^fNMu%a?`>PDl zwtT7_><)k+iFI^FHuvt(G_MpvP#n~;Gf2JUSzh^Hy9H-lPQPncPh=7+WCe17LQ?(=5 zfYtu?Vh2eap^0-n9&S2LiM+@AdL4Sr`%v5Z@(<7aDaM9)ZD!|IvEqkg|8xK@Ft%)? zbd*R0h|E6H&M{i`p1J8h?k*K+*LIZWE{y~EtF0KiPAuKjjJK+Inye%ruFGbnJu7dm zQP}IsES{xUB9-Iakt=8SUY*dm(S7MUb3zk`NVG!7V9vnrsJWU#CX-Ji^)O+g*k6fv z=Gn1`Ec@t)t$jb+z;j-W&-h1g2+Qlgy%a46%Uc*$X#WKl5mHd!{;2&7$7;t zPjd_o)z+UsB4gSgZAlm4ovH*nB(+dTUF)m^gpO62sbO2mnz}I@p_GOF+KiI$Rqs07 zDew*B0!4winq@yxefj4ES~C;|Fz{WHQ+Yp)W_Qt#uoB0$?G<=p88o+?>l%$jAYLBj z+8l{S32G`GUqszxQ~h;D$vowD=@7g;9UtCOG$=2)uY37pc*u`UuH2IvTV!v)Ivbul z`>l~rK0H>JlAYX40n4mnwOm@KJWn33oZ%>M66kq2i+03c$}uY?-=cEx*#OzW^2Az# zj)iCLFUqRZ<(BeOn7&XOH~-HfLOHcx zqCW1sM{AT6TF&h2cwkS180a(F;E2M}yFX%rl5`5>8Eg3z1&yB?(|a%_gxFgkvs)iL z3^OnKR`X$@{-fExZ7}y=-m+S9v8BD)I`wI#``9O~RaL7IhH+{4Z)of7L+Pl2$Z4N1 z;@MR9g^@8@po2FwTmIEY3kDp{ZvFjMmfi4FFQQOfvS)N_&R`dwaTz>#)rh?*(%u9N zNB+Dm0t9<_x=ov;$3el6b6n2{!*~3&^$WCnBYHguMjC zw%ZsvyN&@Ek(NU*KFKB~d`e2vy%l?2D3`!CLOnG86JVXCYc4lDgzyB`Fm<`S*Kj(? z`I&%OCV$fk6~t2wQez{9bP?aWz_oq8tVb;>4^A(Q`#zDHsXO$;v%W19(hfKt!fWN= ziWWVJY$~$WC@-Lfx4#^~2;5sNYB#OhKz#hwcJIW=o|wwUcf>{vbCoCR0RxS52xFxZ z;XMn1F3yTJQGzHttUrsR&hRZ-oKjRcmD(xmwwS%*ak1j7_qE8VU1-;BB)sv-7kJCA zkP<_s%K!9+k(6HsDT`$+5>I^}HPRn^X;-lqkBtzq>wCj60@Ep~9lVGu1V^^MmKJ0m zwP|9{010dvH9gM4zvIj>ZbC+f>k7W$3*G&RW<{wyLEg)A#zM^sENtXf`Vz{*i%P#F z;@GP7F^jK$?M1(ehGIeSF#Ro3P>ws;d#ad{wxY*}c$F24{-L~$>Isu4(bx;UjIg!b zWtA(Nhuk(>@N{r6fLi9VXZHSGIY!Wd-P>Q9^g9kOmY7%tc;AI{aeM5B0$lrv8^-N> zPhpI!Z*hR`9T&Vg@8vZA-X&As8GR-uAc_BYc3w*W1hx-~GlOZ&LCwM5rrXHx=d0S` z(!OlqZL(P9X5Xdqt<%jG%kHOQhR)y}ufrXp=4#Ms`{=KrN_piuZwyFBBCkVrLdBEsa}Bdz!X0!@)^H^G1(b;hbPXiMjh_y^fsP~sS8d!mPhAxL}DSfHln zvdlHohrxGHR@_lk9pZ<;ytcl(p&js+d2(9Ik!$tJ48mOl> zT< z4pK%PEul*;h{Rfd*xAk;MbPhEdD?okR8A#qO)XeDchzIQ#uQ_)pOi9E!H8xW8=?lj z+<(>0n7lRzBrSiWfq?cmprjk)WV4~0cR;~$(}XB0y=`f23C;I0Se5=6I1@H`3CJDz zBXMl0SaS?`hd<-oapQGmQUo4sPlJ2cGnmqd4U2*r&J<>0_eQKxO|Z37e;XP5Fh7&o zVj%KIEvzsDvfn1iR=%bNoA>q=UaR`l&I5aN&O>Dj7_)(|j%D~rWVja9bwJSeRaGGZ z(t=o0%DIN}Iixc<2Dam7yyJ^Gz@iE!N%V%;W^uvRL9IipDOiZ?Nw9MR4c-xGl;i{n zV(2*VzT|hg#`Qx3$Ua+AT!Qy@VK+=kN$={$t!t&DbuUCIhL!?>Zi=G82>jEPzlfi! z@4W-LJ_*E9&<^Z2FP-Nkin!uPX{6&=_l{91V?HoFJmcWJOqLF|CHsM0rlLa4G+b;w z`TY2d?T>4>YG^iW)Tn%XC1XcXZ& z-_YDrCD0RutO8M6kc)iS0HC48#cBPjiB?8Gl9etBqg?R@4swP?%J|xlM|(GLRqOia z)wS(^hSlK{hK`#Oz`^##pKH8E0iU*S;Q4oD@C%lxO~5+l=S*gesTk`oV}em?f_>B; zW5)Z0g3D82cCYJlXl6G&RbOpsV7a_+J&i=YvS)7fr&qhx(r3LV6?c=V-S+o=Pm;8- zLmInbkcl3KeQM&%zdr46*0|;dQGk^%-noA8AjP}i^i%l~JxE_|3bZ?Zu5dI`-f*AP zw9)2$FYo|A0GF}(4qu6r;oOoQVB6$DZ;-}k z^Bz{FU@Tf+?IIzwx`80$Y$?A60je6`?!Zds$r#T>_vB^av0k_GG|_|M;(pgJNcZ#w z7I0m$%}ltQQp_o>Zi>#&)k!2P@YzmQS?xA8XQ0!VK8Q+6yD&Mea;zLpX0@VW0Wrqr zoYpC)L`;+(HgEKCqQ5lBfg5oT51NR{~*O_v&0XioW z%a{lsgfQ7+_KaIyV2E*8P(0`_fN(!D<1-Ug)H}EVhVfr&ZYRYcx2e!s{c^l#8-uaU1(pQ&oG4;BVlH1)!S$gk3Q`J8Iva#{d$+lTE@tTTm9&i8jKfCf#fG zG{g1acFB!pcn+C#SR_cy?j@jpw_$K3hEqMOR==L0#wJt4R{W@-8R=BwAV7~;3`4hs zdsGQEygo%Oz9p2dwwl5n#0APEsj+<$<8q zQ`dC7y}mqE_%+T;*>HH%(L8Miai|omk4=3VEy{#f`k9DYBzW&G7Iz3=udm9VN105* zgQ{NS7X5^x|8y6ZOwwYp0Zd4fFL z=t#=&4)s!vlwXrC@>v9?O`iYe0&v`%%`S}O^Va#)M^&K{!<~wBGqZWBa5NUXAvX~R z4!i0J-n7v9T%I&UAZTysxAxQWfXA`^ik`&!0Ztw%S1+0`ZNV=p;2*b=X+e>-G}i{E zd4_jIp`d>R(~cBWXX|R|011SY?FtsJp`ql#+3{x((3xEwUkkfXw=3(A{QcmQK})`q zfz)($X~fBkMf8#k5UlAf@^>i@xV(q(Ok^$7cj?<&lULUE;ITVufPU+Vz#;c^j+Jwi z(_OM%SKFR{D&AEs1Y6Uq$s@U3&o;BA`yHD}!qg$DmM=nJT~V-(!66HTon~(r4Hs_r z1v7I%5i_=W4F6g-4j-xiX_aI%%I_wN-pcmk&A=SLi8UcDx|}o$X;z+pSOKrDbn)3z1#Pr^$exjww!aUWC(N^+eNRch><1dIKhu1*GanR$oU{-JJ3e$>ZW=0>2H*7 zNV$|?fY1gI?>^IbyGN33#YWYe80WO6c7%JXo2ybgEpk4%h9mQgmUw_+0G1gH9v^Se z3wBaejXv}u@lk7DfHrC#(|j;A0ndescN*l*QGag)FVHLAy%!hmwqP{MW%2IT)7F6al!BOS1ox60TckP3h4 zQ1%yH0g%e~Qn`%JX=qnu5C^JQ0D3=4PL0c^@)8oNNagg@&q#8u*q_x|7KkrkfE0dgL`3Po)^2yq0 z8)LmWC8o=_!uV_z2cs*3p}&T7{8l;@IRuyXxg!8IjiR={+UG-Mo-5yrXKz7RS=}57 zw@hAyUw3Q9Y()>Oxs;UbM`3>5IQUvX*K)?*_ItL-i$RqkA0o@~$a(N>b^7G#q;!JG zRL-`;V|(9!1W`w$6t@Ijh47NmHC9A(QoQO}o{kaaSsoP(Wur9r?wTbP!{1fF^*riW z^+TugS4F7{#P77&y)1ELA`GVewuAfi<&GGHC$j36({#h^Y}3T8D4sDkTvJbETFZ_8 z5dnVOt53FC*x&p>^THvJw%*}}C+V+70hWPo(?_cI`=Kn_S&6End`yL8BPvbiDqcTG zmPs@M@0-rQDKPvV+Y}_zPoc=;(!Eic?e@?vsb{o-QaP{|1$HUOM7i$E0#sQ%uJ0N4 zjyZaH$Az~le>54G=gL?>u%7X*HKy={Sdj(e|B%!JhMN?8x2HmD@J$R`)l8mdbq@mt z!VzR>qP7L&IODrkpT`|9*8tvAyT96?qoAOTpjyu)>R;MBCjSn?2?Ep*ky1{jD@VPo zCDx7Y0HiLml2-#WgxCbe1-XaGO%jgap$)t9%!toZxL2bV${R&=3Ht4D^>WbcZFvwr z7HOokf$>ud`iF~T5uIKSsRj_9mEL-g(G+iHl>QXzx%#QK2$1o-A-cXjc78X;fYP^4 zNd}g#b!tsP7%2%Wo~Fu3o6*KaqO%Iyuip>rTyrq&1C`p=)-vPF$aa-+o)&1|e;I6R zU1hG@=hIPQwLjWJ28cZB4@aAwPJK)4QDj~7?Rnfl?OMQ&7y}^)=&a)1KEOA!@O{iL z^`go;;;^5jPSY{$$?a?TvvIB5;$O?iBkHz?RL zC3=((QYckh7R&st2gxUx%k(^yMxn$_s;mRkqZdiWTL<$~4VwD+jSoq%U!679OMm@@ z^L+@&Omh7B4Q#*I7Ercw%^Ya;qCRPbQ0*cm)+h?YC|6H&e6AAS-u^1Jl{kW}nYpJ! zQc|G65PJ{}dR&w&bQ zkk-Eg9!Y-e_mG}RofdVn_lAsH8eQ34m@<=$XNfa zO6Jah0t)Mr8FRNvzm_nsnU<(fxU$iW$@|rqYo1JBYp676Zlsk)uvxT3qC*^-NZ`xU|gLf zuRZZ7oAseYAA=hU457~NNO39_jr0isrqL3PujvRQ)V06m7DSHcxp{HICV&OJ-|X_s zs&eFaRk^0JBp2Uv4KM;9d{X=R@&D?%I!G4v}U?fxe0OZt+P_CN_E?5)*e;pbhMu4?v0T4 zFG(ZdkciXFi*Sv5S_t!6+yN0i(co(NB|~pcu0ZLb#yI%FpgQ@+d6R)&q2{vQgZ538=f~ewMyP|vRWFoglz_f-^(-A z-dxxp6==>Y#cDk$*CGU9A+u9G?!93>zpHx_?PhR(0Jbo{s~WveR-OV^c# zwk3-g*BkWCUYU(G3F{p2{t`-J7`47cW0vR8EE&DJ_fxy~R;w_Dqdp={UBk*}4d3zw zyE~-!=kj;e4O6hx--z@W2vZvWkQ9`XLUMWM7RXAHCyHUw z7@b(6j%c?y*U(vQR}OxWsmME52FS1EWS4mZ*9Dp;?SjXKQ(+E;KW{N*f|4~ ztcIBW2+dZA6d);0fMyj5wGL6=@I^8;m)MD!sOcI@Cpr#{`2R;X)Ol{K3u*#E@ENIDWTh|dumc{CgF9~jpvQrME zTa^#-KRZ`SlJN>;SIr!`yN%(+KL@2V;%<$>kr)|siGF;$zabFN&*>3BBXJ|Bpo>@v zv#tSngbqC;4L2j`o$)GNusBk-rUE7T*k_hsHt_m#{5}Fdia>(fH9aUYizv5UfkjSe z<{Oo^DweWip8evGjokf2^MU?aql#sMd2na;;iOloGLE$JE1vsLZPd%REtOc**$4Y+v0}Rk zX7g7 zPAgh{>H94T;5rYo0V58UDh=9UbkO`$=VK(^tX7C9cHm0O)>VVbj+P>6_%4J2EhQjK zS?Xe|O5rZJo=y}rXF4X(+#I^k=}|$S$|VU$78u;PVrWexV9xtxp`+!eLPwVV<@9&S z!III8QKaiF(q9^o9$}yuIcfB@AMolyNsSnS)hwwZJ!-#P1Zc{MZ_zTOHkn6y z5)71Kxr0oH(e(EY&HlQ<`M-DLaadNJwfav$nbWZW%in30&HJR+tg@Q44}7mBz+Hbt zZys6j>`b<;I^_5W6zeM`HHha44h%t70~;RtVSruov$wyu=_kq7tz^;04j80dfydyW zPiL$-pM36`B@neUu)`9R4Tk~{VF-RuFKfqx|eCS^gInR;;N^ghr1!uR;a$d;U>1@lb^x4%ut3CrB zPR+{*2{SdSDr)!)#f$@!P}t+|u!I7Kjyi8%(iZPwep4(MW?~>2Pv;v}v{b5lg`|`B zAp}dR5-sCp(z7O)*`=P7YvL600L57%K$q^iqH6iy*ksIhtxRF~_={P)D4RtoZ*fG= zB$_U`q3k1q*D1Oakzbv8EIf*9RyZj#XQ-`3q=7sgCwmceI(|8K&CNxn{a{ zW>U7u(l;gplwU$8x@Z2vK0#j12P%Gz7q}t)$X1E-?Q)GEPgnnJb zG^)rH>~)gYHRleVUDJcGMe%bwXI)_Dg!v+yXD-CDPo-;G+HL$s0o=A!;#&{~a#h#kG zwScH)CpKCbJ@fZcyI+m6xWGky8idGcDHTkI)i3;VNc5HFr{j>Fl>xGz{>}SjzO$15 z5t+`aZBMYcXlK5oJXfd4>hN>5pNV|(?$YD=r-rV!jvb+JqGZO1LPq8fND}AB#zjqq zJ?PS}#!4a=@xRAiXbO<_C9K9(8jloKbrH|gz^I28%1wz#KLv52b#`#e-g}nTP16j* z8rEEN`Sg{P9F~zw?q_O6qK%F~B@{%)rkkA=cWai(D&K}+hBr=>hNng;>awVk+nlMp zSWhcL42@J!W>d(bljd9Gwk4|C!%=wr?s70KP}+m!DzzagPQwzxyLUMC!O8|In*=b} zqD%W|%QZr?(-JcLC@agFe>n%rE|h**YR8D+DQ_WPRvnQYq)46IiD;-^LljA2dQYYG8P>drK!U}`;O-8 zO8|_bdDXY^M91CaPJe=a6n3cNM|~ZQ-@3~`3}xA5@C`XofIWQc>bgJtO|%!d=Q7b> zX_<+7cGLLg+NX{dA}d1V$e>?nC8&myszYYywUw7vBL&o}y%REu}9W@)GXh&J>B=n!FLBD#`EWmlSrqcVs2dSY!`-IQEloh7=75{8M&)K zr?+u=gmt4c8h*d2))kOq&!I!qFi7kXz4Ppo4aJqE-}M%m>!y53q|VvujG&1^XXL0K z8R85V>KdkiNPp5tjWsebA-_amK|^Cbmu&wbszkDj;iafxf;8qA%4zxc#y)vcEzfowsA#cs=`j&(q zLHuaZ%hkbvxeNQu>ZZFnbLw&3Ay%Q;C0s9n^!XIx1?%ovJX)alz5ox5#)k2LKKNUk z5YXdv<5wcOZp_}@?u$Zu!p-ZrzSK!v4ofALz7#{IsNTJNMocO-af?d1MqP+~TKj<5K zlLec^Jun^^sdG2PPlo559le3}U99~(lOLR63`zuQuMUI=d!N2;y~Ygg?!)QdN#`ehfdAlpqYV=JA`j8!hlAN2-U+mvqJjO&HyxwV7--TNVD=Cp zBKj^1B-KWv?o zy-!t!bOCB0z*#`_?kzW{O5_egbe8kyJY^Qch6*PQ3WVwwxw6GWS|c<}g;JACR)Bcj zV%`Ni6tySm)66i$5W%l2MsD@E)gCgoAeQ`po4UoHu*p0M7TO}oQs73YH zRP+Q_WeEt{q-5CFLYN>S1YZe<1tS_RvrdVPTFPJlGgm@a*GMKG)R=FpT~vv{LGV#v_713hM;$Pe+OiE{_J{uv)80x z#o+gSR_ZR$tbW>T>hl@q^H+TH)muMze5KTP(`Slnyc!aB;%d~_yrwY9rL)+(Y9Pqx zN84>n#Wth1Nhz~f;sh@u@mZfK)}{J0q&dm{BN@b&-PDAgj>J}> zluUZWJ9=8CWcW++$J*)VF9pYt$g-rcYzVY+;jT1rc3Ll;0}Q2QG4LNL6_qL)OR-!M~{c|1245c^QC!{zcILKU4P) zL4bfr|BIUTAN8!ht)ZozzKN-`v6H2Pi@no-2x$K=Z2b_WX}b+ZxSkKHR}f|!3L*BL zNJBpKc8nlFX%!-s5*10NP%{*r`)C!nfg;F+nk2LwrwOLN>TalEs{ z($t{PltNyVb2dP!yG=#)xu55Ud9w3-z^vBNPvZ$_TOGHUDmcUS4=%5>?)ZSXwtiEDjJ@~ISbG14%jMRyXW`1_E%tyEv{&#}Hx%TAHY zpbxTYth#TCasH#hT=9!C?hLBwo=I(&^gm}K@QcXbm*JsS_ z|19ypgwm3cCrbZYF#WGP#6Kk#v$Qe&U!v=Y#Ulf||Ci|cKPx=w{_*ka-+c&M7}}Ye z%GjIJyLh<#yNdh2(Qv?ykI||D6(mNhgfvYR?4yJg`q{Qp;C&i_Qg|F5oO zJr(}>1^#7jO#h$0{Hu&eGO z-&`)V*_gb&b3ckasm!*#AI+Tws-h9qmORvZxT{(m4DD9L%zpMOYhb!dUu#v2umk*{ z$Tp$ME|8akfs`E!`a-xE)|E=2XuWqbWvHjj2hv2B=W3 z??<0%CpT7MCR324Kcd0jM=gN%J9D;n?*>d|)U5=u1oA226h0le+PzgaBb^$Ddi|>v z#S4wn$;C@t19~JO8?E6L$Zk4%!e44K-IYYom`7ukn1WEAbluZk*M_L?-W#?lTk6|r zq%7?kPI8AlhoN*?&|V%)O>i#-e+e|~*y}>-$9d@`GYI|UO^vIYL@4NFiGPpmdgGQY z-3ti$vIRR*u#^peZ8w&-<`qFp*rAwH_|mCM|9u3utJ-b_A(7%Qd{dDPgLmBKw$n-) zjG|yGn$+s(D1^Owe!l(lm8dJmcVt>qoip9D?*Zvs!Fp4E&ajkPc1&&BxN`Q#wpC7E z>uS-R=LyjMP{GB|$DcT3#<8bmhp1J(BGgE(!^@>@6N1hJjmw%O8-zA5dX%G{AbR;& zoyn?nF21$hkkd0-H#I|9IFs38`xiv~ltXHc6N(e17EB4BbfU11=)ge42Q@bFlsOEe zfk+WkcH?XH)!*ehA*A?6g?3I@n-72pK`T z#h}%zMtx?OUN;aqx8eT1P)aVl0-i4c;*-bU#J8<2o)nIwb~nzYtm1eA*P~I4#MppS zn2|YD4Kh7EB0m92GZXxocEAFYIs`xYYFxlO?_u6aw$xt!{Xh}&^YuF|3dx_y1Q~O~ z=l1BTKDlI`t^aPE%A(inXYY!MQ~YrBrI$df0l+tzdqIgLbamXs8PF!oriUkOH?>h5l=vB1rr0Yi^%cSFo-1*f1XyrP< zsczr@L3Z}YIw2$T7|9mLCbG#W#Idq+Y&tJVM2FX2uS0fZ?_^}}P1!ReWm7`_=hS;j z{odYx{r>-(=lUMkxvu+pKF|2xpXYPC?gtJBRkvMnsrlhh1Goq${w9wh>p^@1ewyY@;`tI?Za=ZCRmIq&R{%v!rnP@#)~UcR2gbU8@B7R`ra;Z5W@*k>#^~yds>C+ z0iGFoNWNiecA1D)s%Ma7tR}(XHeqAXGfJ6zw)yjMUUSg9yE7`hAC-09+WH(n=uoB4 z$Ui1GxU3kY zPD&$-X|1eK4SHe*6z2$=ig`IXmCfnv&z-95Y50lIH-8Lc)ku}Si4b!wis|@zx9Q74 zibvU>?%kjae0Pl(F&=k43p2XItnt2qBxX+kL`u)yDx+omj~B{56>1(G+|{l&Ske*p zKXFSN!OT~8BBj^tB^)a^5BCN@Nwq-}%)v3*EOA;Rv-CVtvjz`0SBWr00dbmm%}bpj zeXP;#C0e~X@O9Zd!rDHSqoM7W)kjZl76EyCU0yTiJLxlX5*3LjS;LyVjM^8)4bpUQ zTLd8uS)UN{rc4hDw0%gDeO8`VUMa*CHxN5guJhHT_K=Gm52>+kJHe8869)?qEhIXJ z>$;Ecf(zatIFNc`)kx#*l0v{<#(jC6QqxhsXAH?L&V(p%nlw?BE&`I)6*Zj@WwjS}SgKe8q0{a5v-Cn#MY|JHL z-vbg<6rh$%4AH;_08lUJUx@-rZnj9e@~x)GzeE87WPuR?K+k(e+kfZ%>kG)ws78-$ zUqa;HuH2vy7gKALTU40SwXuaVA%D%8pym$F_7Eo2*G!+kX0k#?RuI9rQ5svVd>4Jc z#VQpCb8GY{5y$|XhHR+kPb;4&o(kk7$PrR~ja7fUa)m%$zlPyvWL%H?=eW-OB@D|4 zMh?%DgFXjZatdr0luuOqHuxU{?YFS64l#$ixj-EL58?LYG62B#6~cE8HPub!Z7ooh zVa#2;el6-6O*tY9w$yFtkX9!UnbA8$_Bu)!JY1;2No)>|0t6`V**q+%j^H3tVcR-O z&>ir$Zc?%XA*W5v*;TwyH3YPGp)kJCCtbfIcW#U`T&NE_X6Jn&-Vs$KC;Nqcg!2B? z4z|!9i%baZsjlN$iC}S;?b0W ziLZe9@1w*!mF{-j$331ZAbJs<>&`7^uT<-pX;)lLAB0D4s}2*^|J#X}Kvp zovfAk6^*MU%-I$1D%I^)A+QfOg=1Bv@xj_kI$6H9yIAkp>^uQ6$rT{*Ih#r8qoU)O z-DyYFjakOpN+%LKZxM3_e7W-;0p%B{o6>y>tQV~&sfC*Pr6639&t(`3M0Tir`WqaI z(+Lo1(F(--P*W+0xS1`#$hpz=s*Ang_Ey2!q?xVwz2J*|1w0z>Uq(!IVz|x$YM3Ls z85oVO!BrbryDSjh@RA29LxTGbInElfSQvB)nHh zI8vA;CTH%w&Cy}TtkRV5zAJKw2G&r%KlpcHOuBOO$+}Rc)K|kyy)o8 zocYQ-&|5qWdvo#umCjL@F+o6*$*AX+k#mm!=wIe7q-wgjaJ)Gj#xqpa?}Z}wDkoGP z-x~_!>F2(dq`-6iN9#=ZA_kfdqO`rDV^P(q=dBPy@QDA#ijwz{y&}q)O4&YB?8(z7TgB=?(Lp5o^|X> zJhc_WLxeXdMtlTI+q`kb;6V@I0&Pk;&uFNOR%$3lLCD8Gf5+tsImX~Dz{9}vUtzAz}vu~~1{f0vLcY8=9Z=w;)G z)UTDG#Q0O2zhaw|?qE(;bZhCB=zSG646zt5!_){F(D<-j*Nbjkg3O*tB1r@h zrG+Xv`$O#^nD zjP%58gUAv1qF1l+z|y&i?$u7W=%{WSm)fSR4k$WFa$zl&6H|Wk-sA$QkJS0UZiO-4 zApY29@Zn(I(s#?t85Hk(=_t_m_+$Cb@)+QmUXQI^JB}QG|4=OybjiYb zU2zLGXAIw@8lHD9+T6RZUQHUG{lYRD&z&S*Ng+YIV~?RYZh1?5;N;-p-e~uMJ_hWD=4Xyo(&_Tl5A5(W=I*KIKsNzbqwGDpZ8Um@6>9b+u-Wm(RZP0zF5CnfZG9T*Iu_3ueA3@2pWC8>^qwcFnnnw z_foSvDLtKAIRO;u2MjkbERAfs){)D-y;1DIPRbgK@8x`-g}SCV=_+x>B_J?3el}36 z?fxFQNg!a8W`e+D62Kb}g|Mb}kyV?ZglSIO;JBYLi<ZA=Pb-Hde4{w(X)1 zwz>2HacYcay3EwQui|b z@(~2Jq3S9o^J5~#9+>2+qaoD}f-rbC!d>`fnWjPq>MScqytI~|WCqG{@z?a&M6OB6 z5Ws9B2O3$*7ou&ocAIl3K_QarK&82?{f*~}8fkYfBW1;(Rd6K#3d#_v0Gc{DS-ROn ze(BCdiG=k97!u28tHA&uy`g2v)BYU193ie2PL5W{TY#hW|E4cGgikpJ@Sj!O*rC*4 zPe_en&zFQNs6pVO90Gc@^HWM6hMv9a73(W!JJp-d0WLGAP!l2PwZwa_H&!kza zx7T<^D)T%&ri6TTa-%xull-3TjPT)Yij0vxnt5Pgy(Tn73Oi{i7F;~=Xzl}Ta%l9R z_Xfx1h(q-rw+37#U>S4cZS4%)LaBF|Pp^b*8%4+TTs`A#Q zx=Q_Lc7WrV*{I%D>>BTEp!p$|MiCSM^ttD+koSIJ>nLT|LY`#Ebi>q3H@M_mmaIWVs}#MRu=+|~Ry7DXp^>g#Q_AK4Y{e>(f@8{~lW z{dqNcE|AS#30FM%lyzZdKvD1@_~YmE zx_Z}BKOBh*h3(4}qPC**sXO{I^3+iyQU6oo!@=q^&t!I-4)Ipop zyb1+keOfi^tckIC-|Idv-!R-2H-_N!Eq7WgO6#o`>lIzE%%B zTS_BnO}JF@8hf)PCz26^Q*AfX>7n+HR#k|@9=(U>M4`dXyGp4jb9(w|#R#|9AV**l z--;{*BN*4!h5rw)9xK)c@cfW@J#6^g%ZvPx!#LYoUC5$s9nynt)`o>1dqMjw5rj&g zUKVJp`79Bljz^YT>aHj9XrNY5^C|`=CDzYN&mx!qvmJv`kKZdq`^7VA-=Ip-p00?6 zDn*O7GJn+{JpaEGrbPoozd=uD^}g?Z3R$M>G_W7L|F4R0{n-8Kj1ww#h_*8Sc!}@3 z|EKfxwG!{YBofg*qCEdoCJWu;G1x~69T`8L(tpiseOp76#C1B$gp#<>R_0&-)#q>J zxlRxIM-2@n0HW;}tbPSO=wD7^>$M+@1kI&OM{z;ET&)G5f;A>~Utj>O0 z4wOE7T1Q3cvuG>xmsdbf(Qm4?r$_(eW;(rFkHHmRyZBB`{mPnts)K*Sfa>5hsYMC2 zXe;x_RYYU>u7lr~YyS-siY=q<82k=d?E8DZ`zvsyv3|y$Pj4+TB&q)1_h~Nu-Olo( zF^rHL!SEZGVa`VGF!b}^aG(e>+K$1EU;8>uZ+`^~DvqIF_opc!iZG+C%%2>|+rD5q z?f>`a^S_}%(Mq%(gN<|mzfUs1?J^Y2JWa(=G!t!Q{@MTO{MSh*8Uvcwon{3nUWc|a d|72ajf64MRuVUk%{7@l(ZsGv|UizrN{s*+TgF^rS literal 0 HcmV?d00001 diff --git a/common-be/src/test/resources/csarSizeReducer/dummy.csar b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar similarity index 100% rename from common-be/src/test/resources/csarSizeReducer/dummy.csar rename to common-be/src/test/resources/csarSizeReducer/dummyToReduce.csar diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip b/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip new file mode 100644 index 0000000000000000000000000000000000000000..fecb45aaafc52eda8a78d9e646cf9e4366808f6f GIT binary patch literal 23905 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 riT|Rk|M>y`* { + final ArtifactInfo artifactInfo = onboardPackageInfo.getArtifactInfo(); + validatePackageSecurity(originalOnboardPackage, artifactInfo).ifPresent(packageSignatureResponse -> { if (packageSignatureResponse.hasErrors()) { uploadFileResponse.addStructureErrors(packageSignatureResponse.getErrors()); } @@ -74,11 +76,11 @@ public class OrchestrationTemplateCSARHandler extends BaseOrchestrationTemplateH return uploadFileResponse; } - private Optional validatePackageSecurity(final OnboardSignedPackage originalOnboardPackage) { + private Optional validatePackageSecurity(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) { final UploadFileResponse uploadFileResponseDto = new UploadFileResponse(); try { final CsarSecurityValidator csarSecurityValidator = new CsarSecurityValidator(); - if (!csarSecurityValidator.verifyPackageSignature(originalOnboardPackage)) { + if (!csarSecurityValidator.verifyPackageSignature(signedPackage, artifactInfo)) { final ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, Messages.FAILED_TO_VERIFY_SIGNATURE.getErrorMessage()); logger.error(errorMessage.getMessage()); uploadFileResponseDto.addStructureError(SdcCommon.UPLOAD_FILE, errorMessage); @@ -86,7 +88,7 @@ public class OrchestrationTemplateCSARHandler extends BaseOrchestrationTemplateH } } catch (final SecurityManagerException e) { final ErrorMessage errorMessage = new ErrorMessage(ErrorLevel.ERROR, e.getMessage()); - logger.error("Could not validate package signature {}", originalOnboardPackage.getFilename(), e); + logger.error("Could not validate package signature {}", signedPackage.getFilename(), e); uploadFileResponseDto.addStructureError(SdcCommon.UPLOAD_FILE, errorMessage); return Optional.of(uploadFileResponseDto); } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java index 81a17f333b..bf5abe3737 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java @@ -19,7 +19,8 @@ package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation; import java.util.Optional; -import org.openecomp.core.utilities.file.FileContentHandler; +import lombok.NoArgsConstructor; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; @@ -27,13 +28,11 @@ import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; /** * Validates the package security */ +@NoArgsConstructor public class CsarSecurityValidator { private SecurityManager securityManager = SecurityManager.getInstance(); - public CsarSecurityValidator() { - } - //for tests purpose CsarSecurityValidator(final SecurityManager securityManager) { this.securityManager = securityManager; @@ -45,15 +44,24 @@ public class CsarSecurityValidator { * @return true if signature verified * @throws SecurityManagerException when a certificate error occurs. */ - public boolean verifyPackageSignature(final OnboardSignedPackage signedPackage) throws SecurityManagerException { - final FileContentHandler fileContentHandler = signedPackage.getFileContentHandler(); - final byte[] signatureBytes = fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()); - final byte[] archiveBytes = fileContentHandler.getFileContent(signedPackage.getInternalPackageFilePath()); - byte[] certificateBytes = null; - final Optional certificateFilePath = signedPackage.getCertificateFilePath(); - if (certificateFilePath.isPresent()) { - certificateBytes = fileContentHandler.getFileContent(certificateFilePath.get()); + public boolean verifyPackageSignature(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) throws SecurityManagerException { + if (isArtifactInfoPresent(artifactInfo)) { + return securityManager.verifyPackageSignedData(signedPackage, artifactInfo); + } else { + final var fileContentHandler = signedPackage.getFileContentHandler(); + final byte[] signatureBytes = fileContentHandler.getFileContent(signedPackage.getSignatureFilePath()); + final byte[] archiveBytes = fileContentHandler.getFileContent(signedPackage.getInternalPackageFilePath()); + byte[] certificateBytes = null; + final Optional certificateFilePath = signedPackage.getCertificateFilePath(); + if (certificateFilePath.isPresent()) { + certificateBytes = fileContentHandler.getFileContent(certificateFilePath.get()); + } + return securityManager.verifySignedData(signatureBytes, certificateBytes, archiveBytes); } - return securityManager.verifySignedData(signatureBytes, certificateBytes, archiveBytes); } + + private boolean isArtifactInfoPresent(final ArtifactInfo artifactInfo) { + return artifactInfo != null && artifactInfo.getPath() != null; + } + } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java index d60b54b5e1..fec15b5fcc 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java @@ -19,14 +19,17 @@ */ package org.openecomp.sdc.vendorsoftwareproduct.security; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + import com.google.common.collect.ImmutableSet; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; @@ -48,22 +51,28 @@ import java.util.Collection; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipFile; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSProcessableFile; import org.bouncycastle.cms.CMSSignedData; -import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.OperatorCreationException; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; +import org.openecomp.sdc.common.errors.SdcRuntimeException; import org.openecomp.sdc.logging.api.Logger; import org.openecomp.sdc.logging.api.LoggerFactory; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; /** * This is temporary solution. When AAF provides functionality for verifying trustedCertificates, this class should be reviewed Class is responsible @@ -71,13 +80,12 @@ import org.openecomp.sdc.logging.api.LoggerFactory; */ public class SecurityManager { + public static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); + public static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); private static final String CERTIFICATE_DEFAULT_LOCATION = "cert"; - public static final Set ALLOWED_SIGNATURE_EXTENSIONS = ImmutableSet.of("cms"); - public static final Set ALLOWED_CERTIFICATE_EXTENSIONS = ImmutableSet.of("cert", "crt"); - private Logger logger = LoggerFactory.getLogger(SecurityManager.class); - private Set trustedCertificates = new HashSet<>(); - private Set trustedCertificatesFromPackage = new HashSet<>(); - private File certificateDirectory; + private static final Logger logger = LoggerFactory.getLogger(SecurityManager.class); + private static final String UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION = "Unexpected error occurred during signature validation!"; + private static final String COULD_NOT_VERIFY_SIGNATURE = "Could not verify signature!"; static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -85,6 +93,10 @@ public class SecurityManager { } } + private Set trustedCertificates = new HashSet<>(); + private Set trustedCertificatesFromPackage = new HashSet<>(); + private File certificateDirectory; + private SecurityManager() { certificateDirectory = this.getcertDirectory(System.getenv("SDC_CERT_DIR")); } @@ -98,21 +110,13 @@ public class SecurityManager { return SecurityManagerInstanceHolder.instance; } - /** - * Initialization on demand class / synchronized singleton pattern. - */ - private static class SecurityManagerInstanceHolder { - - private static final SecurityManager instance = new SecurityManager(); - } - /** * Checks the configured location for available trustedCertificates * * @return set of trustedCertificates * @throws SecurityManagerException */ - public Set getTrustedCertificates() throws SecurityManagerException, FileNotFoundException { + public Set getTrustedCertificates() throws SecurityManagerException { //if file number in certificate directory changed reload certs String[] certFiles = certificateDirectory.list(); if (certFiles == null) { @@ -145,43 +149,121 @@ public class SecurityManager { * @return true if signature verified * @throws SecurityManagerException */ - public boolean verifySignedData(final byte[] messageSyntaxSignature, final byte[] packageCert, final byte[] innerPackageFile) - throws SecurityManagerException { - try (ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature); final PEMParser pemParser = new PEMParser( - new InputStreamReader(signatureStream))) { + public boolean verifySignedData(final byte[] messageSyntaxSignature, + final byte[] packageCert, + final byte[] innerPackageFile) throws SecurityManagerException { + try (final ByteArrayInputStream signatureStream = new ByteArrayInputStream(messageSyntaxSignature); + final PEMParser pemParser = new PEMParser(new InputStreamReader(signatureStream))) { final Object parsedObject = pemParser.readObject(); if (!(parsedObject instanceof ContentInfo)) { throw new SecurityManagerException("Signature is not recognized"); } - final ContentInfo signature = ContentInfo.getInstance(parsedObject); - final CMSTypedData signedContent = new CMSProcessableByteArray(innerPackageFile); - final CMSSignedData signedData = new CMSSignedData(signedContent, signature); - final Collection signers = signedData.getSignerInfos().getSigners(); - final SignerInformation firstSigner = signers.iterator().next(); - final X509Certificate cert; - Collection certs; - if (packageCert == null) { - certs = signedData.getCertificates().getMatches(null); - cert = readSignCert(certs, firstSigner) - .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!")); - } else { - certs = parseCertsFromPem(packageCert); - cert = readSignCert(certs, firstSigner) - .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!")); + return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject))); + } catch (final IOException | CMSException e) { + logger.error(e.getMessage(), e); + throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } + } + + public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) + throws SecurityManagerException { + boolean fail = false; + final var fileContentHandler = signedPackage.getFileContentHandler(); + byte[] packageCert = null; + final Optional certificateFilePath = signedPackage.getCertificateFilePath(); + if (certificateFilePath.isPresent()) { + packageCert = fileContentHandler.getFileContent(certificateFilePath.get()); + } + final var path = artifactInfo.getPath(); + final var target = Path.of(path.toString() + "." + UUID.randomUUID()); + + try (final var signatureStream = new ByteArrayInputStream(fileContentHandler.getFileContent(signedPackage.getSignatureFilePath())); + final var pemParser = new PEMParser(new InputStreamReader(signatureStream))) { + final var parsedObject = pemParser.readObject(); + if (!(parsedObject instanceof ContentInfo)) { + fail = true; + throw new SecurityManagerException("Signature is not recognized"); } - trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner); - if (verifyCertificate(cert, getTrustedCertificates()) == null) { + + if (!findCSARandExtract(path, target)) { + fail = true; return false; } - return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); - } catch (OperatorCreationException | IOException | CMSException e) { + final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject))); + fail = !verify; + return verify; + } catch (final IOException e) { + fail = true; logger.error(e.getMessage(), e); - throw new SecurityManagerException("Unexpected error occurred during signature validation!", e); - } catch (GeneralSecurityException e) { - throw new SecurityManagerException("Could not verify signature!", e); + throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } catch (final CMSException e) { + fail = true; + throw new SecurityManagerException(COULD_NOT_VERIFY_SIGNATURE, e); + } catch (final SecurityManagerException e) { + fail = true; + throw e; + } finally { + deleteFile(target); + if (fail) { + deleteFile(path); + } + } + } + + private void deleteFile(final Path filePath) { + try { + Files.delete(filePath); + } catch (final IOException e) { + logger.warn("Failed to delete '{}' after verifying package signed data", filePath, e); } } + private boolean verify(final byte[] packageCert, final CMSSignedData signedData) throws SecurityManagerException { + final SignerInformation firstSigner = signedData.getSignerInfos().getSigners().iterator().next(); + final X509Certificate cert; + Collection certs; + if (packageCert == null) { + certs = signedData.getCertificates().getMatches(null); + cert = readSignCert(certs, firstSigner) + .orElseThrow(() -> new SecurityManagerException("No certificate found in cms signature that should contain one!")); + } else { + try { + certs = parseCertsFromPem(packageCert); + } catch (final IOException e) { + throw new SecurityManagerException("Failed to parse certificate from PEM", e); + } + cert = readSignCert(certs, firstSigner) + .orElseThrow(() -> new SecurityManagerException("No matching certificate found in certificate file that should contain one!")); + } + trustedCertificatesFromPackage = readTrustedCerts(certs, firstSigner); + if (verifyCertificate(cert, getTrustedCertificates()) == null) { + return false; + } + try { + return firstSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert)); + } catch (CMSException | OperatorCreationException e) { + throw new SecurityManagerException("Failed to verify package signed data", e); + } + } + + private boolean findCSARandExtract(final Path path, final Path target) throws IOException { + final AtomicBoolean found = new AtomicBoolean(false); + try (final var zf = new ZipFile(path.toString())) { + zf.entries().asIterator().forEachRemaining(entry -> { + final var entryName = entry.getName(); + if (!entry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) { + try { + Files.copy(zf.getInputStream(entry), target, REPLACE_EXISTING); + } catch (final IOException e) { + throw new SdcRuntimeException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + } + found.set(true); + } + }); + } + return found.get(); + } + private Optional readSignCert(final Collection certs, final SignerInformation firstSigner) { return certs.stream().filter(crt -> firstSigner.getSID().match(crt)).findAny().map(this::loadCertificate); } @@ -205,7 +287,7 @@ public class SecurityManager { return allCerts; } - private void processCertificateDir() throws SecurityManagerException, FileNotFoundException { + private void processCertificateDir() throws SecurityManagerException { if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) { logger.error("Issue with certificate directory, check if exists!"); return; @@ -253,8 +335,8 @@ public class SecurityManager { } } - private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set additionalCerts) - throws GeneralSecurityException, SecurityManagerException { + private PKIXCertPathBuilderResult verifyCertificate(final X509Certificate cert, + final Set additionalCerts) throws SecurityManagerException { if (null == cert) { throw new SecurityManagerException("The certificate is empty!"); } @@ -273,7 +355,11 @@ public class SecurityManager { intermediateCerts.add(additionalCert); } } - return verifyCertificate(cert, trustedRootCerts, intermediateCerts); + try { + return verifyCertificate(cert, trustedRootCerts, intermediateCerts); + } catch (final GeneralSecurityException e) { + throw new SecurityManagerException("Failed to verify certificate", e); + } } private PKIXCertPathBuilderResult verifyCertificate(X509Certificate cert, Set allTrustedRootCerts, @@ -325,4 +411,12 @@ public class SecurityManager { private boolean isSelfSigned(X509Certificate cert) { return cert.getIssuerDN().equals(cert.getSubjectDN()); } + + /** + * Initialization on demand class / synchronized singleton pattern. + */ + private static class SecurityManagerInstanceHolder { + + private static final SecurityManager instance = new SecurityManager(); + } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java index 5f5f9eb7dc..96d11eb148 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java @@ -19,6 +19,7 @@ package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -27,12 +28,21 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.Before; -import org.junit.Test; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.openecomp.sdc.be.csar.storage.ArtifactInfo; +import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager; @@ -40,37 +50,88 @@ import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; -public class CsarSecurityValidatorTest { +class CsarSecurityValidatorTest { - private static final String BASE_DIR = "/vspmanager.csar/"; + private static final String BASE_DIR = "/vspmanager.csar/signing/"; + private static final String DELIMITER = "---"; private CsarSecurityValidator csarSecurityValidator; @Mock - SecurityManager securityManager; + private SecurityManager securityManager; - @Before - public void setUp() { + @AfterEach + void tearDown() throws Exception { + restore(); + } + + private void restore() throws Exception { + final URI uri = CsarSecurityValidatorTest.class.getResource(BASE_DIR).toURI(); + final List list = Files.list(Path.of(uri.getPath())).filter(path -> path.toString().contains(DELIMITER)).collect(Collectors.toList()); + for (final Path path : list) { + final String[] split = path.toString().split(DELIMITER); + Files.move(path, Path.of(split[0]), REPLACE_EXISTING); + } + } + + @BeforeEach + public void setUp() throws Exception { initMocks(this); csarSecurityValidator = new CsarSecurityValidator(securityManager); + backup(); + } + + private void backup() throws Exception { + final URI uri = CsarSecurityValidatorTest.class.getResource(BASE_DIR).toURI(); + final List list = Files.list(Path.of(uri.getPath())).collect(Collectors.toList()); + for (final Path path : list) { + Files.copy(path, Path.of(path.toString() + DELIMITER + UUID.randomUUID()), REPLACE_EXISTING); + } } @Test - public void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException { - final byte[] packageBytes = getFileBytesOrFail("signing/signed-package.zip"); - final OnboardSignedPackage onboardSignedPackage = loadSignedPackage("signed-package.zip", + void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException, IOException { + final byte[] packageBytes = getFileBytesOrFail("signed-package.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package.zip", packageBytes, null); + when(securityManager.verifyPackageSignedData(any(OnboardSignedPackage.class), any(ArtifactInfo.class))).thenReturn(true); + final boolean isSignatureValid = csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + assertThat("Signature should be valid", isSignatureValid, is(true)); + } + + @Test + void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package-tampered-data.zip", packageBytes, null); + //no mocked securityManager + csarSecurityValidator = new CsarSecurityValidator(); + Assertions.assertThrows(SecurityManagerException.class, () -> { + csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + }); + } + + @Test + void isSignatureValidTestCorrectStructureAndValidSignatureExistsArtifactStorageManagerIsEnabled() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithoutArtifactInfo("signed-package.zip", packageBytes, null); when(securityManager.verifySignedData(any(), any(), any())).thenReturn(true); - final boolean isSignatureValid = csarSecurityValidator.verifyPackageSignature(onboardSignedPackage); + final boolean isSignatureValid = csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + assertThat("Signature should be valid", isSignatureValid, is(true)); } - @Test(expected = SecurityManagerException.class) - public void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws SecurityManagerException { - final byte[] packageBytes = getFileBytesOrFail("signing/signed-package-tampered-data.zip"); - final OnboardSignedPackage onboardSignedPackage = loadSignedPackage("signed-package-tampered-data.zip", + @Test + void isSignatureValidTestCorrectStructureAndNotValidSignatureExistsArtifactStorageManagerIsEnabled() throws SecurityManagerException { + final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithoutArtifactInfo("signed-package-tampered-data.zip", packageBytes, null); //no mocked securityManager csarSecurityValidator = new CsarSecurityValidator(); - csarSecurityValidator.verifyPackageSignature(onboardSignedPackage); + Assertions.assertThrows(SecurityManagerException.class, () -> { + csarSecurityValidator + .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); + }); } private byte[] getFileBytesOrFail(final String path) { @@ -87,8 +148,21 @@ public class CsarSecurityValidatorTest { CsarSecurityValidatorTest.class.getResource(BASE_DIR + path).toURI())); } - private OnboardSignedPackage loadSignedPackage(final String packageName, final byte[] packageBytes, - CnfPackageValidator cnfPackageValidator) { + private OnboardPackageInfo loadSignedPackageWithArtifactInfo(final String packageName, final byte[] packageBytes, + final CnfPackageValidator cnfPackageValidator) { + final OnboardingPackageProcessor onboardingPackageProcessor = + new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, + new PersistentStorageArtifactInfo(Path.of("src/test/resources/vspmanager.csar/signing/signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + if (onboardPackageInfo == null) { + fail("Unexpected error. Could not load original package"); + } + + return onboardPackageInfo; + } + + private OnboardPackageInfo loadSignedPackageWithoutArtifactInfo(final String packageName, final byte[] packageBytes, + final CnfPackageValidator cnfPackageValidator) { final OnboardingPackageProcessor onboardingPackageProcessor = new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, null); final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); @@ -96,6 +170,6 @@ public class CsarSecurityValidatorTest { fail("Unexpected error. Could not load original package"); } - return (OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(); + return onboardPackageInfo; } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java index b5479e0868..6dc5517c45 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java @@ -27,14 +27,20 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; +import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; +import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; +import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; -public class SecurityManagerTest { +class SecurityManagerTest { private File certDir; private String cerDirPath = "/tmp/cert/"; @@ -71,7 +77,7 @@ public class SecurityManagerTest { } @Test - public void testGetCertificates() throws IOException, SecurityManagerException, URISyntaxException { + void testGetCertificates() throws IOException, SecurityManagerException, URISyntaxException { File newFile = prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "/root-certificate.pem"); assertEquals(1, securityManager.getTrustedCertificates().size()); newFile.delete(); @@ -79,13 +85,13 @@ public class SecurityManagerTest { } @Test - public void testGetCertificatesNoDirectory() throws IOException, SecurityManagerException { + void testGetCertificatesNoDirectory() throws IOException, SecurityManagerException { certDir.delete(); assertEquals(0, securityManager.getTrustedCertificates().size()); } @Test - public void testGetCertificatesException() throws IOException, SecurityManagerException { + void testGetCertificatesException() throws IOException, SecurityManagerException { File newFile = new File(cerDirPath + "root-certificate.pem"); newFile.createNewFile(); Assertions.assertThrows(SecurityManagerException.class, () -> { @@ -97,9 +103,9 @@ public class SecurityManagerTest { } @Test - public void testGetCertificatesUpdated() throws IOException, SecurityManagerException, URISyntaxException { + void testGetCertificatesUpdated() throws IOException, SecurityManagerException, URISyntaxException { File newFile = prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "root-certificate.pem"); - assertTrue(securityManager.getTrustedCertificates().size() == 1); + assertEquals(1, securityManager.getTrustedCertificates().size()); File otherNewFile = prepareCertFiles("/cert/package-certificate.pem", cerDirPath + "package-certificate.pem"); assertEquals(2, securityManager.getTrustedCertificates().size()); otherNewFile.delete(); @@ -109,7 +115,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/2-file-signed-package/dummyPnfv4.cms"); byte[] archive = readAllBytes("/cert/2-file-signed-package/dummyPnfv4.csar"); @@ -117,7 +123,22 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertNotIncludedIntoSignatureButExpected() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertIncludedIntoSignatureArtifactStorageManagerIsEnabled() + throws IOException, URISyntaxException, SecurityManagerException { + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/2-file-signed-package/2-file-signed-package.zip"); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("2-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), + new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + } + + @Test + void verifySignedDataTestCertNotIncludedIntoSignatureButExpected() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -128,7 +149,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); byte[] archive = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.csar"); @@ -137,7 +158,22 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIntermediateNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertNotIncludedIntoSignatureArtifactStorageManagerIsEnabled() + throws IOException, URISyntaxException, SecurityManagerException { + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/3-file-signed-package/3-file-signed-package.zip"); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("3-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), + new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip"))); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + } + + @Test + void verifySignedDataTestCertIntermediateNotIncludedIntoSignature() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/package2.cert", cerDirPath + "signing-ca2.crt"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -147,7 +183,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertWrongIntermediate() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertWrongIntermediate() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); @@ -160,7 +196,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertIncludedIntoSignatureWithWrongIntermediateInDirectory() + void verifySignedDataTestCertIncludedIntoSignatureWithWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); @@ -170,7 +206,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestCertWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestCertWrongIntermediateInDirectory() throws IOException, URISyntaxException, SecurityManagerException { prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); prepareCertFiles("/cert/signing-ca1.crt", cerDirPath + "signing-ca1.crt"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -180,7 +216,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestWrongCertificate() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestWrongCertificate() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root-certificate.pem", cerDirPath + "root-certificate.cert"); byte[] signature = readAllBytes("/cert/3-file-signed-package/dummyPnfv4.cms"); @@ -192,7 +228,7 @@ public class SecurityManagerTest { } @Test - public void verifySignedDataTestChangedArchive() throws IOException, URISyntaxException, SecurityManagerException { + void verifySignedDataTestChangedArchive() throws IOException, URISyntaxException, SecurityManagerException { Assertions.assertThrows(SecurityManagerException.class, () -> { prepareCertFiles("/cert/root.cert", cerDirPath + "root.cert"); byte[] signature = readAllBytes("/cert/tampered-signed-package/dummyPnfv4.cms"); diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip new file mode 100644 index 0000000000000000000000000000000000000000..be48e8a6743b34adfa82718bb67ee464ceda8b86 GIT binary patch literal 3154 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 literal 0 HcmV?d00001 diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip new file mode 100644 index 0000000000000000000000000000000000000000..7f2eacbe10dfa07d5da6c6dc79dd1fcea18248d3 GIT binary patch literal 3446 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 literal 0 HcmV?d00001 -- 2.16.6