diff options
Diffstat (limited to 'csarvalidation/src')
17 files changed, 934 insertions, 57 deletions
diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java b/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java index ed53fc8..448469d 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/CSARArchive.java @@ -15,23 +15,18 @@ */ package org.onap.cvc.csar; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.Optional; import org.apache.commons.io.FileUtils; import org.yaml.snakeyaml.Yaml; @@ -48,11 +43,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class CSARArchive implements AutoCloseable { public static final String SOL0004_2_4_1 = "V2.4.1 (2018-02)"; - public String getSOL004Version() { return SOL0004_2_4_1; } + private FileArchive.Workspace workspace; protected Path tempDir; public static final String TEMP_DIR = "/tmp"; @@ -124,6 +119,10 @@ public class CSARArchive implements AutoCloseable { public static final String CSAR_Archive = "CSAR Archive"; + public FileArchive.Workspace getWorkspace() { + return this.workspace; + } + public enum Mode { WITH_TOSCA_META_DIR, WITHOUT_TOSCA_META_DIR @@ -260,7 +259,6 @@ public class CSARArchive implements AutoCloseable { this.lineNumber = lineNo; this.file = file; - //this.message = "More than one file exists in the archive root for " + fileName + ", It should be only one. Files: " + files; } } @@ -298,7 +296,6 @@ public class CSARArchive implements AutoCloseable { this.message += ". " + message; } - this.file = file; this.lineNumber = lineNo; } } @@ -878,41 +875,6 @@ public class CSARArchive implements AutoCloseable { this.manifest = manifest; } - private void unzip(String csarPath, Path destination) throws IOException { - File csarFile = new File(csarPath); - - if (!csarFile.exists()) { - throw new RuntimeException(csarPath + " does not exist"); - } - - try (ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(csarFile)))){ - - ZipEntry entry; - while ((entry = zipInputStream.getNextEntry()) != null) { - - File filePath = new File(destination + File.separator + entry.getName()); - - if(entry.isDirectory()){ - filePath.mkdirs(); - } else { - extract(zipInputStream, filePath); - } - } - } - } - - private void extract(ZipInputStream csar, File filePath) throws IOException { - byte[] buffer = new byte[2048]; - try (FileOutputStream fos = new FileOutputStream(filePath); - BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) { - - int len; - while ((len = csar.read(buffer)) > 0) { - bos.write(buffer, 0, len); - } - } - } - public String getProductName() { if (this.toscaMeta.getMode().equals(Mode.WITH_TOSCA_META_DIR)) { @@ -1317,11 +1279,14 @@ public class CSARArchive implements AutoCloseable { } public void init(String csarPath) throws IOException { - this.tempDir = Paths.get(TEMP_DIR + File.separator + System.currentTimeMillis()); - this.tempDir.toFile().mkdirs(); + this.workspace = new FileArchive(TEMP_DIR).unpack(csarPath); - this.unzip(csarPath, this.tempDir); + final Optional<Path> pathToCsarFolder = workspace.getPathToCsarFolder(); + if (pathToCsarFolder.isPresent()) { + this.tempDir = pathToCsarFolder.get(); + } } + public void parse() throws IOException { //Find out the mode of CSAR this.setMode(); @@ -1338,7 +1303,10 @@ public class CSARArchive implements AutoCloseable { public void cleanup() throws IOException { //remove temp dir - FileUtils.deleteDirectory(this.tempDir.toFile()); + final Optional<Path> rootFolder = workspace.getRootFolder(); + if(rootFolder.isPresent()) { + FileUtils.deleteDirectory(rootFolder.get().toFile()); + } } public File getFileFromCsar(String path) { @@ -1347,8 +1315,6 @@ public class CSARArchive implements AutoCloseable { @Override public void close() throws Exception { - if(this.tempDir != null){ - cleanup(); - } + cleanup(); } } diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/FileArchive.java b/csarvalidation/src/main/java/org/onap/cvc/csar/FileArchive.java new file mode 100644 index 0000000..86904b1 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/FileArchive.java @@ -0,0 +1,215 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + + + +public class FileArchive { + + private static final String ZIP_POSTFIX = "zip"; + private static final String CSAR_POSTFIX = ".csar"; + private static final String CERT_POSTFIX = ".cert"; + private static final String CMS_POSTFIX = ".cms"; + private final String tempDir; + + FileArchive(String tempDir){ + this.tempDir = tempDir; + } + + Workspace unpack(String pathToFile) throws IOException { + File fileArchive = new File(pathToFile); + if (!fileArchive.exists()) { + throw new IllegalArgumentException(String.format("%s does not exist", fileArchive.getName())); + } + + String path = String.format("%s%s%d", tempDir, File.separator, System.currentTimeMillis()); + Optional<Path> workspaceFolderPath = createWorkspaceFolder(path); + if(workspaceFolderPath.isPresent()) { + final Path destination = workspaceFolderPath.get(); + unzip(fileArchive, destination); + + if (pathToFile.endsWith(ZIP_POSTFIX)) { + return createZipWorkspace(path, destination); + }else { + return Workspace.forCsar(destination); + } + } + + return Workspace.empty(); + } + + private Workspace createZipWorkspace(String path, Path workspaceFolderPath) throws IOException { + + Optional<Path> pathToCsarFile = findFile(workspaceFolderPath, CSAR_POSTFIX); + Optional<Path> pathToCertFile = findFile(workspaceFolderPath, CERT_POSTFIX); + Optional<Path> pathToCmsFile = findFile(workspaceFolderPath, CMS_POSTFIX); + + Path workspaceCsarPath = new File(String.format("%s%scsar", path, File.separator)).toPath(); + if (pathToCsarFile.isPresent()) { + final Path csarFilePath = pathToCsarFile.get(); + unzip(csarFilePath.toFile(), workspaceCsarPath); + + return Workspace.forZip( + workspaceFolderPath, + workspaceCsarPath, + pathToCertFile.orElse(null), + pathToCmsFile.orElse(null), + csarFilePath + ); + } + + + return Workspace.forZip(workspaceFolderPath); + } + + private Optional<Path> findFile(Path workspaceFolderPath, String filePostfix) throws IOException { + try(Stream<Path> files = Files.find( + workspaceFolderPath, + 1, + (p, b)->p.getFileName().toString().endsWith(filePostfix))){ + return files.findFirst(); + } + } + + private Optional<Path> createWorkspaceFolder(String path) { + Path destination = Paths.get(path); + if(destination.toFile().mkdirs()){ + return Optional.of(destination); + } + + return Optional.empty(); + } + + private void unzip(File file, Path destination) throws IOException { + + try (ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)))){ + + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + + File filePath = new File(destination + File.separator + entry.getName()); + + if(entry.isDirectory()){ + filePath.mkdirs(); + } else { + extract(zipInputStream, filePath); + } + } + } + } + + private void extract(ZipInputStream csar, File filePath) throws IOException { + byte[] buffer = new byte[2048]; + try (FileOutputStream fos = new FileOutputStream(filePath); + BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) { + + int len; + while ((len = csar.read(buffer)) > 0) { + bos.write(buffer, 0, len); + } + } + } + + public static class Workspace{ + private boolean isZip; + private Path rootFolder; + private Path pathToCsarFolder; + private Path certFile; + private Path cmsFile; + private Path csarFile; + + private Workspace(boolean isZip, Path rootFolder, + Path pathToCsarFolder, + Path certFile, Path cmsFile, + Path csarFile) { + this.isZip = isZip; + this.rootFolder = rootFolder; + this.pathToCsarFolder = pathToCsarFolder; + this.certFile = certFile; + this.cmsFile = cmsFile; + this.csarFile = csarFile; + } + + private Workspace() { + } + + private Workspace(boolean isZip, Path rootFolder){ + this.isZip = isZip; + this.rootFolder = rootFolder; + this.pathToCsarFolder = rootFolder; + } + + static Workspace empty(){ + return new Workspace(); + } + + static Workspace forCsar(Path workspaceFolder) { + return new Workspace(false, workspaceFolder); + } + + static Workspace forZip(Path rootFolder) { + return new Workspace(true, rootFolder); + } + + + static Workspace forZip(Path rootFolder, Path pathToCsarWorkspace, + Path certFile, Path cmsFile, Path csarFile) { + return new Workspace(true, rootFolder, pathToCsarWorkspace, certFile, cmsFile, csarFile); + } + + public boolean isZip() { + return isZip; + } + + public Optional<Path> getPathToCsarFolder() { + return Optional.ofNullable(pathToCsarFolder); + } + + public Optional<Path> getPathToCertFile() { + return Optional.ofNullable(certFile); + } + + public Optional<Path> getRootFolder() { + return Optional.ofNullable(rootFolder); + } + + public Optional<Path> getPathToCmsFile() { + return Optional.ofNullable(cmsFile); + } + + public Optional<Path> getPathToCsarFile() { + return Optional.ofNullable(csarFile); + } + } + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/ZipFileContentValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/ZipFileContentValidator.java new file mode 100644 index 0000000..801d8cf --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/ZipFileContentValidator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar; + +import java.util.ArrayList; +import java.util.List; + +public class ZipFileContentValidator { + + public static class CSARErrorCertMissing extends CSARArchive.CSARError { + CSARErrorCertMissing() { + super("0x1008"); + this.message = "Missing. Cert file is not available!"; + } + } + + public static class CSARErrorCMSMissing extends CSARArchive.CSARError { + CSARErrorCMSMissing() { + super("0x1009"); + this.message = "Missing. CMS file is not available!"; + } + } + + public List<CSARArchive.CSARError> validate(FileArchive.Workspace workspace){ + final ArrayList<CSARArchive.CSARError> retValue = new ArrayList<>(); + + if(!workspace.getPathToCertFile().isPresent()){ + retValue.add(new CSARErrorCertMissing()); + } + + if(!workspace.getPathToCmsFile().isPresent()){ + retValue.add(new CSARErrorCMSMissing()); + } + + return retValue; + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/VTPValidateCSARBase.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/VTPValidateCSARBase.java index d879ff3..bd771e0 100644 --- a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/VTPValidateCSARBase.java +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/VTPValidateCSARBase.java @@ -16,20 +16,24 @@ package org.onap.cvc.csar.cc; -import java.util.ArrayList; -import java.util.List; - import org.onap.cli.fw.cmd.OnapCommand; import org.onap.cli.fw.error.OnapCommandException; import org.onap.cli.fw.error.OnapCommandExecutionFailed; import org.onap.cvc.csar.CSARArchive; import org.onap.cvc.csar.CSARArchive.CSARError; +import org.onap.cvc.csar.FileArchive; +import org.onap.cvc.csar.ZipFileContentValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + public abstract class VTPValidateCSARBase extends OnapCommand { protected static final Logger LOG = LoggerFactory.getLogger(VTPValidateCSARBase.class); + private final ZipFileContentValidator zipFileContentValidator = new ZipFileContentValidator(); + protected abstract void validateCSAR(CSARArchive csar) throws Exception; protected abstract String getVnfReqsNo(); @@ -43,7 +47,14 @@ public abstract class VTPValidateCSARBase extends OnapCommand { //execute try (CSARArchive csar = this.createArchiveInstance()){ + csar.init(path); + + FileArchive.Workspace workspace = csar.getWorkspace(); + if(workspace.isZip()) { + errors.addAll(zipFileContentValidator.validate(workspace)); + } + csar.parse(); errors.addAll(csar.getErrors()); diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965.java b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965.java new file mode 100644 index 0000000..ede1b6c --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar.cc.sol004; + + +import org.onap.cli.fw.error.OnapCommandException; +import org.onap.cli.fw.schema.OnapCommandSchema; +import org.onap.cvc.csar.CSARArchive; +import org.onap.cvc.csar.FileArchive; +import org.onap.cvc.csar.cc.VTPValidatePnfCSARBase; +import org.onap.cvc.csar.rsa.RSACertificateValidator; +import org.onap.cvc.csar.rsa.X509RsaCertification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.Optional; + +@OnapCommandSchema(schema = "vtp-validate-csar-r787965.yaml") +public class VTPValidateCSARR787965 extends VTPValidatePnfCSARBase { + + private static final Logger LOG = LoggerFactory.getLogger(VTPValidateCSARR787965.class); + + public static class CSARErrorInvalidSignature extends CSARArchive.CSARError { + CSARErrorInvalidSignature() { + super("0x3001"); + this.message = "Invalid CSAR signature!"; + } + } + + @Override + protected void validateCSAR(CSARArchive csar) throws OnapCommandException { + + try { + final RSACertificateValidator rsaCertificateValidator = new RSACertificateValidator(new X509RsaCertification()); + + FileArchive.Workspace workspace = csar.getWorkspace(); + final Optional<Path> pathToCsarFile = workspace.getPathToCsarFile(); + final Optional<Path> pathToCertFile = workspace.getPathToCertFile(); + final Optional<Path> pathToCmsFile = workspace.getPathToCmsFile(); + + if (workspace.isZip() && pathToCsarFile.isPresent() && pathToCertFile.isPresent() && pathToCmsFile.isPresent()) { + byte[] csarContent = Files.readAllBytes(pathToCsarFile.get()); + String signature = Base64.getEncoder().encodeToString(Files.readAllBytes(pathToCmsFile.get())); + String publicCertification = Base64.getEncoder().encodeToString(Files.readAllBytes(pathToCertFile.get())); + + if (!rsaCertificateValidator.isValid(csarContent, signature, publicCertification)) { + this.errors.add(new CSARErrorInvalidSignature()); + } + } + + } catch (Exception e) { + LOG.error("Internal VTPValidateCSARR787965 command error", e); + throw new OnapCommandException("0x3000", "Internal VTPValidateCSARR787965 command error. See logs."); + } + + } + + @Override + protected String getVnfReqsNo() { + return "R787965"; + } + + +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/RSACertificateValidator.java b/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/RSACertificateValidator.java new file mode 100644 index 0000000..022f697 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/RSACertificateValidator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar.rsa; + + +import java.security.PublicKey; + +public class RSACertificateValidator { + + private final X509RsaCertification x509RsaCertification; + + public RSACertificateValidator(X509RsaCertification x509RsaCertification) { + this.x509RsaCertification = x509RsaCertification; + } + + public boolean isValid(byte [] content, String signature, String publicCertificateContent) throws Exception { + + String publicCert = extractPublicKeyCertificate(publicCertificateContent); + final PublicKey publicKey = this.x509RsaCertification.generatePublicKey(publicCert); + + return this.x509RsaCertification.verify(content,signature,publicKey); + } + + private String extractPublicKeyCertificate(String publicCertificateContent) { + String publicCert = publicCertificateContent.replace("-----BEGIN CERTIFICATE-----\n", ""); + return publicCert.replace("-----END CERTIFICATE-----\n", ""); + } +} diff --git a/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/X509RsaCertification.java b/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/X509RsaCertification.java new file mode 100644 index 0000000..8395221 --- /dev/null +++ b/csarvalidation/src/main/java/org/onap/cvc/csar/rsa/X509RsaCertification.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar.rsa; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +public class X509RsaCertification { + + private static final Logger LOG = LoggerFactory.getLogger(X509RsaCertification.class); + + PublicKey generatePublicKey(String cert) throws CertificateException { + byte[] encodedCert = cert.getBytes(StandardCharsets.UTF_8); + byte[] decodedCert = Base64.decodeBase64(encodedCert); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + InputStream in = new ByteArrayInputStream(decodedCert); + X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in); + + LOG.info(String.format("Subject DN : %s", certificate.getSubjectDN().getName())); + LOG.info(String.format("Issuer : %s", certificate.getIssuerDN().getName())); + LOG.info(String.format("Not After: %s", certificate.getNotAfter())); + LOG.info(String.format("Not Before: %s", certificate.getNotBefore())); + LOG.info(String.format("version: %d", certificate.getVersion())); + LOG.info(String.format("serial number : %s", certificate.getSerialNumber())); + + return certificate.getPublicKey(); + } + + boolean verify(byte[] content, String signature, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature publicSignature = Signature.getInstance("SHA256withRSA"); + publicSignature.initVerify(publicKey); + publicSignature.update(content); + + byte[] signatureBytes = java.util.Base64.getDecoder().decode(signature); + + return publicSignature.verify(signatureBytes); + } +} diff --git a/csarvalidation/src/main/resources/META-INF/services/org.onap.cli.fw.cmd.OnapCommand b/csarvalidation/src/main/resources/META-INF/services/org.onap.cli.fw.cmd.OnapCommand index bbef237..21c6af1 100644 --- a/csarvalidation/src/main/resources/META-INF/services/org.onap.cli.fw.cmd.OnapCommand +++ b/csarvalidation/src/main/resources/META-INF/services/org.onap.cli.fw.cmd.OnapCommand @@ -46,4 +46,5 @@ org.onap.cvc.csar.cc.sol004.VTPValidateCSARR87234 org.onap.cvc.csar.cc.sol004.VTPValidateCSARR293901 org.onap.cvc.csar.cc.sol004.VTPValidateCSARR146092 org.onap.cvc.csar.cc.sol004.VTPValidateCSARR57019 +org.onap.cvc.csar.cc.sol004.VTPValidateCSARR787965 diff --git a/csarvalidation/src/main/resources/open-cli-schema/sol004/vtp-validate-csar-r787965.yaml b/csarvalidation/src/main/resources/open-cli-schema/sol004/vtp-validate-csar-r787965.yaml new file mode 100644 index 0000000..04eb147 --- /dev/null +++ b/csarvalidation/src/main/resources/open-cli-schema/sol004/vtp-validate-csar-r787965.yaml @@ -0,0 +1,54 @@ +# Copyright 2019 Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +open_cli_schema_version: 1.0 + +name: csar-validate-r787965 + +description: | + If the VNF or PNF CSAR Package utilizes Option 2 for package security, then the complete CSAR file MUST be digitally signed with the VNF or PNF provider private key. The VNF or PNF provider delivers one zip file consisting of the CSAR file, a signature file and a certificate file that includes the VNF or PNF provider public key. The certificate may also be included in the signature container, if the signature format allows that. The VNF or PNF provider creates a zip file consisting of the CSAR file with .csar extension, signature and certificate files. The signature and certificate files must be siblings of the CSAR file with extensions .cms and .cert respectively. + +info: + product: onap-vtp + version: 1.0 + service: validation + author: ONAP VTP Team onap-discuss@lists.onap.org + +parameters: + - name: csar + description: CSAR file path + long_option: csar + short_option: b + type: binary + is_optional: false + +results: + direction: landscape + attributes: + - name: code + description: Error code + scope: short + type: string + - name: message + description: Error message + scope: short + type: string + - name: file + description: File in which error occured + scope: short + type: string + - name: line-no + description: Line no at which error occured + scope: short + type: string diff --git a/csarvalidation/src/main/resources/open-cli-schema/vtp-validate-csar.yaml b/csarvalidation/src/main/resources/open-cli-schema/vtp-validate-csar.yaml index b8a4cb1..af3b0a8 100644 --- a/csarvalidation/src/main/resources/open-cli-schema/vtp-validate-csar.yaml +++ b/csarvalidation/src/main/resources/open-cli-schema/vtp-validate-csar.yaml @@ -34,6 +34,7 @@ parameters: - name: pnf description: CSAR file contains PNF long_option: pnf + short_option: p type: bool is_optional: true default_value: false diff --git a/csarvalidation/src/main/resources/vnfreqs.properties b/csarvalidation/src/main/resources/vnfreqs.properties index 8f45363..6f41351 100644 --- a/csarvalidation/src/main/resources/vnfreqs.properties +++ b/csarvalidation/src/main/resources/vnfreqs.properties @@ -1,5 +1,5 @@ -vnfreqs.enabled=r02454,r04298,r07879,r09467,r13390,r23823,r26881,r27310,r35851,r40293,r43958,r66070,r77707,r77786,r87234,r10087,r21322,r26885,r40820,r35854,r65486,r17852,r46527,r15837,r54356,r67895,r95321,r32155,r01123,r51347 -pnfreqs.enabled=r293901,r146092,r57019 +vnfreqs.enabled=r02454,r04298,r07879,r09467,r13390,r23823,r26881,r27310,r35851,r40293,r43958,r66070,r77707,r77786,r87234,r10087,r21322,r26885,r40820,r35854,r65486,r17852,r46527,r15837,r54356,r67895,r95321,r32155,r01123,r51347,r787965 +pnfreqs.enabled=r293901,r146092,r57019,r787965 # ignored all chef and ansible related tests vnferrors.ignored=0x1005,0x1006,r07879-0x1000,r13390-0x1000,r27310-0x1000,r40293-0x1000,r77786-0x1000,r04298-0x1000,r07879-0x1000,r10087-0x1000,r13390-0x1000,r23823-0x1000,r26881-0x1000,r40820-0x1000,r35851-0x1000,r32155-0x1000,r54356-0x1000,r67895-0x1000,r95321-0x1000,r46527-0x1000,r02454-0x1000 pnferrors.ignored=
\ No newline at end of file diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java index c441b80..25e36f6 100644 --- a/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/CsarValidatorTest.java @@ -41,7 +41,7 @@ public class CsarValidatorTest { @Test - public void testAllTestCasesForPNF() throws URISyntaxException { + public void testAllTestCasesForPNF_CsarCase() throws URISyntaxException { OnapCli cli = new OnapCli(new String [] { "--product", "onap-vtp", "csar-validate", @@ -52,4 +52,17 @@ public class CsarValidatorTest { assertEquals(0, cli.getExitCode()); } + + @Test + public void testAllTestCasesForPNF_ZipCase() throws URISyntaxException { + OnapCli cli = new OnapCli(new String [] { + "--product", "onap-vtp", + "csar-validate", + "--format", "json", + "--pnf", + "--csar", absoluteFilePath("pnf/signed-package.zip")}); + cli.handle(); + assertEquals(0, cli.getExitCode()); + } + } diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/FileArchiveTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/FileArchiveTest.java new file mode 100644 index 0000000..1ac8073 --- /dev/null +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/FileArchiveTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; +import java.net.URISyntaxException; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.onap.cvc.csar.cc.sol004.IntegrationTestUtils.absoluteFilePath; + +public class FileArchiveTest { + + @Rule + public TemporaryFolder folder= new TemporaryFolder(); + + @Test + public void shouldUnpackCsarFile() throws URISyntaxException, IOException { + // given + String absolutePath = folder.getRoot().getAbsolutePath(); + + // when + FileArchive.Workspace workspace = new FileArchive(absolutePath).unpack(absoluteFilePath("pnf/r57019/allMandatoryEntriesDefinedInMetadataManifest.csar")); + + // then + assertFalse(workspace.isZip()); + assertTrue(workspace.getRootFolder().isPresent()); + assertTrue(workspace.getPathToCsarFolder().isPresent()); + assertFalse(workspace.getPathToCertFile().isPresent()); + assertFalse(workspace.getPathToCmsFile().isPresent()); + } + + @Test + public void shouldUnpackZipFile() throws URISyntaxException, IOException { + // given + String absolutePath = folder.getRoot().getAbsolutePath(); + + // when + FileArchive.Workspace workspace = new FileArchive(absolutePath).unpack(absoluteFilePath("pnf/signed-package.zip")); + + // then + assertTrue(workspace.isZip()); + assertTrue(workspace.getRootFolder().isPresent()); + assertTrue(workspace.getPathToCsarFolder().isPresent()); + assertTrue(workspace.getPathToCertFile().isPresent()); + assertTrue(workspace.getPathToCmsFile().isPresent()); + } + +}
\ No newline at end of file diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/ZipFileContentValidatorTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/ZipFileContentValidatorTest.java new file mode 100644 index 0000000..7da91f8 --- /dev/null +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/ZipFileContentValidatorTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class ZipFileContentValidatorTest { + + @Mock + Path rootFolder; + @Mock + Path pathToCsarWorkspace; + @Mock + Path certFile; + @Mock + Path csarFile; + @Mock + Path cmsFile; + + private ZipFileContentValidator zipFileContentValidator; + + + @Before + public void setUp(){ + zipFileContentValidator = new ZipFileContentValidator(); + } + + @Test + public void shouldReportThatCertFileAndCmsFileIsNotAvailable() { + // given + FileArchive.Workspace workspace = FileArchive.Workspace.forZip( + rootFolder, + pathToCsarWorkspace, + null, + null, + null + ); + + // when + List<CSARArchive.CSARError> errors = zipFileContentValidator.validate(workspace); + + // then + assertThat(errors.size()).isEqualTo(2); + assertThat(errors.stream().map(CSARArchive.CSARError::getMessage).collect(Collectors.toList())).contains( + "Missing. Cert file is not available!", "Missing. CMS file is not available!" + ); + } + + @Test + public void shouldReportThatCertFileIsNotAvailable() { + // given + FileArchive.Workspace workspace = FileArchive.Workspace.forZip( + rootFolder, + pathToCsarWorkspace, + null, + cmsFile, + csarFile + ); + + // when + List<CSARArchive.CSARError> errors = zipFileContentValidator.validate(workspace); + + // then + assertThat(errors.size()).isEqualTo(1); + assertThat(errors.stream().map(CSARArchive.CSARError::getMessage).collect(Collectors.toList())).contains( + "Missing. Cert file is not available!" + ); + } + + @Test + public void shouldReportThatCmsFileIsNotAvailable() { + // given + FileArchive.Workspace workspace = FileArchive.Workspace.forZip( + rootFolder, + pathToCsarWorkspace, + certFile, + null, + csarFile + ); + + // when + List<CSARArchive.CSARError> errors = zipFileContentValidator.validate(workspace); + + // then + assertThat(errors.size()).isEqualTo(1); + assertThat(errors.stream().map(CSARArchive.CSARError::getMessage).collect(Collectors.toList())).contains( + "Missing. CMS file is not available!" + ); + } + + @Test + public void shouldNotReportAnyErrorWhenAllFilesAreAvailable() { + // given + FileArchive.Workspace workspace = FileArchive.Workspace.forZip( + rootFolder, + pathToCsarWorkspace, + certFile, + cmsFile, + csarFile + ); + + // when + List<CSARArchive.CSARError> errors = zipFileContentValidator.validate(workspace); + + // then + assertThat(errors.size()).isEqualTo(0); + } +}
\ No newline at end of file diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965IntegrationTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965IntegrationTest.java new file mode 100644 index 0000000..5c11c8a --- /dev/null +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/cc/sol004/VTPValidateCSARR787965IntegrationTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar.cc.sol004; + +import org.junit.Before; +import org.junit.Test; +import org.onap.cvc.csar.CSARArchive; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.onap.cvc.csar.cc.sol004.IntegrationTestUtils.configureTestCase; +import static org.onap.cvc.csar.cc.sol004.IntegrationTestUtils.convertToMessagesList; + + +public class VTPValidateCSARR787965IntegrationTest { + + private VTPValidateCSARR787965 testCase; + + @Before + public void setUp() { + testCase = new VTPValidateCSARR787965(); + } + + @Test + public void shouldReturnProperRequestNumber() { + assertThat(testCase.getVnfReqsNo()).isEqualTo("R787965"); + } + + @Test + public void shouldReportCsarHasInvalidSignature() throws Exception { + // We will not prepare positive test case, because X509 certification has expiration date and such test will + // stop working in the future. + + // given + configureTestCase(testCase, "pnf/signed-package.zip"); + + // when + testCase.execute(); + + // then + List<CSARArchive.CSARError> errors = testCase.getErrors(); + assertThat(errors.size()).isEqualTo(1); + assertThat(convertToMessagesList(errors)).contains( + "Invalid CSAR signature!" + ); + } + + +}
\ No newline at end of file diff --git a/csarvalidation/src/test/java/org/onap/cvc/csar/rsa/RSACertificateValidatorTest.java b/csarvalidation/src/test/java/org/onap/cvc/csar/rsa/RSACertificateValidatorTest.java new file mode 100644 index 0000000..9a3e124 --- /dev/null +++ b/csarvalidation/src/test/java/org/onap/cvc/csar/rsa/RSACertificateValidatorTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 Nokia + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onap.cvc.csar.rsa; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.security.PublicKey; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class RSACertificateValidatorTest { + + @Mock + private X509RsaCertification x509RsaCertification; + + @Mock + private PublicKey publicKey; + + @Test + public void shouldReturnInformationThatCsarHasValidSignature() throws Exception { + + // given + String publicCertificate ="-----BEGIN CERTIFICATE-----\n" + + "MIIDyzCCArMCCQCXF5To+FxujDANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UEBhMC\n" + + "SUUxETAPBgNVBAgMCExlaW5zdGVyMQ8wDQYDVQQHDAZEdWJsaW4xETAPBgNVBAoM\n" + + "CEVyaWNzc29uMRwwGgYDVQQLDBNCdXNpbmVzcyBBcmVhIFJhZGlvMSMwIQYDVQQD\n" + + "DBpSb290IGNlcnRpZmljYXRlIGF1dGhvcml0eTElMCMGCSqGSIb3DQEJARYWYXV0\n" + + "aG9yaXR5QGVyaWNzc29uLmNvbTAeFw0xOTAzMDcyMDA4MDRaFw0xOTA0MDYyMDA4\n" + + "MDRaMIGfMQswCQYDVQQGEwJJRTERMA8GA1UECAwITGVpbnN0ZXIxDzANBgNVBAcM\n" + + "BkR1YmxpbjERMA8GA1UECgwIRXJpY3Nzb24xHDAaBgNVBAsME0J1c2luZXNzIEFy\n" + + "ZWEgUmFkaW8xFzAVBgNVBAMMDlBhY2thZ2Ugc2lnbmVyMSIwIAYJKoZIhvcNAQkB\n" + + "FhNzaWduZXJAZXJpY3Nzb24uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + + "CgKCAQEA1bZWYbM3W9WK7E6brlMWw/pHdYmKrLmqnmyS4QWj6PoSudReX1x1QO+o\n" + + "jlzzlWn15ozgeDtsyQWRQakSkV8IUlywmM99tH7jGejrH87eLYv0IoJONVJLMsuQ\n" + + "chMd/cm0OGwUHHuk7iRnMGlcskp3FPvHlBRgBLrg+40yksJMmpHyS9amrG2/3bSa\n" + + "ssuc3F8ICNtejYVXDg5rIHyKIvD8Jaozf+V8FyFcFkfL7NyIS8rSuHM40vp3jlVO\n" + + "yNDztZ9orTA9Frucxr6y5UIXHd/bmh7YsjihyCoPOwvkfEy/S08S245eKS1zwgcE\n" + + "zkSwPC+XR7HwXoVb63hgBlcJCkUAswIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCC\n" + + "nWjpa+JeJj05UfX0tejdnHTotnT4AQfxp1YesG3O7ioIY4Y93/Cj8N+7rzeB392v\n" + + "eUMN2HKXGNRZhVJKs8fdoD/b5OxlwX1BattPS1Oh7HmLYzevOxotrm5YOR4KG2qa\n" + + "Rw/m6jFWxnAovpQTaCOgkuAJyF9l6wlQE4FyzyZMaThObcnLBzuQJjJXKMwaVT6D\n" + + "AQuMP3DRrH3aXlFpqV4bugLy8agSc2w9sF3w4osGZSwPjerJiulncUyBr+cjv1KB\n" + + "IfgzoP3b9frMBZmSpxeT3YzR1wZAh9AterRKAm6EGVxrnRDQ1b/OuW4y2RxQ/Q3G\n" + + "OUU/dbcjLaFvoQsv3aAk\n" + + "-----END CERTIFICATE-----\n"; + + String signature = "r+18GjD74DWNbp1U5zzbw7lB0QI5OXXBReGQ5DmRn/SFqQj0H22omSoolqlmwk8fc6pBfSTQl68yWEztH6m14dKTcYozVFpn1TS0qSgxMYjPJ5N/4+wrhC/70yosLATdc2w1U/9UYeFxP0QbCBSLtH9dDgTfm8e7Y25c7l6jSI+/VZ6b4lno5786y4W/VYeP6ktOvI0qbLtFPLfpxjqJ5idXUspkblhrZ6dHzURTlUWfYTku5NfLoIPL2Hdr8WfTBBTk+TYmAEBGC7J3SY5m1SZOOGElh80CfLGFVtdZ862Sgj2X8hV1isBTEJpczQwdMmid2xzdmZgbnkzFh9F/eQ=="; + byte [] content = new byte[] {'t','e','s','t'}; + + + String cert = "MIIDyzCCArMCCQCXF5To+FxujDANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UEBhMC\n" + + "SUUxETAPBgNVBAgMCExlaW5zdGVyMQ8wDQYDVQQHDAZEdWJsaW4xETAPBgNVBAoM\n" + + "CEVyaWNzc29uMRwwGgYDVQQLDBNCdXNpbmVzcyBBcmVhIFJhZGlvMSMwIQYDVQQD\n" + + "DBpSb290IGNlcnRpZmljYXRlIGF1dGhvcml0eTElMCMGCSqGSIb3DQEJARYWYXV0\n" + + "aG9yaXR5QGVyaWNzc29uLmNvbTAeFw0xOTAzMDcyMDA4MDRaFw0xOTA0MDYyMDA4\n" + + "MDRaMIGfMQswCQYDVQQGEwJJRTERMA8GA1UECAwITGVpbnN0ZXIxDzANBgNVBAcM\n" + + "BkR1YmxpbjERMA8GA1UECgwIRXJpY3Nzb24xHDAaBgNVBAsME0J1c2luZXNzIEFy\n" + + "ZWEgUmFkaW8xFzAVBgNVBAMMDlBhY2thZ2Ugc2lnbmVyMSIwIAYJKoZIhvcNAQkB\n" + + "FhNzaWduZXJAZXJpY3Nzb24uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + + "CgKCAQEA1bZWYbM3W9WK7E6brlMWw/pHdYmKrLmqnmyS4QWj6PoSudReX1x1QO+o\n" + + "jlzzlWn15ozgeDtsyQWRQakSkV8IUlywmM99tH7jGejrH87eLYv0IoJONVJLMsuQ\n" + + "chMd/cm0OGwUHHuk7iRnMGlcskp3FPvHlBRgBLrg+40yksJMmpHyS9amrG2/3bSa\n" + + "ssuc3F8ICNtejYVXDg5rIHyKIvD8Jaozf+V8FyFcFkfL7NyIS8rSuHM40vp3jlVO\n" + + "yNDztZ9orTA9Frucxr6y5UIXHd/bmh7YsjihyCoPOwvkfEy/S08S245eKS1zwgcE\n" + + "zkSwPC+XR7HwXoVb63hgBlcJCkUAswIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCC\n" + + "nWjpa+JeJj05UfX0tejdnHTotnT4AQfxp1YesG3O7ioIY4Y93/Cj8N+7rzeB392v\n" + + "eUMN2HKXGNRZhVJKs8fdoD/b5OxlwX1BattPS1Oh7HmLYzevOxotrm5YOR4KG2qa\n" + + "Rw/m6jFWxnAovpQTaCOgkuAJyF9l6wlQE4FyzyZMaThObcnLBzuQJjJXKMwaVT6D\n" + + "AQuMP3DRrH3aXlFpqV4bugLy8agSc2w9sF3w4osGZSwPjerJiulncUyBr+cjv1KB\n" + + "IfgzoP3b9frMBZmSpxeT3YzR1wZAh9AterRKAm6EGVxrnRDQ1b/OuW4y2RxQ/Q3G\n" + + "OUU/dbcjLaFvoQsv3aAk\n"; + + when(x509RsaCertification.generatePublicKey(cert)).thenReturn(publicKey); + when(x509RsaCertification.verify(content,signature, publicKey)).thenReturn(true); + + // when + RSACertificateValidator rsaCertificateValidator = new RSACertificateValidator(x509RsaCertification); + + // then + assertThat(rsaCertificateValidator.isValid(content, signature, publicCertificate)).isTrue(); + verify(x509RsaCertification,times(1)).generatePublicKey(cert); + verify(x509RsaCertification,times(1)).verify(content,signature, publicKey); + } + +}
\ No newline at end of file diff --git a/csarvalidation/src/test/resources/pnf/signed-package.zip b/csarvalidation/src/test/resources/pnf/signed-package.zip Binary files differnew file mode 100644 index 0000000..e4b7d00 --- /dev/null +++ b/csarvalidation/src/test/resources/pnf/signed-package.zip |