Skip to content

Commit

Permalink
added verify-package cli command. untested yet
Browse files Browse the repository at this point in the history
  • Loading branch information
shannah committed Aug 18, 2024
1 parent 0950dff commit 59ad148
Show file tree
Hide file tree
Showing 5 changed files with 413 additions and 60 deletions.
23 changes: 14 additions & 9 deletions cli/src/main/java/ca/weblite/jdeploy/JDeploy.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@
import ca.weblite.jdeploy.app.JVMSpecification;
import ca.weblite.jdeploy.appbundler.Bundler;
import ca.weblite.jdeploy.appbundler.BundlerSettings;
import ca.weblite.jdeploy.cli.controllers.CheerpjController;
import ca.weblite.jdeploy.cli.controllers.GitHubRepositoryInitializerCLIController;
import ca.weblite.jdeploy.cli.controllers.JPackageController;
import ca.weblite.jdeploy.cli.controllers.ProjectGeneratorCLIController;
import ca.weblite.jdeploy.cli.controllers.*;
import ca.weblite.jdeploy.di.JDeployModule;
import ca.weblite.jdeploy.factories.JDeployKeyProviderFactory;
import ca.weblite.jdeploy.gui.JDeployMainMenu;
import ca.weblite.jdeploy.gui.JDeployProjectEditor;
import ca.weblite.jdeploy.helpers.PackageInfoBuilder;
import ca.weblite.jdeploy.helpers.PrereleaseHelper;
import ca.weblite.jdeploy.npm.NPM;
import ca.weblite.jdeploy.services.DeveloperIdentityKeyStore;
import ca.weblite.jdeploy.services.GithubWorkflowGenerator;
import ca.weblite.jdeploy.services.JavaVersionExtractor;
import ca.weblite.jdeploy.services.PackageSigningService;
import ca.weblite.jdeploy.services.*;
import ca.weblite.tools.io.*;
import ca.weblite.tools.security.KeyProvider;
import com.codename1.io.JSONParser;
Expand Down Expand Up @@ -1971,6 +1965,13 @@ private void jpackageCLI(String[] args) {
private void _package() throws IOException {
_package(new BundlerSettings());
}

private void _verify(String[] args) throws Exception {
String[] verifyArgs = new String[args.length-1];
System.arraycopy(args, 1, verifyArgs, 0, verifyArgs.length);
CLIVerifyPackageController verifyPackageController = new CLIVerifyPackageController(new VerifyPackageService());
verifyPackageController.verifyPackage(verifyArgs);
}

private void _package(BundlerSettings bundlerSettings) throws IOException {
File jdeployBundle = new File(directory, "jdeploy-bundle");
Expand Down Expand Up @@ -2514,6 +2515,10 @@ public static void main(String[] args) {
prog.generate(generateArgs);
return;
}
if (args.length > 0 && "verify-package".equals(args[0])) {
prog._verify(args);
return;
}
if (args.length > 0 && "github".equals(args[0]) && args.length> 1 && "init".equals(args[1])) {
String[] githubInitArgs = new String[args.length-2];
System.arraycopy(args, 2, githubInitArgs, 0, githubInitArgs.length);
Expand All @@ -2522,7 +2527,7 @@ public static void main(String[] args) {
}

Options opts = new Options();
opts.addOption("y", "no-prompt", false,"Indicates not to prompt user ");
opts.addOption("y", "no-prompt", false,"Indicates not to prompt_ user ");
opts.addOption("W", "no-workflow", false,"Indicates not to create a github workflow if true");
boolean noPromptFlag = false;
boolean noWorkflowFlag = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ca.weblite.jdeploy.cli.controllers;

import ca.weblite.jdeploy.services.VerifyPackageService;

public class CLIVerifyPackageController {
private final VerifyPackageService verifyPackageService;

public CLIVerifyPackageController(VerifyPackageService verifyPackageService) {
this.verifyPackageService = verifyPackageService;
}

public void verifyPackage(String[] args) {
VerifyPackageService.Result result;
try {
result = verifyPackageService.verifyPackage(parseParameters(args));
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
printUsage();
System.exit(1);
return;
}

if (result.verified) {
System.out.println("Package verified successfully");
System.exit(0);
} else {
System.out.println("Package verification failed: " + result.errorMessage);
if (result.verificationResult != null) {
System.out.println("Verification result: " + result.verificationResult);
System.exit(90 + result.verificationResult.ordinal());
} else {
printUsage();
System.exit(1);
}
}
}

private VerifyPackageService.Parameters parseParameters(String[] args) {
VerifyPackageService.Parameters result = new VerifyPackageService.Parameters();

for (int i = 0; i < args.length; i++) {
String arg = args[i];

switch (arg) {
case "-v":
case "--version":
if (i + 1 < args.length) {
result.version = args[++i];
} else {
throw new IllegalArgumentException("Missing value for version");
}
break;

case "-k":
case "--keystore":
if (i + 1 < args.length) {
result.keyStore = args[++i];
} else {
throw new IllegalArgumentException("Missing value for keystore");
}
break;

default:
if (result.jdeployBundlePath == null) {
result.jdeployBundlePath = arg;
} else {
throw new IllegalArgumentException("Unknown argument: " + arg);
}
break;
}
}

if (result.version == null || result.jdeployBundlePath == null || result.keyStore == null) {
throw new IllegalArgumentException("Required parameters: --version, jdeployBundlePath, --keystore");
}

return result;
}

private void printUsage() {
System.out.println("Usage: jdeploy verify-package [options] <jdeployBundlePath>");
System.out.println("Options:");
System.out.println(" -v, --version <version> Specify the version of the package to verify.");
System.out.println(" -k, --keystore <keystore> Specify the path to the keystore containing trusted certificates.");
System.out.println();
System.out.println("Arguments:");
System.out.println(" jdeployBundlePath Path to the jdeploy bundle directory to verify.");
System.out.println();
System.out.println("Example:");
System.out.println(" jdeploy verify-package -v 1.0.0 -k /path/to/keystore /path/to/jdeploy-bundle");
System.out.println();
System.out.println("Description:");
System.out.println(" This command verifies the integrity and authenticity of a jdeploy package.");
System.out.println(" The command checks the signature of the package against the provided version and keystore.");
System.out.println(" If the verification is successful, the package is considered authentic and untampered.");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package ca.weblite.jdeploy.services;

import ca.weblite.tools.security.*;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class VerifyPackageService {

public static class Parameters {
public String version;
public String jdeployBundlePath;
public String keyStore;
}

public static class Result {
public boolean verified;
public String errorMessage;
public VerificationResult verificationResult;

private Result(boolean verified, String errorMessage, VerificationResult verificationResult) {
this.verified = verified;
this.errorMessage = errorMessage;
this.verificationResult = verificationResult;
}
}

public Result verifyPackage(Parameters params) {
try {
validateParameters(params);
KeyStore trustedCertificates = loadTrustedCertificates(params.keyStore);
VerificationResult verificationResult = FileVerifier.verifyDirectory(
params.version,
params.jdeployBundlePath,
createCertificateVerifier(trustedCertificates)
);

switch (verificationResult) {
case NOT_SIGNED_AT_ALL:
return new Result(false, "The package is not signed", verificationResult);
case UNTRUSTED_CERTIFICATE:
return new Result(
false,
"The package is signed with an untrusted certificate",
verificationResult
);
case SIGNED_CORRECTLY:
return new Result(true, null, verificationResult);
case SIGNATURE_MISMATCH:
return new Result(
false, "The package signature does not match the contents",
verificationResult
);
default:
return new Result(
false, "Unknown verification result: " + verificationResult,
verificationResult
);
}

} catch (Exception e) {
return new Result(false, e.getMessage(), null);
}
}

private void validateParameters(Parameters params) {
if (params.jdeployBundlePath == null || params.jdeployBundlePath.isEmpty()) {
throw new IllegalArgumentException("jdeployBundlePath is required");
}
if (params.keyStore == null || params.keyStore.isEmpty()) {
throw new IllegalArgumentException("trustedCertificatesPemString is required");
}
}

private KeyStore loadTrustedCertificates(String keyStore) throws Exception {
if (isPemString(keyStore)) {
return CertificateUtil.loadCertificatesFromPEM(keyStore);
} else if (isJksFile(keyStore)) {
return loadJksFile(keyStore);
} else if (isPemFile(keyStore)) {
return loadPemFile(keyStore);
} else if (isDerFile(keyStore)) {
return loadDerFile(keyStore);
} else if (isPkcs12File(keyStore)) {
return loadPkcs12File(keyStore);
} else if (isPkcs7File(keyStore)) {
return loadPkcs7File(keyStore);
} else {
throw new IllegalArgumentException("Invalid key store format");
}
}

private boolean isPemString(String keyStore) {
return keyStore.startsWith("-----BEGIN CERTIFICATE-----");
}

private boolean isJksFile(String keyStore) {
return isFile(keyStore, ".jks");
}

private boolean isPemFile(String keyStore) {
return isFile(keyStore, ".pem");
}

private boolean isDerFile(String keyStore) {
return isFile(keyStore, ".der") || isFile(keyStore, ".cer") || isFile(keyStore, ".crt");
}

private boolean isFile(String path, String extension) {
return path.endsWith(extension) && (new File(path)).exists();
}

private boolean isPkcs12File(String keyStore) {
return isFile(keyStore, ".p12") || isFile(keyStore, ".pfx");
}

private boolean isPkcs7File(String keyStore) {
return isFile(keyStore, ".p7b") || isFile(keyStore, ".p7c");
}

private KeyStore loadDerFile(String filePath) throws Exception {
// Load the DER file into a byte array
FileInputStream fis = new FileInputStream(filePath);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

// Create the X.509 certificate from the input stream
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(fis);
fis.close();

// Extract the common name (CN) from the certificate's subject DN
String subjectDN = certificate.getSubjectX500Principal().getName();
String alias = getCommonName(subjectDN);

// Create a new KeyStore instance
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null); // Initialize an empty KeyStore

keyStore.setCertificateEntry(alias, certificate);

return keyStore;
}

private KeyStore loadJksFile(String filePath) throws Exception {
// Load the JKS file
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(filePath);
keyStore.load(fis, null);
fis.close();
return keyStore;
}

private KeyStore loadPkcs7File(String filePath) throws Exception {
// Load the PKCS#7 file
FileInputStream fis = new FileInputStream(filePath);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(fis);
fis.close();

// Create a new KeyStore instance
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null); // Initialize an empty KeyStore

// Add each certificate to the KeyStore
int i = 1;
for (Certificate certificate : certificates) {
String subjectDN = ((X509Certificate)certificate).getSubjectX500Principal().getName();
String alias = getCommonName(subjectDN);
keyStore.setCertificateEntry(alias, certificate);
}

return keyStore;
}

private KeyStore loadPkcs12File(String filePath) throws Exception {
// Load the PKCS#12 file
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(filePath);
keyStore.load(fis, null);
fis.close();
return keyStore;
}

private KeyStore loadPemFile(String filePath) throws Exception {
// Load the PEM file
FileInputStream fis = new FileInputStream(filePath);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(fis);
fis.close();

// Create a new KeyStore instance
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null); // Initialize an empty KeyStore

// Add each certificate to the KeyStore
int i = 1;
for (Certificate certificate : certificates) {
String subjectDN = ((X509Certificate)certificate).getSubjectX500Principal().getName();
String alias = getCommonName(subjectDN);
keyStore.setCertificateEntry(alias, certificate);
}

return keyStore;
}

private String getCommonName(String subjectDN) {
Pattern cnPattern = Pattern.compile("CN=([^,]+)");
Matcher matcher = cnPattern.matcher(subjectDN);
if (matcher.find()) {
return matcher.group(1);
}
throw new IllegalArgumentException("No CN found in subject DN: " + subjectDN);
}

private CertificateVerifier createCertificateVerifier(KeyStore trustedCertificates) {
return new SimpleCertificateVerifier(trustedCertificates);
}
}
Loading

0 comments on commit 59ad148

Please sign in to comment.