From 1a951638949270300d67ced5672f5275b609a342 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sat, 7 Nov 2015 18:12:08 +0800 Subject: [PATCH] * remove commons-openpgp dependency and implement the necessary with BouncyCastle APIs directly --- .classpath | 1 - examples/helloworld/build.xml | 2 +- ivy.xml | 11 +-- ivysettings.xml | 9 -- src/net/filebot/ant/spk/CodeSignTask.java | 88 +++++++++---------- src/net/filebot/ant/spk/PackageTask.java | 5 +- .../ant/spk/openpgp/OpenPGPSecretKey.java | 48 ++++++++++ .../ant/spk/openpgp/OpenPGPSignature.java | 71 +++++++++++++++ 8 files changed, 170 insertions(+), 65 deletions(-) delete mode 100644 ivysettings.xml create mode 100644 src/net/filebot/ant/spk/openpgp/OpenPGPSecretKey.java create mode 100644 src/net/filebot/ant/spk/openpgp/OpenPGPSignature.java diff --git a/.classpath b/.classpath index 404ef66..d97203d 100644 --- a/.classpath +++ b/.classpath @@ -8,7 +8,6 @@ - diff --git a/examples/helloworld/build.xml b/examples/helloworld/build.xml index 088f4d9..bcbf58e 100644 --- a/examples/helloworld/build.xml +++ b/examples/helloworld/build.xml @@ -25,7 +25,7 @@ - + diff --git a/ivy.xml b/ivy.xml index c04d7ba..6677d91 100644 --- a/ivy.xml +++ b/ivy.xml @@ -2,11 +2,12 @@ - - - - - + + + + + + diff --git a/ivysettings.xml b/ivysettings.xml deleted file mode 100644 index 85921db..0000000 --- a/ivysettings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/net/filebot/ant/spk/CodeSignTask.java b/src/net/filebot/ant/spk/CodeSignTask.java index e256750..69a8863 100644 --- a/src/net/filebot/ant/spk/CodeSignTask.java +++ b/src/net/filebot/ant/spk/CodeSignTask.java @@ -4,13 +4,16 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.security.SignatureException; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; -import org.apache.commons.openpgp.ant.OpenPgpSignerTask; +import net.filebot.ant.spk.openpgp.OpenPGPSignature; + import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; @@ -19,34 +22,30 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; -import org.apache.tools.ant.taskdefs.Concat; import org.apache.tools.ant.taskdefs.Tar.TarFileSet; import org.apache.tools.ant.types.Resource; +import org.bouncycastle.openpgp.PGPException; public class CodeSignTask extends Task { - // user properties + private static final int BUFFER_SIZE = 64 * 1024; + String keyId; - File pubring; File secring; - String password = ""; // empty password by default + char[] password = new char[0]; // empty password by default String timestamp = "http://timestamp.synology.com/timestamp.php"; // default Synology signature server public void setKeyId(String keyId) { this.keyId = keyId; } - public void setPubring(File pubring) { - this.pubring = pubring; - } - public void setSecring(File secring) { this.secring = secring; } public void setPassword(String password) { - this.password = password; + this.password = password.toCharArray(); } public void setTimestamp(String timestamp) { @@ -67,43 +66,29 @@ public void addConfiguredCat(TarFileSet files) { @Override public void execute() { - // temporary files - File allcat = new File("ALLCAT.dat"); - File allsig = new File("ALLCAT.dat.asc"); // generated by default mapper - - Concat concat = new Concat(); - concat.setProject(getProject()); - concat.setDestfile(allcat); - concat.setBinary(true); - concat.setOverwrite(true); - - // cat files in case-sensitive alphabetical tar entry path order - TreeMap sortedCats = new TreeMap(); - cats.forEach(fs -> { - fs.setProject(getProject()); - fs.forEach(r -> { - sortedCats.put(getTarEntryName(r.getName(), fs), r); - }); - }); + byte[] asciiArmoredSignatureFile; - log(String.format("CAT: Prepare package data (%,d files, %,d bytes)", sortedCats.size(), sortedCats.values().stream().mapToLong(r -> r.getSize()).sum())); - sortedCats.values().forEach(concat::add); - concat.execute(); - - // GPG sign all cat data + // compute PGP signature log("GPG: sign with key " + keyId); - OpenPgpSignerTask signer = new OpenPgpSignerTask(); - signer.setProject(getProject()); - signer.setAsciiarmor(true); - signer.setPubring(pubring); - signer.setSecring(secring); - signer.setPassword(password); - signer.setKeyId(keyId); - signer.setArtefact(allcat); + try { + OpenPGPSignature signature = OpenPGPSignature.createSignatureGenerator(keyId, secring, password); + + // cat files in case-sensitive alphabetical tar entry path order + byte[] buffer = new byte[BUFFER_SIZE]; + int length = 0; + for (Resource r : getTarOrderCatResources()) { + try (InputStream in = r.getInputStream()) { + while ((length = in.read(buffer, 0, buffer.length)) != -1) { + signature.update(buffer, 0, length); + } + } + } - // sign the binary data - signer.execute(); + asciiArmoredSignatureFile = signature.generate(true); + } catch (IOException | SignatureException | PGPException e) { + throw new BuildException("Failed to compute PGP signature: " + e.getMessage()); + } // sign the signature log("SYNO: Submit signature to " + timestamp); @@ -111,19 +96,26 @@ public void execute() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { HttpPost httpPost = new HttpPost(timestamp); - HttpEntity pastData = MultipartEntityBuilder.create().addBinaryBody("file", allsig).build(); + HttpEntity pastData = MultipartEntityBuilder.create().addBinaryBody("file", asciiArmoredSignatureFile).build(); httpPost.setEntity(pastData); HttpResponse response = httpClient.execute(httpPost); Files.copy(response.getEntity().getContent(), token.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - throw new BuildException("Failed to retrieve signature to " + timestamp); - } finally { - allcat.delete(); - allsig.delete(); + throw new BuildException("Failed to retrieve signature: " + e.getMessage()); } } + protected Resource[] getTarOrderCatResources() { + TreeMap sortedCats = new TreeMap(); + cats.forEach(fs -> { + fs.forEach(r -> { + sortedCats.put(getTarEntryName(r.getName(), fs), r); + }); + }); + return sortedCats.values().toArray(new Resource[0]); + } + protected String getTarEntryName(String vPath, TarFileSet tarFileSet) { if (vPath.isEmpty() || vPath.startsWith("/")) throw new IllegalArgumentException("Illegal tar entry: " + vPath); diff --git a/src/net/filebot/ant/spk/PackageTask.java b/src/net/filebot/ant/spk/PackageTask.java index 0321943..6d96948 100644 --- a/src/net/filebot/ant/spk/PackageTask.java +++ b/src/net/filebot/ant/spk/PackageTask.java @@ -171,7 +171,10 @@ public void execute() throws BuildException { private void prepareSignature(File tempDirectory) { if (codesign != null) { // select files that need to be signed - spkFiles.forEach(codesign::addConfiguredCat); + spkFiles.forEach((fs) -> { + fs.setProject(getProject()); + codesign.addConfiguredCat(fs); + }); // create signature file File signatureFile = new File(tempDirectory, SYNO_SIGNATURE); diff --git a/src/net/filebot/ant/spk/openpgp/OpenPGPSecretKey.java b/src/net/filebot/ant/spk/openpgp/OpenPGPSecretKey.java new file mode 100644 index 0000000..6a06c2a --- /dev/null +++ b/src/net/filebot/ant/spk/openpgp/OpenPGPSecretKey.java @@ -0,0 +1,48 @@ +package net.filebot.ant.spk.openpgp; + +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; + +public class OpenPGPSecretKey { + + private static final long MASK = 0xFFFFFFFFL; + + private PGPSecretKey secretKey; + private char[] password; + + public OpenPGPSecretKey(String keyId, InputStream secretKeyRing, char[] password) throws IOException { + PGPObjectFactory pgpObjectFactory = new BcPGPObjectFactory(PGPUtil.getDecoderStream(secretKeyRing)); + + for (Object it = pgpObjectFactory.nextObject(); it != null; it = pgpObjectFactory.nextObject()) { + PGPSecretKeyRing pgpSecretKeyRing = (PGPSecretKeyRing) it; + PGPSecretKey pgpSecretKey = pgpSecretKeyRing.getSecretKey(); + + if (keyId == null || keyId.isEmpty() || Long.valueOf(keyId, 16) == (pgpSecretKey.getKeyID() & MASK)) { + this.secretKey = pgpSecretKey; + break; + } + } + + // sanity check + if (secretKey == null) { + throw new IllegalArgumentException("Secret key " + keyId + " not found"); + } + + this.password = password; + } + + public PGPSecretKey getSecretKey() { + return secretKey; + } + + public char[] getPassword() { + return password; + } + +} diff --git a/src/net/filebot/ant/spk/openpgp/OpenPGPSignature.java b/src/net/filebot/ant/spk/openpgp/OpenPGPSignature.java new file mode 100644 index 0000000..ebe9dd6 --- /dev/null +++ b/src/net/filebot/ant/spk/openpgp/OpenPGPSignature.java @@ -0,0 +1,71 @@ +package net.filebot.ant.spk.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Security; +import java.security.SignatureException; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +public class OpenPGPSignature { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private PGPSignatureGenerator signature; + + public OpenPGPSignature(OpenPGPSecretKey key) throws PGPException { + PGPDigestCalculatorProvider pgpDigestCalculator = new JcaPGPDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(); + PBESecretKeyDecryptor pbeSecretKeyDecryptor = new JcePBESecretKeyDecryptorBuilder(pgpDigestCalculator).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(key.getPassword()); + JcaPGPContentSignerBuilder pgpContentSigner = new JcaPGPContentSignerBuilder(key.getSecretKey().getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1).setProvider(BouncyCastleProvider.PROVIDER_NAME).setDigestProvider(BouncyCastleProvider.PROVIDER_NAME); + + signature = new PGPSignatureGenerator(pgpContentSigner); + + PGPPrivateKey privateKey = key.getSecretKey().extractPrivateKey(pbeSecretKeyDecryptor); + signature.init(PGPSignature.BINARY_DOCUMENT, privateKey); + } + + public void update(byte[] buffer, int offset, int length) throws SignatureException { + signature.update(buffer, offset, length); + } + + public void generate(OutputStream output, boolean asciiArmor) throws IOException, SignatureException, PGPException { + if (asciiArmor) { + output = new ArmoredOutputStream(output); + } + signature.generate().encode(new BCPGOutputStream(output)); + } + + public byte[] generate(boolean asciiArmor) throws IOException, SignatureException, PGPException { + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + generate(out, asciiArmor); + return out.toByteArray(); + } + + public static OpenPGPSignature createSignatureGenerator(String keyId, File secring, char[] password) throws FileNotFoundException, IOException, PGPException { + try (InputStream secretKeyRing = new FileInputStream(secring)) { + OpenPGPSecretKey key = new OpenPGPSecretKey(keyId, secretKeyRing, password); + OpenPGPSignature signature = new OpenPGPSignature(key); + return signature; + } + } + +}