Update vulnerable package dependencies
[sdc.git] / openecomp-be / lib / openecomp-tosca-lib / src / main / java / org / openecomp / sdc / tosca / csar / SOL004ManifestOnboarding.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation.
4  *  Modification Copyright (C) 2021 Nokia.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21 package org.openecomp.sdc.tosca.csar;
22
23 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_6_1;
24 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_7_1;
25 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA_LIMIT_VERSION_3;
26 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
27 import static org.openecomp.sdc.tosca.csar.ManifestTokenType.COMPATIBLE_SPECIFICATION_VERSIONS;
28
29 import com.vdurmont.semver4j.Semver;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.Optional;
35 import org.apache.commons.lang3.StringUtils;
36 import org.openecomp.sdc.common.errors.Messages;
37
38 /**
39  * Processes a SOL004 Manifest.
40  */
41 public class SOL004ManifestOnboarding extends AbstractOnboardingManifest {
42
43     private int maxAllowedMetaEntries;
44
45     @Override
46     protected void processMetadata() {
47         Optional<String> currentLine = getCurrentLine();
48         //SOL004 #4.3.2: The manifest file shall start with the package metadata
49         if (!currentLine.isPresent() || !isMetadata(currentLine.get())) {
50             reportError(Messages.MANIFEST_START_METADATA);
51             continueToProcess = false;
52             return;
53         }
54         while (continueToProcess) {
55             currentLine = readNextNonEmptyLine();
56             if (!currentLine.isPresent()) {
57                 continueToProcess = validateMetadata();
58                 return;
59             }
60             final String metadataLine = currentLine.get();
61             final String metadataEntry = readEntryName(metadataLine).orElse(null);
62             if (!isMetadataEntry(metadataEntry)) {
63                 if (metadata.size() < getMaxAllowedManifestMetaEntries()) {
64                     reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, metadataLine);
65                     continueToProcess = false;
66                     return;
67                 }
68                 continueToProcess = validateMetadata();
69                 return;
70             }
71             final String metadataValue = readEntryValue(metadataLine).orElse(null);
72             addToMetadata(metadataEntry, metadataValue);
73             continueToProcess = isValid();
74         }
75         readNextNonEmptyLine();
76     }
77
78     @Override
79     protected void processBody() {
80         while (continueToProcess) {
81             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
82             if (manifestTokenType == null) {
83                 getCurrentLine().ifPresent(line -> reportInvalidLine());
84                 break;
85             }
86             switch (manifestTokenType) {
87                 case CMS_BEGIN:
88                     readCmsSignature();
89                     break;
90                 case NON_MANO_ARTIFACT_SETS:
91                     processNonManoArtifactEntry();
92                     break;
93                 case SOURCE:
94                     processSource();
95                     break;
96                 default:
97                     getCurrentLine().ifPresent(line -> reportInvalidLine());
98                     continueToProcess = false;
99                     break;
100             }
101         }
102     }
103
104     /**
105      * Processes the {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS} entry.
106      */
107     private void processNonManoArtifactEntry() {
108         Optional<String> currentLine = readNextNonEmptyLine();
109         while (currentLine.isPresent()) {
110             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
111             if (manifestTokenType == ManifestTokenType.CMS_BEGIN) {
112                 return;
113             }
114             if (manifestTokenType != null) {
115                 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, manifestTokenType.getToken());
116                 continueToProcess = false;
117                 return;
118             }
119             final String nonManoKey = readCurrentEntryName().orElse(null);
120             if (nonManoKey == null) {
121                 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, currentLine.get());
122                 continueToProcess = false;
123                 return;
124             }
125             readNextNonEmptyLine();
126             final List<String> nonManoSourceList = readNonManoSourceList();
127             if (!isValid()) {
128                 continueToProcess = false;
129                 return;
130             }
131             if (nonManoSourceList.isEmpty()) {
132                 reportError(Messages.MANIFEST_EMPTY_NON_MANO_KEY, nonManoKey);
133                 continueToProcess = false;
134                 return;
135             }
136             if (nonManoSources.get(nonManoKey) == null) {
137                 nonManoSources.put(nonManoKey, nonManoSourceList);
138             } else {
139                 nonManoSources.get(nonManoKey).addAll(nonManoSourceList);
140             }
141             currentLine = getCurrentLine();
142         }
143     }
144
145     /**
146      * Processes {@link ManifestTokenType#SOURCE} entries in {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS}.
147      *
148      * @return A list of sources paths
149      */
150     private List<String> readNonManoSourceList() {
151         final List<String> nonManoSourceList = new ArrayList<>();
152         while (getCurrentLine().isPresent()) {
153             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
154             if (manifestTokenType != ManifestTokenType.SOURCE) {
155                 break;
156             }
157             final String value = readCurrentEntryValue().orElse(null);
158             if (!StringUtils.isEmpty(value)) {
159                 nonManoSourceList.add(value);
160             } else {
161                 reportError(Messages.MANIFEST_EMPTY_NON_MANO_SOURCE);
162                 break;
163             }
164             readNextNonEmptyLine();
165         }
166         return nonManoSourceList;
167     }
168
169     /**
170      * Reads a manifest CMS signature.
171      */
172     private void readCmsSignature() {
173         if (cmsSignature != null) {
174             reportError(Messages.MANIFEST_SIGNATURE_DUPLICATED);
175             continueToProcess = false;
176             return;
177         }
178         final StringBuilder cmsSignatureBuilder = new StringBuilder();
179         cmsSignatureBuilder.append(currentLine).append("\n");
180         Optional<String> currentLine = readNextNonEmptyLine();
181         if (!getCurrentLine().isPresent()) {
182             return;
183         }
184         while (currentLine.isPresent()) {
185             if (detectLineEntry().orElse(null) == ManifestTokenType.CMS_END) {
186                 cmsSignatureBuilder.append(currentLine.get());
187                 break;
188             }
189             cmsSignatureBuilder.append(currentLine.get()).append("\n");
190             currentLine = readNextNonEmptyLine();
191         }
192         if (currentLine.isPresent()) {
193             cmsSignature = cmsSignatureBuilder.toString();
194             readNextNonEmptyLine();
195         }
196         if (getCurrentLine().isPresent()) {
197             reportError(Messages.MANIFEST_SIGNATURE_LAST_ENTRY);
198             continueToProcess = false;
199         }
200     }
201
202     /**
203      * Detects the current line manifest token.
204      *
205      * @return the current line manifest token.
206      */
207     private Optional<ManifestTokenType> detectLineEntry() {
208         final Optional<String> currentLine = getCurrentLine();
209         if (currentLine.isPresent()) {
210             final String line = currentLine.get();
211             final String entry = readEntryName(line).orElse(null);
212             if (entry == null) {
213                 return ManifestTokenType.parse(line);
214             } else {
215                 return ManifestTokenType.parse(entry);
216             }
217         }
218         return Optional.empty();
219     }
220
221     /**
222      * Validates the manifest metadata content, reporting errors found.
223      *
224      * @return {@code true} if the metadata content is valid, {@code false} otherwise.
225      */
226     private boolean validateMetadata() {
227         if (metadata.isEmpty()) {
228             reportError(Messages.MANIFEST_NO_METADATA);
229             return false;
230         }
231         String key = metadata.keySet().stream().filter(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k)).findFirst().orElse(null);
232         final ManifestTokenType firstManifestEntryTokenType = ManifestTokenType.parse(key).orElse(null);
233         if (firstManifestEntryTokenType == null) {
234             reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, key);
235             return false;
236         }
237         for (final Entry<String, String> manifestEntry : metadata.entrySet()) {
238             final ManifestTokenType manifestEntryTokenType = ManifestTokenType.parse(manifestEntry.getKey()).orElse(null);
239             if (manifestEntryTokenType == null) {
240                 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, manifestEntry.getKey());
241                 return false;
242             }
243             if ((firstManifestEntryTokenType.isMetadataVnfEntry() && !manifestEntryTokenType.isMetadataVnfEntry()) || (
244                 firstManifestEntryTokenType.isMetadataPnfEntry() && !manifestEntryTokenType.isMetadataPnfEntry())) {
245                 reportError(Messages.MANIFEST_METADATA_UNEXPECTED_ENTRY_TYPE);
246                 return false;
247             }
248         }
249         if (metadata.entrySet().size() != getMaxAllowedManifestMetaEntries()) {
250             reportError(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT, getMaxAllowedManifestMetaEntries());
251             return false;
252         }
253         return true;
254     }
255
256     /**
257      * Processes a Manifest {@link ManifestTokenType#SOURCE} entry.
258      */
259     private void processSource() {
260         final Optional<String> currentLine = getCurrentLine();
261         if (!currentLine.isPresent()) {
262             return;
263         }
264         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
265         if (manifestTokenType != ManifestTokenType.SOURCE) {
266             return;
267         }
268         final String sourceLine = currentLine.get();
269         final String sourcePath = readEntryValue(sourceLine).orElse(null);
270         if (sourcePath == null) {
271             reportError(Messages.MANIFEST_EXPECTED_SOURCE_PATH);
272             return;
273         }
274         sources.add(sourcePath);
275         readNextNonEmptyLine();
276         readAlgorithmEntry(sourcePath);
277         readSignatureEntry(sourcePath);
278     }
279
280     /**
281      * Processes entries  {@link ManifestTokenType#ALGORITHM} and {@link ManifestTokenType#HASH} of a {@link ManifestTokenType#SOURCE} entry.
282      *
283      * @param sourcePath the source path related to the algorithm entry.
284      */
285     private void readAlgorithmEntry(final String sourcePath) {
286         Optional<String> currentLine = getCurrentLine();
287         if (!currentLine.isPresent()) {
288             return;
289         }
290         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
291         if (manifestTokenType == ManifestTokenType.HASH) {
292             reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_BEFORE_HASH);
293             continueToProcess = false;
294             return;
295         }
296         if (manifestTokenType != ManifestTokenType.ALGORITHM) {
297             return;
298         }
299         final String algorithmLine = currentLine.get();
300         final String algorithmType = readEntryValue(algorithmLine).orElse(null);
301         if (algorithmType == null) {
302             reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_VALUE);
303             continueToProcess = false;
304             return;
305         }
306         currentLine = readNextNonEmptyLine();
307         if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.HASH) {
308             reportError(Messages.MANIFEST_EXPECTED_HASH_ENTRY);
309             continueToProcess = false;
310             return;
311         }
312         final String hashLine = currentLine.get();
313         final String hash = readEntryValue(hashLine).orElse(null);
314         if (hash == null) {
315             reportError(Messages.MANIFEST_EXPECTED_HASH_VALUE);
316             continueToProcess = false;
317             return;
318         }
319         sourceAndChecksumMap.put(sourcePath, new AlgorithmDigest(algorithmType, hash));
320         readNextNonEmptyLine();
321     }
322
323     /**
324      * Processes entries  {@link ManifestTokenType#SIGNATURE} and {@link ManifestTokenType#CERTIFICATE} of a {@link ManifestTokenType#SOURCE} entry.
325      *
326      * @param sourcePath the source path related to the algorithm entry.
327      */
328     private void readSignatureEntry(final String sourcePath) {
329         Optional<String> currentLine = getCurrentLine();
330         if (!currentLine.isPresent()) {
331             return;
332         }
333         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
334         if (manifestTokenType == ManifestTokenType.CERTIFICATE) {
335             reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_BEFORE_CERTIFICATE);
336             continueToProcess = false;
337             return;
338         }
339         if (manifestTokenType != ManifestTokenType.SIGNATURE) {
340             return;
341         }
342         final String signatureLine = currentLine.get();
343         final String signatureFile = readEntryValue(signatureLine).orElse(null);
344         if (signatureFile == null) {
345             reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_VALUE);
346             continueToProcess = false;
347             return;
348         }
349         currentLine = readNextNonEmptyLine();
350         if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.CERTIFICATE) {
351             sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, null));
352             return;
353         }
354         final String certLine = currentLine.get();
355         final String certFile = readEntryValue(certLine).orElse(null);
356         if (certFile == null) {
357             reportError(Messages.MANIFEST_EXPECTED_CERTIFICATE_VALUE);
358             continueToProcess = false;
359             return;
360         }
361         sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, certFile));
362         readNextNonEmptyLine();
363     }
364
365     private int getMaxAllowedManifestMetaEntries() {
366         if (maxAllowedMetaEntries == 0) {
367             boolean isVersion3 =
368                 metadata.containsKey(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()) && !getHighestCompatibleVersion().isLowerThan(ETSI_VERSION_2_7_1);
369             //Both PNF and VNF share attribute COMPATIBLE_SPECIFICATION_VERSIONS
370             if (isVersion3) {
371                 maxAllowedMetaEntries = metadata.keySet().stream().anyMatch(
372                     k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k) && isMetadataEntry(k) && ManifestTokenType.parse(k).get()
373                         .isMetadataPnfEntry()) ? MANIFEST_PNF_METADATA_LIMIT_VERSION_3 : MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
374             } else {
375                 maxAllowedMetaEntries = MAX_ALLOWED_MANIFEST_META_ENTRIES;
376             }
377         }
378         return maxAllowedMetaEntries;
379     }
380
381     private Semver getHighestCompatibleVersion() {
382         return Arrays.asList(metadata.get(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()).split(",")).stream().map(Semver::new)
383             .max((v1, v2) -> v1.compareTo(v2)).orElse(new Semver(ETSI_VERSION_2_6_1));
384     }
385 }