-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added verify-package cli command. untested yet
- Loading branch information
Showing
5 changed files
with
413 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
cli/src/main/java/ca/weblite/jdeploy/cli/controllers/CLIVerifyPackageController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."); | ||
} | ||
|
||
} |
224 changes: 224 additions & 0 deletions
224
cli/src/main/java/ca/weblite/jdeploy/services/VerifyPackageService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.