From cc4870219a274192b3ef9e5e2ce5ae0833fb41a3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 13:33:17 +0100 Subject: [PATCH] Implement some operations --- bcsop-cli/pom.xml | 41 +++++++ bcsop/pom.xml | 18 +++ .../bouncycastle/sop/BouncyCastleSOP.java | 23 ++-- .../bouncycastle/sop/DetachedVerifyTest.java | 97 +++++++++++++++ .../sop/operation/AbstractBCOperation.java | 41 +++++++ .../bouncycastle/sop/operation/BCArmor.java | 4 +- .../bouncycastle/sop/operation/BCDearmor.java | 4 +- .../bouncycastle/sop/operation/BCDecrypt.java | 69 ++++------- .../sop/operation/BCDetachedSign.java | 116 ++++++++++++++++++ .../sop/operation/BCDetachedVerify.java | 72 +++++++++++ .../bouncycastle/sop/operation/BCEncrypt.java | 97 +++++++++++++++ .../sop/operation/BCExtractCert.java | 4 +- .../sop/operation/BCGenerateKey.java | 90 ++++++++++++++ .../sop/operation/BCInlineSign.java | 63 ++++++++++ .../sop/operation/BCInlineVerify.java | 60 +++++++++ .../sop/operation/BCListProfiles.java | 5 +- .../bouncycastle/sop/operation/BCVersion.java | 4 +- pom.xml | 52 +++++++- 18 files changed, 802 insertions(+), 58 deletions(-) create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/DetachedVerifyTest.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/AbstractBCOperation.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedSign.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedVerify.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCEncrypt.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCGenerateKey.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineSign.java create mode 100644 bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineVerify.java diff --git a/bcsop-cli/pom.xml b/bcsop-cli/pom.xml index 2f14c57..03173ba 100644 --- a/bcsop-cli/pom.xml +++ b/bcsop-cli/pom.xml @@ -31,6 +31,47 @@ org.bouncycastle bcprov-jdk18on + + org.bouncycastle + bcpg-jdk18on + + + org.bouncycastle + bcutil-jdk18on + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven.assembly.plugin.version} + + + jar-with-dependencies + + + + org.pgpainless.BcSopCLI + + + + + + make-assembly + package + + single + + + + + + \ No newline at end of file diff --git a/bcsop/pom.xml b/bcsop/pom.xml index ea8203c..c945daf 100644 --- a/bcsop/pom.xml +++ b/bcsop/pom.xml @@ -10,6 +10,7 @@ bcsop + jar 21 @@ -30,6 +31,23 @@ org.bouncycastle bcpg-jdk18on + + org.bouncycastle + bcutil-jdk18on + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + \ No newline at end of file diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java index b50d8ba..7fc22a9 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/BouncyCastleSOP.java @@ -1,12 +1,21 @@ package org.pgpainless.bouncycastle.sop; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jetbrains.annotations.NotNull; import org.pgpainless.bouncycastle.sop.operation.*; import sop.SOP; import sop.operation.*; +import java.security.Security; + public class BouncyCastleSOP implements SOP { + public BouncyCastleSOP() + { + Security.removeProvider("BC"); + Security.addProvider(new BouncyCastleProvider()); + } + @NotNull @Override public Armor armor() { @@ -16,7 +25,7 @@ public class BouncyCastleSOP implements SOP { @NotNull @Override public GenerateKey generateKey() { - return null; + return new BCGenerateKey(); } @NotNull @@ -28,13 +37,13 @@ public class BouncyCastleSOP implements SOP { @NotNull @Override public DetachedSign detachedSign() { - return null; + return new BCDetachedSign(); } @NotNull @Override public InlineSign inlineSign() { - return null; + return new BCInlineSign(); } @NotNull @@ -46,13 +55,13 @@ public class BouncyCastleSOP implements SOP { @NotNull @Override public Encrypt encrypt() { - return null; + return new BCEncrypt(); } @NotNull @Override public Decrypt decrypt() { - return null; + return new BCDecrypt(); } @NotNull @@ -88,12 +97,12 @@ public class BouncyCastleSOP implements SOP { @NotNull @Override public DetachedVerify detachedVerify() { - return null; + return new BCDetachedVerify(); } @NotNull @Override public InlineVerify inlineVerify() { - return null; + return new BCInlineVerify(); } } diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/DetachedVerifyTest.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/DetachedVerifyTest.java new file mode 100644 index 0000000..8ca11c0 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/DetachedVerifyTest.java @@ -0,0 +1,97 @@ +package org.pgpainless.bouncycastle.sop; + +import sop.Verification; +import sop.operation.DetachedVerify; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class DetachedVerifyTest { + + static String SIG = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wnUEABYKAB0WIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCZ1mi4QAKCRDyMVUMT0fj\n" + + "jun3AP9ZiRPXlow7ywR3D7nC4/GGUC+PueZ1GkcXDtE8zbAiTgEArkOQO8qhC0QG\n" + + "K6TKEPJM07Zie/kgcGyh8HfSpbunwAXCwPMEAAEKAB0WIQTRpm4aI7GCyZgPeIz7\n" + + "/MgqAV5zMAUCZ1mi4QAKCRD7/MgqAV5zMI51C/sFv6vjBbxdgNMp1qwJ3AqMhbtV\n" + + "WJ7z8HJraT+dW0VKYmib7H7lL3x3gTC85VaOWGU3mqhxZLQVi6j99pbbZjGXJADF\n" + + "e5E1S+tp6js56OI4fY/kchLMcz1JxgjSJPJnlyEXy5C2/2RlI1JmpUn6Y8YOlNhZ\n" + + "Vbov36QB2ILt+k62ElQeHgWQYqWWMmxmdk9wipNKYfBoQNVwJNICDjI+XlwL4cGw\n" + + "nyg3cdgkKXikPFEBhlApI5HmX4jOv68gReEMMTaZoQoRD8kzg9m7KC6BqHpG0TAZ\n" + + "Bz5TzZrxW1LdXj/tKqNONcldHHzO4mZIUkdOowGMBmEf0kZ2nVHZGaWE1T7IqJPo\n" + + "cIPtZB+EpDCcPQN35ekC0B1ilDd07IulNHOdbhI7xu0/eQb6dGpkzkak3OpWbGtX\n" + + "rKfxljqLFTnnJfM95UlrlCWwEplRZ2q4QhXi8/mRM/Szz5D3KGd7hnLRnr42J4Dr\n" + + "j/XWKoSXmFZ5t6CBq7BgubE1uO2ToRWOlas+jNE=\n" + + "=bJ8+\n" + + "-----END PGP SIGNATURE-----"; + static String ALICE = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP certificate\n" + + "\n" + + "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=iIGO\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + static String BOB = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Bob's OpenPGP certificate\n" + + "\n" + + "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\n" + + "NEJd3XZRzaXZE2aAMQ==\n" + + "=NXei\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + static String MSG = "Hello World :)"; + + public static void main(String[] args) throws IOException { + BouncyCastleSOP sop = new BouncyCastleSOP(); + ByteArrayInputStream sigIn = new ByteArrayInputStream(SIG.getBytes(StandardCharsets.UTF_8)); + DetachedVerify verify = sop.detachedVerify(); + verify.signatures(sigIn); + verify.cert(new ByteArrayInputStream(ALICE.getBytes(StandardCharsets.UTF_8))) + .cert(new ByteArrayInputStream(BOB.getBytes(StandardCharsets.UTF_8))); + List verif = verify.data(new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/AbstractBCOperation.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/AbstractBCOperation.java new file mode 100644 index 0000000..9a37617 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/AbstractBCOperation.java @@ -0,0 +1,41 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; +import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.util.encoders.Hex; +import sop.SessionKey; +import sop.Verification; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractBCOperation +{ + + protected SessionKey getSessionKey(OpenPGPMessageInputStream.Result result) + { + PGPSessionKey sessionKey = result.getSessionKey(); + if (sessionKey == null) + { + return null; + } + return new SessionKey((byte) sessionKey.getAlgorithm(), sessionKey.getKey()); + } + + protected List getVerifications(OpenPGPMessageInputStream.Result result) + { + List verifications = new ArrayList<>(); + for (OpenPGPSignature.OpenPGPDocumentSignature sig : result.getSignatures()) + { + if (sig.isValid()) + { + Verification verification = new Verification(sig.getCreationTime(), + Hex.toHexString(sig.getIssuer().getKeyIdentifier().getFingerprint()), + Hex.toHexString(sig.getIssuerCertificate().getFingerprint())); + verifications.add(verification); + } + } + return verifications; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCArmor.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCArmor.java index 9de9904..cdc1a16 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCArmor.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCArmor.java @@ -12,7 +12,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public class BCArmor implements Armor { +public class BCArmor + extends AbstractBCOperation + implements Armor { @NotNull @Override diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDearmor.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDearmor.java index d1b2287..78f3de7 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDearmor.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDearmor.java @@ -11,7 +11,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public class BCDearmor implements Dearmor { +public class BCDearmor + extends AbstractBCOperation + implements Dearmor { @NotNull @Override diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDecrypt.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDecrypt.java index 251c48f..7b65fad 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDecrypt.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDecrypt.java @@ -1,9 +1,11 @@ package org.pgpainless.bouncycastle.sop.operation; import org.bouncycastle.openpgp.*; -import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; +import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor; +import org.bouncycastle.util.io.Streams; import org.jetbrains.annotations.NotNull; import sop.DecryptionResult; import sop.ReadyWithResult; @@ -14,21 +16,21 @@ import sop.operation.Decrypt; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StreamCorruptedException; import java.util.ArrayList; import java.util.Date; import java.util.List; -public class BCDecrypt implements Decrypt { +public class BCDecrypt + extends AbstractBCOperation + implements Decrypt { private Date notBefore = new Date(Long.MAX_VALUE); // end of time private Date notAfter = new Date(); // now - private final List sessionKeys = new ArrayList<>(); - private final List messagePassphrases = new ArrayList<>(); - private final List verificationCerts = new ArrayList<>(); private final List encryptionKeys = new ArrayList<>(); private final List encryptionKeyPassphrases = new ArrayList<>(); + private final OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + @NotNull @Override @@ -36,32 +38,17 @@ public class BCDecrypt implements Decrypt { return new ReadyWithResult() { @Override public DecryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { - InputStream in = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory objFac = new BcPGPObjectFactory(in); - Object next = objFac.nextObject(); - - if (next instanceof PGPEncryptedDataList) { - PGPEncryptedDataList encDataList = (PGPEncryptedDataList) next; - - if (!sessionKeys.isEmpty()) { - PGPSessionKeyEncryptedData sesKeyEnc = encDataList.extractSessionKeyEncryptedData(); - SessionKeyDataDecryptorFactory decFac = new BcSessionKeyDataDecryptorFactory(sessionKeys.get(0)); - try { - in = sesKeyEnc.getDataStream(decFac); - objFac = new BcPGPObjectFactory(in); - next = objFac.nextObject(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } else { - for (PGPEncryptedData encData : encDataList) { - if (encData instanceof PGPPublicKeyEncryptedData) { - - } - } - } + try { + OpenPGPMessageInputStream mIn = processor.process(inputStream); + Streams.pipeAll(mIn, outputStream); + mIn.close(); + OpenPGPMessageInputStream.Result result = mIn.getResult(); + return new DecryptionResult( + getSessionKey(result), + getVerifications(result)); + } catch (PGPException e) { + throw new RuntimeException(e); } - return null; } }; } @@ -83,34 +70,30 @@ public class BCDecrypt implements Decrypt { @NotNull @Override public Decrypt verifyWithCert(@NotNull InputStream inputStream) throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - InputStream decodeIn = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory objFac = new BcPGPObjectFactory(decodeIn); - PGPPublicKeyRing cert = (PGPPublicKeyRing) objFac.nextObject(); - verificationCerts.add(cert); + OpenPGPCertificate cert = OpenPGPCertificate.fromInputStream(inputStream); + processor.addVerificationCertificate(cert); return this; } @NotNull @Override public Decrypt withSessionKey(@NotNull SessionKey sessionKey) throws SOPGPException.UnsupportedOption { - this.sessionKeys.add(new PGPSessionKey(sessionKey.getAlgorithm(), sessionKey.getKey())); + processor.setSessionKey(new PGPSessionKey(sessionKey.getAlgorithm(), sessionKey.getKey())); return this; } @NotNull @Override public Decrypt withPassword(@NotNull String s) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - this.messagePassphrases.add(s); + processor.addMessagePassphrase(s.toCharArray()); return this; } @NotNull @Override public Decrypt withKey(@NotNull InputStream inputStream) throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - InputStream decodeIn = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory objFac = new BcPGPObjectFactory(decodeIn); - PGPSecretKeyRing key = (PGPSecretKeyRing) objFac.nextObject(); - this.encryptionKeys.add(key); + OpenPGPKey key = OpenPGPKey.fromInputStream(inputStream); + processor.addDecryptionKey(key); return this; } diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedSign.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedSign.java new file mode 100644 index 0000000..87562cd --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedSign.java @@ -0,0 +1,116 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureGenerator; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException; +import org.jetbrains.annotations.NotNull; +import sop.MicAlg; +import sop.ReadyWithResult; +import sop.SigningResult; +import sop.enums.SignAs; +import sop.exception.SOPGPException; +import sop.operation.DetachedSign; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +public class BCDetachedSign + extends AbstractBCOperation + implements DetachedSign { + + private final OpenPGPDetachedSignatureGenerator sigGen = new OpenPGPDetachedSignatureGenerator(); + private boolean armored = true; + + @NotNull + @Override + public ReadyWithResult data(@NotNull InputStream inputStream) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + return new ReadyWithResult() { + @Override + public SigningResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { + try + { + List signatures = sigGen.sign(inputStream); + OutputStream aOut = null; + OutputStream pOut; + if (armored) + { + aOut = ArmoredOutputStream.builder() + .clearHeaders() + .build(outputStream); + pOut = BCPGOutputStream.wrap(aOut); + } + else + { + pOut = BCPGOutputStream.wrap(outputStream); + } + + for (OpenPGPSignature.OpenPGPDocumentSignature sig : signatures) + { + pOut.write(sig.getSignature().getEncoded()); + } + + pOut.close(); + if (aOut != null) + { + aOut.close(); + } + + return new SigningResult(MicAlg.fromHashAlgorithmId(signatures.get(0).getSignature().getHashAlgorithm())); + } + catch (PGPException e) + { + throw new RuntimeException(e); + } + } + }; + } + + @NotNull + @Override + public DetachedSign mode(@NotNull SignAs signAs) throws SOPGPException.UnsupportedOption { + switch (signAs) + { + case text: + sigGen.setCanonicalTextDocument(); + break; + case binary: + sigGen.setBinarySignature(); + break; + } + return this; + } + + @Override + public DetachedSign noArmor() { + armored = false; + return this; + } + + @Override + public DetachedSign key(@NotNull InputStream inputStream) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + try + { + sigGen.addSigningKey(OpenPGPKey.fromInputStream(inputStream), null); + } + catch (InvalidSigningKeyException e) + { + throw new SOPGPException.KeyCannotSign("Key cannot sign", e); + } + catch (PGPException e) + { + throw new RuntimeException(e); + } + return this; + } + + @Override + public DetachedSign withKeyPassword(@NotNull byte[] bytes) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + return this; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedVerify.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedVerify.java new file mode 100644 index 0000000..e3d3a28 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCDetachedVerify.java @@ -0,0 +1,72 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPDetachedSignatureProcessor; +import org.bouncycastle.openpgp.api.OpenPGPSignature; +import org.bouncycastle.util.encoders.Hex; +import org.jetbrains.annotations.NotNull; +import sop.Verification; +import sop.exception.SOPGPException; +import sop.operation.DetachedVerify; +import sop.operation.VerifySignatures; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class BCDetachedVerify + extends AbstractBCOperation + implements DetachedVerify +{ + + OpenPGPDetachedSignatureProcessor processor = new OpenPGPDetachedSignatureProcessor(); + + public BCDetachedVerify() + { + processor.setExceptionCallback(e -> System.err.println(e.getMessage())); + } + + @NotNull + @Override + public VerifySignatures signatures(@NotNull InputStream inputStream) throws SOPGPException.BadData, IOException { + processor.addSignatures(inputStream); + return this; + } + + @Override + public DetachedVerify notBefore(@NotNull Date date) throws SOPGPException.UnsupportedOption { + return this; + } + + @Override + public DetachedVerify notAfter(@NotNull Date date) throws SOPGPException.UnsupportedOption { + return this; + } + + @Override + public DetachedVerify cert(@NotNull InputStream inputStream) throws SOPGPException.BadData, IOException { + processor.addVerificationCertificate(OpenPGPCertificate.fromInputStream(inputStream)); + return this; + } + + @NotNull + @Override + public List data(@NotNull InputStream inputStream) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + List signatures = processor.verify(inputStream); + + List verifications = new ArrayList<>(); + for (OpenPGPSignature.OpenPGPDocumentSignature signature : signatures) + { + if (signature.isValidAt(signature.getCreationTime())) + { + verifications.add(new Verification( + signature.getCreationTime(), + Hex.toHexString(signature.getIssuer().getKeyIdentifier().getFingerprint()), + Hex.toHexString(signature.getIssuerCertificate().getFingerprint()))); + } + } + return verifications; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCEncrypt.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCEncrypt.java new file mode 100644 index 0000000..f34cca8 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCEncrypt.java @@ -0,0 +1,97 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; +import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.util.io.Streams; +import org.jetbrains.annotations.NotNull; +import sop.EncryptionResult; +import sop.ReadyWithResult; +import sop.SessionKey; +import sop.enums.EncryptAs; +import sop.exception.SOPGPException; +import sop.operation.Encrypt; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BCEncrypt + extends AbstractBCOperation + implements Encrypt { + + private final OpenPGPMessageGenerator mGen; + + public BCEncrypt() + { + this.mGen = new OpenPGPMessageGenerator(); + } + + @NotNull + @Override + public Encrypt mode(@NotNull EncryptAs encryptAs) throws SOPGPException.UnsupportedOption { + // TODO: Implement + return this; + } + + @NotNull + @Override + public Encrypt noArmor() { + mGen.setArmored(false); + return this; + } + + @NotNull + @Override + public Encrypt signWith(@NotNull InputStream inputStream) throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { + return this; + } + + @NotNull + @Override + public Encrypt withKeyPassword(@NotNull byte[] bytes) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + return this; + } + + @NotNull + @Override + public Encrypt withPassword(@NotNull String s) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + mGen.addEncryptionPassphrase(s.toCharArray()); + return this; + } + + @NotNull + @Override + public Encrypt withCert(@NotNull InputStream inputStream) throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { + mGen.addEncryptionCertificate(OpenPGPCertificate.fromInputStream(inputStream)); + return this; + } + + @NotNull + @Override + public Encrypt profile(@NotNull String s) { + // TODO: Implement + return this; + } + + @NotNull + @Override + public ReadyWithResult plaintext(@NotNull InputStream inputStream) throws IOException, SOPGPException.KeyIsProtected { + return new ReadyWithResult() { + SessionKey sessionKey = null; + @Override + public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { + mGen.setSessionKeyExtractionCallback(sk -> this.sessionKey = new SessionKey((byte) sk.getAlgorithm(), sk.getKey())); + try { + OpenPGPMessageOutputStream mOut = mGen.open(outputStream); + Streams.pipeAll(inputStream, mOut); + mOut.close(); + return new EncryptionResult(sessionKey); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + }; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCExtractCert.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCExtractCert.java index d0ad5a2..2f43c31 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCExtractCert.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCExtractCert.java @@ -15,7 +15,9 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -public class BCExtractCert implements ExtractCert { +public class BCExtractCert + extends AbstractBCOperation + implements ExtractCert { private boolean armor = true; diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCGenerateKey.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCGenerateKey.java new file mode 100644 index 0000000..946b868 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCGenerateKey.java @@ -0,0 +1,90 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; +import org.jetbrains.annotations.NotNull; +import sop.Ready; +import sop.exception.SOPGPException; +import sop.operation.GenerateKey; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class BCGenerateKey + extends AbstractBCOperation + implements GenerateKey { + + private boolean armor = true; + private boolean signOnly = false; + private String userId; + private char[] passphrase; + + @NotNull + @Override + public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + return new Ready() + { + @Override + public void writeTo(@NotNull OutputStream outputStream) throws IOException + { + OpenPGPV6KeyGenerator generator = new BcOpenPGPV6KeyGenerator(new Date()); + try + { + PGPSecretKeyRing keyRing = generator.ed25519x25519Key(userId, passphrase); + OpenPGPKey key = new OpenPGPKey(keyRing); + if (armor) + { + outputStream.write(key.toAsciiArmoredString().getBytes(StandardCharsets.UTF_8)); + } + else + { + keyRing.encode(outputStream); + } + } + catch (PGPException e) + { + throw new RuntimeException(e); + } + } + }; + } + + @NotNull + @Override + public GenerateKey noArmor() { + this.armor = false; + return this; + } + + @NotNull + @Override + public GenerateKey userId(@NotNull String s) { + this.userId = s; + return this; + } + + @NotNull + @Override + public GenerateKey withKeyPassword(@NotNull String s) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + this.passphrase = s.toCharArray(); + return this; + } + + @NotNull + @Override + public GenerateKey profile(@NotNull String s) { + return this; + } + + @NotNull + @Override + public GenerateKey signingOnly() { + this.signOnly = true; + return this; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineSign.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineSign.java new file mode 100644 index 0000000..a8f3892 --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineSign.java @@ -0,0 +1,63 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator; +import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream; +import org.bouncycastle.util.io.Streams; +import org.jetbrains.annotations.NotNull; +import sop.Ready; +import sop.enums.InlineSignAs; +import sop.exception.SOPGPException; +import sop.operation.InlineSign; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BCInlineSign + extends AbstractBCOperation + implements InlineSign { + + private final OpenPGPMessageGenerator mGen = new OpenPGPMessageGenerator(); + + @NotNull + @Override + public Ready data(@NotNull InputStream inputStream) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + return new Ready() { + @Override + public void writeTo(@NotNull OutputStream outputStream) throws IOException { + try { + OpenPGPMessageOutputStream mOut = mGen.open(outputStream); + Streams.pipeAll(inputStream, mOut); + mOut.close(); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + }; + } + + @NotNull + @Override + public InlineSign mode(@NotNull InlineSignAs inlineSignAs) throws SOPGPException.UnsupportedOption { + return this; + } + + @Override + public InlineSign noArmor() { + mGen.setArmored(false); + return this; + } + + @Override + public InlineSign key(@NotNull InputStream inputStream) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + mGen.addSigningKey(OpenPGPKey.fromInputStream(inputStream)); + return this; + } + + @Override + public InlineSign withKeyPassword(@NotNull byte[] bytes) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + return this; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineVerify.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineVerify.java new file mode 100644 index 0000000..a747bea --- /dev/null +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCInlineVerify.java @@ -0,0 +1,60 @@ +package org.pgpainless.bouncycastle.sop.operation; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream; +import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor; +import org.bouncycastle.util.io.Streams; +import org.jetbrains.annotations.NotNull; +import sop.ReadyWithResult; +import sop.Verification; +import sop.exception.SOPGPException; +import sop.operation.InlineVerify; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; + +public class BCInlineVerify + extends AbstractBCOperation + implements InlineVerify { + + private final OpenPGPMessageProcessor processor = new OpenPGPMessageProcessor(); + + @NotNull + @Override + public ReadyWithResult> data(@NotNull InputStream inputStream) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + return new ReadyWithResult>() { + @Override + public List writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { + try { + OpenPGPMessageInputStream mIn = processor.process(inputStream); + Streams.pipeAll(mIn, outputStream); + mIn.close(); + OpenPGPMessageInputStream.Result result = mIn.getResult(); + return getVerifications(result); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + }; + } + + @Override + public InlineVerify notBefore(@NotNull Date date) throws SOPGPException.UnsupportedOption { + return this; + } + + @Override + public InlineVerify notAfter(@NotNull Date date) throws SOPGPException.UnsupportedOption { + return this; + } + + @Override + public InlineVerify cert(@NotNull InputStream inputStream) throws SOPGPException.BadData, IOException { + processor.addVerificationCertificate(OpenPGPCertificate.fromInputStream(inputStream)); + return this; + } +} diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCListProfiles.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCListProfiles.java index 6b31387..5d3317d 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCListProfiles.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCListProfiles.java @@ -6,7 +6,10 @@ import sop.operation.ListProfiles; import java.util.List; -public class BCListProfiles implements ListProfiles { +public class BCListProfiles + extends AbstractBCOperation + implements ListProfiles { + @NotNull @Override public List subcommand(@NotNull String s) { diff --git a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCVersion.java b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCVersion.java index cb7e1ce..02c53a6 100644 --- a/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCVersion.java +++ b/bcsop/src/main/java/org/pgpainless/bouncycastle/sop/operation/BCVersion.java @@ -6,7 +6,9 @@ import org.jetbrains.annotations.Nullable; import sop.exception.SOPGPException; import sop.operation.Version; -public class BCVersion implements Version { +public class BCVersion + extends AbstractBCOperation + implements Version { @NotNull @Override diff --git a/pom.xml b/pom.xml index 52c5d17..38a5952 100644 --- a/pom.xml +++ b/pom.xml @@ -17,19 +17,33 @@ 21 21 UTF-8 + 3.3.0 + + + local-maven-repo + file:///home/vanitas/.m2/ + + + + org.bouncycastle bcprov-jdk18on - 1.79 + 1.80-SNAPSHOT org.bouncycastle bcpg-jdk18on - 1.79 + 1.80-SNAPSHOT + + + org.bouncycastle + bcutil-jdk18on + 1.80-SNAPSHOT org.pgpainless @@ -43,5 +57,37 @@ - + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.assembly.plugin.version} + + + + true + org.pgpainless.BcSopCLI + + + + + + + \ No newline at end of file