Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions kse/src/main/java/org/kse/crypto/signing/CmsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ private CmsUtil() {
*
* @param signatureFile The signature file.
* @param chooser The file chooser to use for choosing the content file.
* @return
* @throws CryptoException
* @return The CMSSignedData including any detached content, if available.
* @throws CryptoException If an exception occurs reading the signature or content.
*/
public static CMSSignedData loadSignature(File signatureFile, Supplier<File> chooser)
throws CryptoException {
Expand Down Expand Up @@ -114,6 +114,11 @@ private static CMSProcessableFile loadDetachedContent(File signatureFile, Suppli
return new CMSProcessableFile(contentFile);
}

/**
*
* @param pemInfo A PemInfo containing unknown PEM content.
* @return True if the PEM content is PKCS7 or CMS.
*/
public static boolean isCmsPemType(PemInfo pemInfo) {
return pemInfo != null && (PKCS7_PEM_TYPE.equals(pemInfo.getType()) || CMS_PEM_TYPE.equals(pemInfo.getType()));
}
Expand All @@ -123,6 +128,7 @@ public static boolean isCmsPemType(PemInfo pemInfo) {
*
* @param cms The CMS signature
* @return The PEM'd encoding
* @throws CryptoException If an error occurs when decoding the PEM content.
*/
public static String getPem(CMSSignedData cms) throws CryptoException {
try {
Expand All @@ -135,9 +141,17 @@ public static String getPem(CMSSignedData cms) throws CryptoException {
}
}

/**
* Converts a collection of SignerInformation objects into a collection of KseSignerInformation objects.
*
* @param signerInfos The collection of SignerInformation objects to convert.
* @param trustedCerts The set of trusted certificates to add to the SignerInformation objects.
* @param signedData The signed data to add to the SignerInformation objects.
* @return A collection of KseSignerInformation objects.
*/
public static List<KseSignerInformation> convertSignerInformations(Collection<SignerInformation> signerInfos,
Store<X509CertificateHolder> trustedCerts, Store<X509CertificateHolder> signatureCerts) {
return signerInfos.stream().map(s -> new KseSignerInformation(s, trustedCerts, signatureCerts))
Store<X509CertificateHolder> trustedCerts, CMSSignedData signedData) {
return signerInfos.stream().map(s -> new KseSignerInformation(s, trustedCerts, signedData))
.collect(Collectors.toList());
}
}
83 changes: 41 additions & 42 deletions kse/src/main/java/org/kse/crypto/signing/JarSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
*/
package org.kse.crypto.signing;

import static org.kse.crypto.signing.SignatureType.SHA1_DSA;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
Expand Down Expand Up @@ -119,17 +117,17 @@ public class JarSigner {
// Signature Version
private static final String SIGNATURE_VERSION = "1.0";

// Manifest location in JAR file
private static final String MANIFEST_LOCATION = "META-INF/MANIFEST.MF";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constant is duplicate of JarFile.MANIFEST_NAME.


// DSA signature block extension
private static final String DSA_SIG_BLOCK_EXT = "DSA";
public static final String DSA_SIG_BLOCK_EXT = "DSA";

// RSA signature block extension
private static final String RSA_SIG_BLOCK_EXT = "RSA";
public static final String RSA_SIG_BLOCK_EXT = "RSA";

// EC signature block extension
public static final String EC_SIG_BLOCK_EXT = "EC";

// Signature file extension
private static final String SIGNATURE_EXT = "SF";
public static final String SIGNATURE_EXT = "SF";

// Meta inf file location
private static final String METAINF_FILE_LOC = "META-INF/{0}.{1}";
Expand Down Expand Up @@ -212,8 +210,8 @@ public static void sign(File jarFile, File signedJarFile, PrivateKey privateKey,
// Write out digests to manifest and signature file

// Sign each JAR entry...
for (Enumeration<?> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
for (Enumeration<JarEntry> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = jarEntries.nextElement();

if (!jarEntry.isDirectory()) // Ignore directories
{
Expand Down Expand Up @@ -294,20 +292,13 @@ private static boolean ignoreJarEntry(JarEntry jarEntry) {

// Entries to be ignored are all in the "META-INF" folder
if (entryName.startsWith("META-INF/")) {
if (entryName.equalsIgnoreCase(MANIFEST_LOCATION)) {
return true; // Manifest file - ignore
}

if (entryName.toUpperCase().endsWith(SIGNATURE_EXT)) {
return true; // Signature file - ignore
}

if (entryName.toUpperCase().endsWith(RSA_SIG_BLOCK_EXT)) {
return true; // RSA signature block file - ignore
}

if (entryName.toUpperCase().endsWith(DSA_SIG_BLOCK_EXT)) {
return true; // DSA signature block file - ignore
// Ignore signature file and EC/RSA/DSA signature blocks
if (entryName.equals(JarFile.MANIFEST_NAME)
|| entryName.endsWith(SIGNATURE_EXT) //
|| entryName.endsWith(EC_SIG_BLOCK_EXT) //
|| entryName.endsWith(RSA_SIG_BLOCK_EXT) //
|| entryName.endsWith(DSA_SIG_BLOCK_EXT)) {
return true; // Manifest or signature block file - ignore
}
}

Expand All @@ -323,15 +314,18 @@ private static boolean ignoreJarEntry(JarEntry jarEntry) {
* @throws IOException If an I/O problem occurs while examining the JAR file
*/
public static boolean hasSignature(File jarFile, String signatureName) throws IOException {
signatureName = signatureName.toUpperCase();

try (JarFile jar = new JarFile(jarFile)) {

// Look for signature file
for (Enumeration<?> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
if (!jarEntry.isDirectory() && (jarEntry.getName().equalsIgnoreCase(
MessageFormat.format(METAINF_FILE_LOC, signatureName, DSA_SIG_BLOCK_EXT))) ||
(jarEntry.getName().equalsIgnoreCase(
MessageFormat.format(METAINF_FILE_LOC, signatureName, RSA_SIG_BLOCK_EXT)))) {
for (Enumeration<JarEntry> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName(); // Signature entries must be upper case.
if (!jarEntry.isDirectory() && (entryName
.equals(MessageFormat.format(METAINF_FILE_LOC, signatureName, EC_SIG_BLOCK_EXT))
|| entryName.equals(MessageFormat.format(METAINF_FILE_LOC, signatureName, RSA_SIG_BLOCK_EXT))
|| entryName.equals(MessageFormat.format(METAINF_FILE_LOC, signatureName, DSA_SIG_BLOCK_EXT)))) {
return true;
}
}
Expand Down Expand Up @@ -453,7 +447,7 @@ private static String getDigestManifestAttrs(JarFile jar, JarEntry jarEntry, Dig
*/
private static String getManifest(JarFile jar) throws IOException {

JarEntry manifestEntry = jar.getJarEntry(MANIFEST_LOCATION);
JarEntry manifestEntry = jar.getJarEntry(JarFile.MANIFEST_NAME);

try (InputStream jis = jar.getInputStream(manifestEntry);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Expand Down Expand Up @@ -545,23 +539,25 @@ private static String getManifestEntryAttrs(JarFile jar, String entryName) throw
* manifest and existing signature files for the supplied signature name
*/
private static void writeJarEntries(JarFile jar, JarOutputStream jos, String signatureName) throws IOException {
signatureName = signatureName.toUpperCase();

for (Enumeration<?> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
for (Enumeration<JarEntry> jarEntries = jar.entries(); jarEntries.hasMoreElements(); ) {
JarEntry jarEntry = jarEntries.nextElement();
if (!jarEntry.isDirectory()) {
String entryName = jarEntry.getName();

// Signature files not to write across
String sigFileLocation = MessageFormat.format(METAINF_FILE_LOC, signatureName, SIGNATURE_EXT)
.toUpperCase();
String sigFileLocation = MessageFormat.format(METAINF_FILE_LOC, signatureName, SIGNATURE_EXT);
String dsaSigBlockLocation = MessageFormat.format(METAINF_FILE_LOC, signatureName, DSA_SIG_BLOCK_EXT);
String rsaSigBlockLocation = MessageFormat.format(METAINF_FILE_LOC, signatureName, RSA_SIG_BLOCK_EXT);
String ecSigBlockLocation = MessageFormat.format(METAINF_FILE_LOC, signatureName, EC_SIG_BLOCK_EXT);

// Do not write across existing manifest or matching signature files
if ((!entryName.equalsIgnoreCase(MANIFEST_LOCATION)) &&
(!entryName.equalsIgnoreCase(sigFileLocation)) &&
(!entryName.equalsIgnoreCase(dsaSigBlockLocation)) &&
(!entryName.equalsIgnoreCase(rsaSigBlockLocation))) {
if ((!entryName.equals(JarFile.MANIFEST_NAME)) &&
(!entryName.equals(sigFileLocation)) &&
(!entryName.equals(dsaSigBlockLocation)) &&
(!entryName.equals(ecSigBlockLocation)) &&
(!entryName.equals(rsaSigBlockLocation))) {
// New JAR entry based on original
transferJarEntry(jar, jos, jarEntry);
}
Expand Down Expand Up @@ -596,7 +592,7 @@ private static void transferJarEntry(JarFile jar, JarOutputStream jos, JarEntry
private static void writeManifest(byte[] manifest, JarOutputStream jos) throws IOException {

// Manifest file entry
JarEntry mfJarEntry = new JarEntry(MANIFEST_LOCATION);
JarEntry mfJarEntry = new JarEntry(JarFile.MANIFEST_NAME);
jos.putNextEntry(mfJarEntry);

try (ByteArrayInputStream bais = new ByteArrayInputStream(manifest)) {
Expand Down Expand Up @@ -645,10 +641,13 @@ private static void writeSignatureBlock(byte[] sigBlock, SignatureType signature
// Block's extension depends on signature type
String extension = null;

if (signatureType == SHA1_DSA) {
if (SignatureType.dsaSignatureTypes().contains(signatureType)) {
extension = DSA_SIG_BLOCK_EXT;
} else {
} else if (SignatureType.rsaSignatureTypes().contains(signatureType)) {
extension = RSA_SIG_BLOCK_EXT;
} else {
// This includes EDDSA, ED25519, & ED448
extension = EC_SIG_BLOCK_EXT;
}

// Signature block entry
Expand Down
53 changes: 53 additions & 0 deletions kse/src/main/java/org/kse/crypto/signing/KseJarEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2004 - 2013 Wayne Grant
* 2013 - 2025 Kai Kramer
*
* This file is part of KeyStore Explorer.
*
* KeyStore Explorer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeyStore Explorer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeyStore Explorer. If not, see <http://www.gnu.org/licenses/>.
*/

package org.kse.crypto.signing;

import java.util.jar.JarEntry;

/**
* Augments JarEntry by adding the jarsigner flags to the entry.
*/
public class KseJarEntry extends JarEntry {

public static final char FLAG_BLANK = ' ';
public static final char FLAG_SIGNED = 's';
public static final char FLAG_MANIFEST = 'm';
public static final char FLAG_CERT = 'k';

private String flags;

/**
* @param je The JarEntry to adapt.
* @param flags The verification flags for the JarEntry.
*/
public KseJarEntry(JarEntry je, String flags) {
super(je);
this.flags = flags;
}

/**
* @return the flags
*/
public String getFlags() {
return flags;
}

}
20 changes: 15 additions & 5 deletions kse/src/main/java/org/kse/crypto/signing/KseSignerInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
Expand All @@ -61,6 +62,7 @@
*/
public class KseSignerInformation extends SignerInformation {

private CMSSignedData signedData;
private Store<X509CertificateHolder> trustedCerts;
private Store<X509CertificateHolder> signatureCerts;
private X509CertificateHolder cert;
Expand All @@ -72,16 +74,24 @@ public class KseSignerInformation extends SignerInformation {
*
* @param signerInfo The SignerInformation to extend.
* @param trustedCerts The trusted certs for lookup and verification.
* @param signatureCerts The signature certs for lookup and verification.
* @param signedData The signedData for the signer.
*/
public KseSignerInformation(SignerInformation signerInfo, Store<X509CertificateHolder> trustedCerts,
Store<X509CertificateHolder> signatureCerts) {
CMSSignedData signedData) {
super(signerInfo);
this.signedData = signedData;
this.trustedCerts = trustedCerts;
this.signatureCerts = signatureCerts;
this.signatureCerts = signedData.getCertificates();
lookupCert();
}

/**
* @return the signedData
*/
public CMSSignedData getSignedData() {
return signedData;
}

/**
* @return the trustedCerts
*/
Expand Down Expand Up @@ -307,7 +317,7 @@ private boolean verifyCounterSignatures() {
boolean verified = true;

Collection<KseSignerInformation> counterSigners = CmsUtil
.convertSignerInformations(super.getCounterSignatures().getSigners(), trustedCerts, signatureCerts);
.convertSignerInformations(super.getCounterSignatures().getSigners(), trustedCerts, signedData);

for (KseSignerInformation signer : counterSigners) {
signer.verify();
Expand Down Expand Up @@ -339,7 +349,7 @@ private void verifyTimeStamp()
@Override
public SignerInformationStore getCounterSignatures() {
List<KseSignerInformation> counterSigners = CmsUtil.convertSignerInformations(
super.getCounterSignatures().getSigners(), trustedCerts, signatureCerts);
super.getCounterSignatures().getSigners(), trustedCerts, signedData);
// Load into an ArrayList for mapping between generic types.
return new SignerInformationStore(new ArrayList<>(counterSigners));
}
Expand Down
20 changes: 19 additions & 1 deletion kse/src/main/java/org/kse/crypto/x509/X509CertUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,17 @@ public static X509Certificate[] establishTrust(X509Certificate cert, KeyStore[]
return establishTrust(cert, ksCerts);
}

/**
* Check whether or not a trust path exists between the supplied X.509
Comment thread
jonwltn marked this conversation as resolved.
Resolved
* certificate and the supplied list of certificates, ie that a chain of
* trust exists between the supplied certificate and a self-signed trusted
* certificate in the list.
*
* @param cert The certificate
* @param compCerts The list of trusted certificates.
* @return The trust chain, or null if trust could not be established
* @throws CryptoException If there is a problem establishing trust
*/
public static X509Certificate[] establishTrust(X509Certificate cert, List<X509Certificate> compCerts)
throws CryptoException {
/*
Expand Down Expand Up @@ -706,7 +717,14 @@ public static X509Certificate[] establishTrust(X509Certificate cert, List<X509Ce
return null; // No chain of trust
}

private static List<X509Certificate> extractCertificates(KeyStore keyStore) throws CryptoException {
/**
* Extracts a list of X509Certificates from a KeyStore.
*
* @param keyStore The key store containing trusted certificates.
* @return List<X509Certificate>
* @throws CryptoException If there is a problem extracting the certificates.
*/
public static List<X509Certificate> extractCertificates(KeyStore keyStore) throws CryptoException {
try {
List<X509Certificate> certs = new ArrayList<>();

Expand Down
Loading