[FEATURE] Автогенерация сертификата на лету при его отсутствии

This commit is contained in:
Gravit 2019-10-27 21:47:55 +07:00
parent 585fb06151
commit 625fb75f82
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
7 changed files with 99 additions and 8 deletions

View file

@ -47,7 +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)); tasks.add(new SignJarTask(server.config.sign, server));
} }
@Override @Override

View file

@ -1,8 +1,17 @@
package pro.gravit.launchserver.binary.tasks; package pro.gravit.launchserver.binary.tasks;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.SignerJar; import pro.gravit.launchserver.binary.SignerJar;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
@ -12,12 +21,23 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -45,7 +65,12 @@ public Path process(Path inputFile) throws IOException {
return toRet; return toRet;
} }
public static void sign(LaunchServerConfig.JarSignerConf config, Path inputFile, Path signedFile) throws IOException { public void sign(LaunchServerConfig.JarSignerConf config, Path inputFile, Path signedFile) throws IOException {
if(config.enabled) stdSign(config, inputFile, signedFile);
else autoSign(inputFile, signedFile);
}
private void stdSign(LaunchServerConfig.JarSignerConf config, Path inputFile, Path signedFile) throws IOException {
KeyStore c = SignHelper.getStore(new File(config.keyStore).toPath(), config.keyStorePass, config.keyStoreType); KeyStore c = SignHelper.getStore(new File(config.keyStore).toPath(), config.keyStorePass, config.keyStoreType);
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> SignJarTask.gen(config, c), try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> SignJarTask.gen(config, c),
config.metaInfSfName, config.metaInfKeyName); config.metaInfSfName, config.metaInfKeyName);
@ -64,6 +89,30 @@ public static void sign(LaunchServerConfig.JarSignerConf config, Path inputFile,
} }
} }
} }
private void autoSign(Path inputFile, Path signedFile) throws IOException {
try (SignerJar output = new SignerJar(new ZipOutputStream(IOHelper.newOutput(signedFile)), () -> {
try {
return genCertificate(srv.config.projectName, srv.publicKey, srv.privateKey);
} catch (OperatorCreationException | CertificateException | CMSException e) {
throw new InternalError(e);
}
},
"AUTOGEN.SF", "AUTOGEN.EC");
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();
}
}
}
@Override @Override
public boolean allowDelete() { public boolean allowDelete() {
@ -80,4 +129,26 @@ public static CMSSignedDataGenerator gen(LaunchServerConfig.JarSignerConf config
return null; return null;
} }
} }
public static CMSSignedDataGenerator genCertificate(String projectName, ECPublicKey publicKey, ECPrivateKey privateKey) throws OperatorCreationException, CertificateException, CMSException {
X500NameBuilder subject = new X500NameBuilder();
subject.addRDN(BCStyle.CN, projectName.concat(" Autogenerated"));
subject.addRDN(BCStyle.O, projectName);
LocalDateTime startDate = LocalDate.now().atStartOfDay();
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
subject.build(),
new BigInteger("0"),
Date.from(startDate.atZone(ZoneId.systemDefault()).toInstant()),
Date.from(startDate.plusDays(3650).atZone(ZoneId.systemDefault()).toInstant()),
new X500Name("CN=ca"),
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256WITHECDSA");
ContentSigner signer = csBuilder.build(privateKey);
X509CertificateHolder certificate = builder.build(signer);
X509Certificate x509Certificate = new JcaX509CertificateConverter().setProvider( "BC" )
.getCertificate( certificate );
ArrayList<Certificate> chain = new ArrayList<>();
chain.add(x509Certificate);
return SignHelper.createSignedDataGenerator(privateKey, x509Certificate, chain, "SHA256WITHECDSA");
}
} }

View file

@ -2,6 +2,7 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.SignJarTask; import pro.gravit.launchserver.binary.tasks.SignJarTask;
import pro.gravit.launchserver.binary.tasks.TaskUtil;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
@ -13,13 +14,18 @@
public class SignDirCommand extends Command { public class SignDirCommand extends Command {
private class SignJarVisitor extends SimpleFileVisitor<Path> private class SignJarVisitor extends SimpleFileVisitor<Path>
{ {
private SignJarTask task;
public SignJarVisitor(SignJarTask task) {
this.task = task;
}
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toFile().getName().endsWith(".jar")) if (file.toFile().getName().endsWith(".jar"))
{ {
Path tmpSign = server.dir.resolve("build").resolve(file.toFile().getName()); Path tmpSign = server.dir.resolve("build").resolve(file.toFile().getName());
LogHelper.info("Signing jar %s", file.toString()); LogHelper.info("Signing jar %s", file.toString());
SignJarTask.sign(server.config.sign, file, tmpSign); task.sign(server.config.sign, file, tmpSign);
Files.deleteIfExists(file); Files.deleteIfExists(file);
Files.move(tmpSign, file); Files.move(tmpSign, file);
} }
@ -46,7 +52,8 @@ public void invoke(String... args) throws Exception {
Path targetDir = Paths.get(args[0]); Path targetDir = Paths.get(args[0]);
if(!IOHelper.isDir(targetDir)) if(!IOHelper.isDir(targetDir))
throw new IllegalArgumentException(String.format("%s not directory", targetDir.toString())); throw new IllegalArgumentException(String.format("%s not directory", targetDir.toString()));
IOHelper.walk(targetDir, new SignJarVisitor(), true); SignJarTask task = (SignJarTask) TaskUtil.getTaskByClass(server.launcherBinary.tasks, SignJarTask.class);
IOHelper.walk(targetDir, new SignJarVisitor(task), true);
LogHelper.info("Success signed"); LogHelper.info("Success signed");
} }
} }

View file

@ -2,6 +2,7 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.SignJarTask; import pro.gravit.launchserver.binary.tasks.SignJarTask;
import pro.gravit.launchserver.binary.tasks.TaskUtil;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
@ -34,7 +35,8 @@ public void invoke(String... args) throws Exception {
else else
tmpSign = server.dir.resolve("build").resolve(target.toFile().getName()); tmpSign = server.dir.resolve("build").resolve(target.toFile().getName());
LogHelper.info("Signing jar %s to %s", target.toString(), tmpSign.toString()); LogHelper.info("Signing jar %s to %s", target.toString(), tmpSign.toString());
SignJarTask.sign(server.config.sign, target, tmpSign); SignJarTask task = (SignJarTask) TaskUtil.getTaskByClass(server.launcherBinary.tasks, SignJarTask.class);
task.sign(server.config.sign, target, tmpSign);
if(args.length <= 1) if(args.length <= 1)
{ {
LogHelper.info("Move temp jar %s to %s", tmpSign.toString(), target.toString()); LogHelper.info("Move temp jar %s to %s", tmpSign.toString(), target.toString());

View file

@ -227,7 +227,7 @@ public static class CertificateConf {
} }
public static class JarSignerConf { public static class JarSignerConf {
public boolean enabled = true; public boolean enabled = false;
public String keyStore = "pathToKey"; public String keyStore = "pathToKey";
public String keyStoreType = "JKS"; public String keyStoreType = "JKS";
public String keyStorePass = "mypass"; public String keyStorePass = "mypass";

View file

@ -144,6 +144,17 @@ public static CMSSignedDataGenerator createSignedDataGenerator(KeyStore keyStore
generator.addCertificates(certStore); generator.addCertificates(certStore);
return generator; return generator;
} }
public static CMSSignedDataGenerator createSignedDataGenerator(PrivateKey privateKey, Certificate cert, List<Certificate> certChain, String signAlgo) throws OperatorCreationException, CertificateEncodingException, CMSException {
@SuppressWarnings("rawtypes")
Store certStore = new JcaCertStore(certChain);
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 final String hashFunctionName = "SHA-256";

View file

@ -81,7 +81,7 @@ public X509CertificateHolder generateCertificate(String subjectName, PublicKey s
} }
public void generateCA() throws NoSuchAlgorithmException, IOException, OperatorCreationException, InvalidAlgorithmParameterException { public void generateCA() throws NoSuchAlgorithmException, IOException, OperatorCreationException, InvalidAlgorithmParameterException {
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp384r1"); ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp384k1");
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
generator.initialize(ecGenSpec, SecurityHelper.newRandom()); generator.initialize(ecGenSpec, SecurityHelper.newRandom());
KeyPair pair = generator.generateKeyPair(); KeyPair pair = generator.generateKeyPair();
@ -105,7 +105,7 @@ public void generateCA() throws NoSuchAlgorithmException, IOException, OperatorC
} }
public KeyPair generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { public KeyPair generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp384r1"); ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp384k1");
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
generator.initialize(ecGenSpec, SecurityHelper.newRandom()); generator.initialize(ecGenSpec, SecurityHelper.newRandom());
return generator.generateKeyPair(); return generator.generateKeyPair();