mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-11-15 11:39:11 +03:00
[FEATURE] JarSigner перенесен в основную ветку
This commit is contained in:
parent
f1dcae53b5
commit
d84b13c319
6 changed files with 537 additions and 1 deletions
|
@ -47,6 +47,7 @@ public void init() {
|
||||||
tasks.add(new AdditionalFixesApplyTask(server));
|
tasks.add(new AdditionalFixesApplyTask(server));
|
||||||
if (!server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
|
if (!server.config.launcher.attachLibraryBeforeProGuard) tasks.add(new AttachJarsTask(server));
|
||||||
if (server.config.launcher.compress) tasks.add(new CompressBuildTask(server));
|
if (server.config.launcher.compress) tasks.add(new CompressBuildTask(server));
|
||||||
|
if(server.config.sign.enabled) tasks.add(new SignJarTask(server.config.sign, server));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
package pro.gravit.launchserver.binary;
|
||||||
|
|
||||||
|
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
|
import org.bouncycastle.cms.CMSTypedData;
|
||||||
|
import pro.gravit.launchserver.helper.SignHelper;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generator of signed Jars. It stores some data in memory therefore it is not suited for creation of large files. The
|
||||||
|
* usage:
|
||||||
|
* <pre>
|
||||||
|
* KeyStore keystore = KeyStore.getInstance("JKS");
|
||||||
|
* keyStore.load(keystoreStream, "keystorePassword");
|
||||||
|
* SignerJar jar = new SignerJar(out, keyStore, "keyAlias", "keyPassword");
|
||||||
|
* signedJar.addManifestAttribute("Main-Class", "com.example.MainClass");
|
||||||
|
* signedJar.addManifestAttribute("Application-Name", "Example");
|
||||||
|
* signedJar.addManifestAttribute("Permissions", "all-permissions");
|
||||||
|
* signedJar.addManifestAttribute("Codebase", "*");
|
||||||
|
* signedJar.addFileContents("com/example/MainClass.class", clsData);
|
||||||
|
* signedJar.addFileContents("JNLP-INF/APPLICATION.JNLP", generateJnlpContents());
|
||||||
|
* signedJar.close();
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class SignerJar implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final String MANIFEST_FN = "META-INF/MANIFEST.MF";
|
||||||
|
private static final String SIG_FN = "META-INF/SIGNUMO.SF";
|
||||||
|
private static final String SIG_RSA_FN = "META-INF/SIGNUMO.RSA";
|
||||||
|
private static final String DIGEST_HASH = SignHelper.hashFunctionName + "-Digest";
|
||||||
|
|
||||||
|
private final ZipOutputStream zos;
|
||||||
|
|
||||||
|
private final Map<String, String> manifestAttributes;
|
||||||
|
private String manifestHash;
|
||||||
|
private String manifestMainHash;
|
||||||
|
|
||||||
|
private final Map<String, String> fileDigests;
|
||||||
|
|
||||||
|
private final Map<String, String> sectionDigests;
|
||||||
|
private final Supplier<CMSSignedDataGenerator> gen;
|
||||||
|
|
||||||
|
public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen) {
|
||||||
|
zos = out;
|
||||||
|
this.gen = gen;
|
||||||
|
manifestAttributes = new LinkedHashMap<>();
|
||||||
|
fileDigests = new LinkedHashMap<>();
|
||||||
|
sectionDigests = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
|
||||||
|
* the stream is closed.
|
||||||
|
*
|
||||||
|
* @param filename name of the file to add (use forward slash as a path separator)
|
||||||
|
* @param contents contents of the file
|
||||||
|
* @throws IOException
|
||||||
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
|
*/
|
||||||
|
public void addFileContents(String filename, byte[] contents) throws IOException {
|
||||||
|
addFileContents(filename, new ByteArrayInputStream(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
|
||||||
|
* the stream is closed.
|
||||||
|
*
|
||||||
|
* @param filename name of the file to add (use forward slash as a path separator)
|
||||||
|
* @param contents contents of the file
|
||||||
|
* @throws IOException
|
||||||
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
|
*/
|
||||||
|
public void addFileContents(String filename, InputStream contents) throws IOException {
|
||||||
|
addFileContents(IOHelper.newZipEntry(filename), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
|
||||||
|
* the stream is closed.
|
||||||
|
*
|
||||||
|
* @param entry name of the file to add (use forward slash as a path separator)
|
||||||
|
* @param contents contents of the file
|
||||||
|
* @throws IOException
|
||||||
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
|
*/
|
||||||
|
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
|
||||||
|
addFileContents(entry, new ByteArrayInputStream(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
|
||||||
|
* the stream is closed.
|
||||||
|
*
|
||||||
|
* @param entry name of the file to add (use forward slash as a path separator)
|
||||||
|
* @param contents contents of the file
|
||||||
|
* @throws IOException
|
||||||
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
|
*/
|
||||||
|
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
|
||||||
|
zos.putNextEntry(entry);
|
||||||
|
SignHelper.HashingOutputStream out = new SignHelper.HashingNonClosingOutputStream(zos, SignHelper.hasher());
|
||||||
|
IOHelper.transfer(contents, out);
|
||||||
|
zos.closeEntry();
|
||||||
|
fileDigests.put(entry.getName(), Base64.getEncoder().encodeToString(out.digest()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a header to the manifest of the JAR.
|
||||||
|
*
|
||||||
|
* @param name name of the attribute, it is placed into the main section of the manifest file
|
||||||
|
* @param value value of the attribute
|
||||||
|
*/
|
||||||
|
public void addManifestAttribute(String name, String value) {
|
||||||
|
manifestAttributes.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
|
||||||
|
* underlying stream.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws RuntimeException if the signing goes wrong
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
finish();
|
||||||
|
zos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
|
||||||
|
* underlying stream open.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws RuntimeException if the signing goes wrong
|
||||||
|
*/
|
||||||
|
public void finish() throws IOException {
|
||||||
|
writeManifest();
|
||||||
|
byte[] sig = writeSigFile();
|
||||||
|
writeSignature(sig);
|
||||||
|
zos.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipOutputStream getZos() {
|
||||||
|
return zos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for {@link #writeManifest()} that creates the digest of one entry.
|
||||||
|
*/
|
||||||
|
private String hashEntrySection(String name, Attributes attributes) throws IOException {
|
||||||
|
Manifest manifest = new Manifest();
|
||||||
|
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||||
|
ByteArrayOutputStream o = new ByteArrayOutputStream();
|
||||||
|
manifest.write(o);
|
||||||
|
int emptyLen = o.toByteArray().length;
|
||||||
|
|
||||||
|
manifest.getEntries().put(name, attributes);
|
||||||
|
|
||||||
|
manifest.write(o);
|
||||||
|
byte[] ob = o.toByteArray();
|
||||||
|
ob = Arrays.copyOfRange(ob, emptyLen, ob.length);
|
||||||
|
return Base64.getEncoder().encodeToString(SignHelper.hasher().digest(ob));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for {@link #writeManifest()} that creates the digest of the main section.
|
||||||
|
*/
|
||||||
|
private String hashMainSection(Attributes attributes) throws IOException {
|
||||||
|
Manifest manifest = new Manifest();
|
||||||
|
manifest.getMainAttributes().putAll(attributes);
|
||||||
|
SignHelper.HashingOutputStream o = new SignHelper.HashingNonClosingOutputStream(SignHelper.NULL, SignHelper.hasher());
|
||||||
|
manifest.write(o);
|
||||||
|
return Base64.getEncoder().encodeToString(o.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the CMS signed data.
|
||||||
|
*/
|
||||||
|
private byte[] signSigFile(byte[] sigContents) throws Exception {
|
||||||
|
CMSSignedDataGenerator gen = this.gen.get();
|
||||||
|
CMSTypedData cmsData = new CMSProcessableByteArray(sigContents);
|
||||||
|
CMSSignedData signedData = gen.generate(cmsData, true);
|
||||||
|
return signedData.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
|
||||||
|
* file.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void writeManifest() throws IOException {
|
||||||
|
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
|
||||||
|
Manifest man = new Manifest();
|
||||||
|
|
||||||
|
// main section
|
||||||
|
Attributes mainAttributes = man.getMainAttributes();
|
||||||
|
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : manifestAttributes.entrySet())
|
||||||
|
mainAttributes.put(new Attributes.Name(entry.getKey()), entry.getValue());
|
||||||
|
|
||||||
|
// individual files sections
|
||||||
|
Attributes.Name digestAttr = new Attributes.Name(DIGEST_HASH);
|
||||||
|
for (Map.Entry<String, String> entry : fileDigests.entrySet()) {
|
||||||
|
Attributes attributes = new Attributes();
|
||||||
|
man.getEntries().put(entry.getKey(), attributes);
|
||||||
|
attributes.put(digestAttr, entry.getValue());
|
||||||
|
sectionDigests.put(entry.getKey(), hashEntrySection(entry.getKey(), attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
SignHelper.HashingOutputStream out = new SignHelper.HashingNonClosingOutputStream(zos, SignHelper.hasher());
|
||||||
|
man.write(out);
|
||||||
|
zos.closeEntry();
|
||||||
|
|
||||||
|
manifestHash = Base64.getEncoder().encodeToString(out.digest());
|
||||||
|
manifestMainHash = hashMainSection(man.getMainAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the .SIG file to the JAR.
|
||||||
|
*
|
||||||
|
* @return the contents of the file as bytes
|
||||||
|
*/
|
||||||
|
private byte[] writeSigFile() throws IOException {
|
||||||
|
zos.putNextEntry(IOHelper.newZipEntry(SIG_FN));
|
||||||
|
Manifest man = new Manifest();
|
||||||
|
// main section
|
||||||
|
Attributes mainAttributes = man.getMainAttributes();
|
||||||
|
mainAttributes.put(Attributes.Name.SIGNATURE_VERSION, "1.0");
|
||||||
|
mainAttributes.put(new Attributes.Name(DIGEST_HASH + "-Manifest"), manifestHash);
|
||||||
|
mainAttributes.put(new Attributes.Name(DIGEST_HASH + "-Manifest-Main-Attributes"), manifestMainHash);
|
||||||
|
|
||||||
|
// individual files sections
|
||||||
|
Attributes.Name digestAttr = new Attributes.Name(DIGEST_HASH);
|
||||||
|
for (Map.Entry<String, String> entry : sectionDigests.entrySet()) {
|
||||||
|
Attributes attributes = new Attributes();
|
||||||
|
man.getEntries().put(entry.getKey(), attributes);
|
||||||
|
attributes.put(digestAttr, entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
man.write(zos);
|
||||||
|
zos.closeEntry();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
man.write(baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws RuntimeException if the signing failed
|
||||||
|
*/
|
||||||
|
private void writeSignature(byte[] sigFile) throws IOException {
|
||||||
|
zos.putNextEntry(IOHelper.newZipEntry(SIG_RSA_FN));
|
||||||
|
try {
|
||||||
|
byte[] signature = signSigFile(sigFile);
|
||||||
|
zos.write(signature);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Signing failed.", e);
|
||||||
|
}
|
||||||
|
zos.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package pro.gravit.launchserver.binary.tasks;
|
||||||
|
|
||||||
|
import org.bouncycastle.cms.CMSException;
|
||||||
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.binary.SignerJar;
|
||||||
|
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||||
|
import pro.gravit.launchserver.helper.SignHelper;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
public class SignJarTask implements LauncherBuildTask {
|
||||||
|
|
||||||
|
private final LaunchServerConfig.JarSignerConf config;
|
||||||
|
private final LaunchServer srv;
|
||||||
|
|
||||||
|
public SignJarTask(LaunchServerConfig.JarSignerConf config, LaunchServer srv) {
|
||||||
|
this.config = config;
|
||||||
|
this.srv = srv;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "SignJar";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path process(Path inputFile) throws IOException {
|
||||||
|
Path toRet = srv.launcherBinary.nextPath("signed");
|
||||||
|
KeyStore c = SignHelper.getStore(new File(config.keyStore).toPath(), config.keyStorePass, config.keyStoreType);
|
||||||
|
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(toRet)), () -> this.gen(c));
|
||||||
|
ZipInputStream input = new ZipInputStream(IOHelper.newInput(inputFile))) {
|
||||||
|
//input.getManifest().getMainAttributes().forEach((a, b) -> output.addManifestAttribute(a.toString(), b.toString())); // may not work such as after Radon.
|
||||||
|
ZipEntry e = input.getNextEntry();
|
||||||
|
while (e != null) {
|
||||||
|
if ("META-INF/MANIFEST.MF".equals(e.getName()) || "/META-INF/MANIFEST.MF".equals(e.getName())) {
|
||||||
|
Manifest m = new Manifest(input);
|
||||||
|
m.getMainAttributes().forEach((a, b) -> output.addManifestAttribute(a.toString(), b.toString()));
|
||||||
|
e = input.getNextEntry();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output.addFileContents(IOHelper.newZipEntry(e), input);
|
||||||
|
e = input.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowDelete() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMSSignedDataGenerator gen(KeyStore c) {
|
||||||
|
try {
|
||||||
|
return SignHelper.createSignedDataGenerator(c,
|
||||||
|
config.keyAlias, config.signAlgo, config.keyStorePass);
|
||||||
|
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
|
||||||
|
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,6 +88,7 @@ public AuthProviderPair getAuthProviderPair() {
|
||||||
public String whitelistRejectString;
|
public String whitelistRejectString;
|
||||||
public LauncherConf launcher;
|
public LauncherConf launcher;
|
||||||
public CertificateConf certificate;
|
public CertificateConf certificate;
|
||||||
|
public JarSignerConf sign;
|
||||||
|
|
||||||
public String startScript;
|
public String startScript;
|
||||||
|
|
||||||
|
@ -225,6 +226,16 @@ public static class CertificateConf {
|
||||||
public boolean enabled;
|
public boolean enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class JarSignerConf {
|
||||||
|
public boolean enabled = true;
|
||||||
|
public String keyStore = "pathToKey";
|
||||||
|
public String keyStoreType = "JKS";
|
||||||
|
public String keyStorePass = "mypass";
|
||||||
|
public String keyAlias = "myname";
|
||||||
|
public String keyPass = "mypass";
|
||||||
|
public String signAlgo = "SHA256WITHRSA";
|
||||||
|
}
|
||||||
|
|
||||||
public static class NettyUpdatesBind {
|
public static class NettyUpdatesBind {
|
||||||
public String url;
|
public String url;
|
||||||
public boolean zip;
|
public boolean zip;
|
||||||
|
@ -324,6 +335,7 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
||||||
|
|
||||||
newConfig.certificate = new LaunchServerConfig.CertificateConf();
|
newConfig.certificate = new LaunchServerConfig.CertificateConf();
|
||||||
newConfig.certificate.enabled = false;
|
newConfig.certificate.enabled = false;
|
||||||
|
newConfig.sign = new JarSignerConf();
|
||||||
|
|
||||||
newConfig.components = new HashMap<>();
|
newConfig.components = new HashMap<>();
|
||||||
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
|
AuthLimiterComponent authLimiterComponent = new AuthLimiterComponent();
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
package pro.gravit.launchserver.helper;
|
||||||
|
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||||
|
import org.bouncycastle.cms.CMSException;
|
||||||
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
|
import org.bouncycastle.cms.SignerInfoGenerator;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.DigestCalculatorProvider;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||||
|
import org.bouncycastle.util.Store;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SignHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper output stream that also sends the data to the given.
|
||||||
|
*/
|
||||||
|
public static class HashingOutputStream extends OutputStream {
|
||||||
|
public final OutputStream out;
|
||||||
|
public final MessageDigest hasher;
|
||||||
|
|
||||||
|
public HashingOutputStream(OutputStream out, MessageDigest hasher) {
|
||||||
|
this.out = out;
|
||||||
|
this.hasher = hasher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
out.write(b);
|
||||||
|
hasher.update(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
out.write(b, off, len);
|
||||||
|
hasher.update(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
out.write(b);
|
||||||
|
hasher.update((byte) b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] digest() {
|
||||||
|
return hasher.digest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper output stream that also sends the data to the given.
|
||||||
|
*/
|
||||||
|
public static class HashingNonClosingOutputStream extends HashingOutputStream {
|
||||||
|
public HashingNonClosingOutputStream(OutputStream out, MessageDigest hasher) {
|
||||||
|
super(out, hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final OutputStream NULL = new OutputStream() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NullOutputStream";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte array. */
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte array. */
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Discards the specified byte. */
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Never closes */
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private SignHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the KeyStore with given algo.
|
||||||
|
*/
|
||||||
|
public static KeyStore getStore(Path file, String storepass, String algo) throws IOException {
|
||||||
|
try {
|
||||||
|
KeyStore st = KeyStore.getInstance(algo);
|
||||||
|
st.load(IOHelper.newInput(file), storepass != null ? storepass.toCharArray() : null);
|
||||||
|
return st;
|
||||||
|
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the beast that can actually sign the data (for JKS, for other make it).
|
||||||
|
*/
|
||||||
|
public static CMSSignedDataGenerator createSignedDataGenerator(KeyStore keyStore, String keyAlias, String signAlgo, String keyPassword) throws KeyStoreException, OperatorCreationException, CertificateEncodingException, UnrecoverableKeyException, NoSuchAlgorithmException, CMSException {
|
||||||
|
List<Certificate> certChain = new ArrayList<>(Arrays.asList(keyStore.getCertificateChain(keyAlias)));
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Store certStore = new JcaCertStore(certChain);
|
||||||
|
Certificate cert = keyStore.getCertificate(keyAlias);
|
||||||
|
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword != null ? keyPassword.toCharArray() : null);
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder(signAlgo).setProvider("BC").build(privateKey);
|
||||||
|
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||||
|
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
||||||
|
SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
|
||||||
|
generator.addSignerInfoGenerator(sig);
|
||||||
|
generator.addCertificates(certStore);
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String hashFunctionName = "SHA-256";
|
||||||
|
|
||||||
|
public static MessageDigest hasher() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance(hashFunctionName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
modules
2
modules
|
@ -1 +1 @@
|
||||||
Subproject commit bcaa597a85e3ff749144fabc182e47b26f4f14ef
|
Subproject commit 6f008f595975887b6a1366cb444b82b339e3afad
|
Loading…
Reference in a new issue