mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Fix decryption, WiP
This commit is contained in:
parent
afce16e51e
commit
5d8effb0c5
10 changed files with 883 additions and 3 deletions
171
src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java
Normal file
171
src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java
Normal file
|
@ -0,0 +1,171 @@
|
|||
package de.vanitasvitae.crypto.pgpainless;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
import java.util.Collections;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.encryption_signing.SecretKeyRingDecryptor;
|
||||
import de.vanitasvitae.crypto.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args)
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
|
||||
IOException {
|
||||
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
PGPSecretKeyRing a = PGPainless.generateKeyRing().simpleRsaKeyRing("a@b.c", RsaLength._2048);
|
||||
PGPSecretKeyRing b = PGPainless.generateKeyRing().simpleRsaKeyRing("b@c.d", RsaLength._2048);
|
||||
|
||||
SecretKeyRingDecryptor secretKeyRingDecryptor = new SecretKeyRingDecryptor() {
|
||||
@Override
|
||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
byte[] m = "Dies ist ein verschlüsselter Text.".getBytes();
|
||||
ByteArrayInputStream fromPlain = new ByteArrayInputStream(m);
|
||||
ByteArrayOutputStream toEncrypted = new ByteArrayOutputStream();
|
||||
|
||||
OutputStream encryptor = PGPainless.createEncryptor().onOutputStream(toEncrypted)
|
||||
.toRecipient(b.getPublicKey())
|
||||
.usingAlgorithms(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA512, CompressionAlgorithm.UNCOMPRESSED)
|
||||
.signWith(a.getSecretKey(), secretKeyRingDecryptor)
|
||||
.asciiArmor();
|
||||
|
||||
Streams.pipeAll(fromPlain, encryptor);
|
||||
fromPlain.close();
|
||||
encryptor.close();
|
||||
|
||||
System.out.println(new String(toEncrypted.toByteArray()));
|
||||
|
||||
ByteArrayInputStream fromEncrypted = new ByteArrayInputStream(toEncrypted.toByteArray());
|
||||
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
|
||||
|
||||
PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor()
|
||||
.onInputStream(fromEncrypted)
|
||||
.decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor)
|
||||
.verifyWith(Collections.singleton(a.getPublicKey().getKeyID()),
|
||||
Collections.singleton(new PGPPublicKeyRing(a.getPublicKey().getEncoded(), new BcKeyFingerprintCalculator())))
|
||||
.ignoreMissingPublicKeys()
|
||||
.build();
|
||||
|
||||
InputStream decryptor = resultAndInputStream.getInputStream();
|
||||
|
||||
Streams.pipeAll(decryptor, toPlain);
|
||||
decryptor.close();
|
||||
toPlain.close();
|
||||
|
||||
PainlessResult result = resultAndInputStream.getResult();
|
||||
|
||||
System.out.println(b.getPublicKey().getKeyID() + " " + result.getDecryptionKeyId());
|
||||
System.out.println(new String(toPlain.toByteArray()));
|
||||
}
|
||||
|
||||
private static void gpg(PGPSecretKeyRing a, PGPSecretKeyRing b, SecretKeyRingDecryptor secretKeyRingDecryptor)
|
||||
throws IOException, PGPException {
|
||||
String gpg = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"hQGMAwAAAAAAAAAAAQv+JyovfiPxDiLe9XlgQAG6zD+YdRtZRuUJD+A+ZX4Sn0w0\n" +
|
||||
"2Dl9Ehf7lKjIo0cIfVOUrgITnWIRWAyfrk5KiXdXcZ6dXxz/YJFnLSlgqUwq7GWi\n" +
|
||||
"NYf3Uqg+/8f3Ucl0x6sr1oddwB9OI7zRJwDqEzTORjLBu1vtDlFPMPWwAeqDtZgz\n" +
|
||||
"ikT6vSFfhVjVbgx4mztw7hatWNjXzNkl9+lojzo9IyiA+3SBsRe/2My3ZBjPx97f\n" +
|
||||
"3YGMCvbggdX3C/MRV2iek2pFX7YTKasFeEy5Y1c09upqaEIpaJq8vi1Fu44dv0Rt\n" +
|
||||
"gv4sdljaJXsFn9aoVFrp/xU4SyPiC1Z/KjqE3Zfyh+OMWKoWmtYH07/g8IGkkBCh\n" +
|
||||
"xuDiyy813WS3xtCyX405Vd+rxYC3y1h1FtthdO/AIrYSWj6qI6hyK2tyYmwsg+oY\n" +
|
||||
"1oaXhcTbWXsBO89v0YtmVK1bPVXq8ao/DQvrs84JsYsKzXA17gKyBLBUoTNn8h8A\n" +
|
||||
"AXg455AN8iHi2u5pAKr7hQIMAwAAAAAAAAAAAQ/7Bg8SGEfMPmtDy/BazrYWaXvX\n" +
|
||||
"1+WWRNM630ULPEq7LG4BKJJOOrhk0kNkjIsaXhgqn9bt3YxxuE1eOQksjn0sNOD7\n" +
|
||||
"3NAOicHzQ7xOarvN9OUSGirc+EIn4ETRGKYF1TXHBSYnBeb+DLCbRZkBZhRrA5Lc\n" +
|
||||
"z08kWrGRfq5Bz6eMatBTO1L8XTIxHPgc9/LNv7OqcIfT0udjOQMkA7oxCz5mLl2b\n" +
|
||||
"dApsDEFNKNaGgRzSf2rDqw0SGkDxYsXI6IYrVSEm6uDt+ScybS0KkcEgg+I9l91n\n" +
|
||||
"XgqQQaXpYnHgETqKYfcUOk5iEND5Lvik/XhHNViaL3CdOkxFLTa0wfy0y0IsV2Y3\n" +
|
||||
"xGkMOWdDjXlY8UWRgoK61M91phgZ48zfSoVvXNDrjOJzm1jn8CFFFov4Gse7CtlM\n" +
|
||||
"A+3ntVdjL94jkp+2mU3e9kzCOG+ChylLuqlGTvavbHt/rzuZooi/6g1VHy1r+v9I\n" +
|
||||
"rWKX6q55H8JzZXZOrfED39QocK9b1BjtEca/Qnqw82+IVY/CufBmnmbOWUkHq1zP\n" +
|
||||
"6nj840HxH1zV5vHf8vlXxV7/iBesAF94dLT/Hp0E7+Ilyp/pQaQjMS2RLycMUJQJ\n" +
|
||||
"pQey81gpuOWD6YIbvgnrMBMrJLyJSk3r3sMdJ3DCPxHC+OyvHxddA5TdL2e4aP3L\n" +
|
||||
"OzKql59v1w+9Doe3LEPSPgEVAdUUg0nEl5lg9LLqaepaYp8NfsEC1Rnk/MLxciJu\n" +
|
||||
"9oNjqPqQxKTv4aQO/Qb8gHFb3O34OnNKz+CzrX5Q\n" +
|
||||
"=sVVl\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(gpg.getBytes());
|
||||
|
||||
InputStream decryptor = PGPainless.createDecryptor().onInputStream(inputStream)
|
||||
.decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor)
|
||||
.doNotVerify()
|
||||
.build()
|
||||
.getInputStream();
|
||||
|
||||
}
|
||||
|
||||
public static void symm(PGPSecretKeyRing a, PGPSecretKeyRing b, SecretKeyRingDecryptor secretKeyRingDecryptor)
|
||||
throws IOException, PGPException {
|
||||
byte[] bytes = "Diese Nachricht ist streng geheim!!!".getBytes(Charset.forName("UTF-8"));
|
||||
ByteArrayInputStream fromPlain = new ByteArrayInputStream(bytes);
|
||||
ByteArrayOutputStream toEncrypted = new ByteArrayOutputStream();
|
||||
|
||||
OutputStream encryptor = PGPainless.createEncryptor()
|
||||
.onOutputStream(toEncrypted)
|
||||
.toRecipient(b.getPublicKey())
|
||||
.usingAlgorithms(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA512, CompressionAlgorithm.UNCOMPRESSED)
|
||||
.signWith(a.getSecretKey(), secretKeyRingDecryptor)
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(fromPlain, encryptor);
|
||||
encryptor.close();
|
||||
|
||||
System.out.println(new String(toEncrypted.toByteArray(), Charset.forName("UTF-8")));
|
||||
|
||||
ByteArrayInputStream fromEncrypted = new ByteArrayInputStream(toEncrypted.toByteArray());
|
||||
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
|
||||
|
||||
PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor()
|
||||
.onInputStream(fromEncrypted)
|
||||
.decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor)
|
||||
.verifyWith(Collections.singleton(a.getPublicKey().getKeyID()),
|
||||
Collections.singleton(new PGPPublicKeyRing(a.getPublicKey().getEncoded(), new BcKeyFingerprintCalculator())))
|
||||
.ignoreMissingPublicKeys()
|
||||
.build();
|
||||
|
||||
InputStream decryptor = resultAndInputStream.getInputStream();
|
||||
|
||||
Streams.pipeAll(decryptor, toPlain);
|
||||
decryptor.close();
|
||||
|
||||
PainlessResult result = resultAndInputStream.getResult();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package de.vanitasvitae.crypto.pgpainless;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
|
||||
public class PGPv4Fingerprint {
|
||||
|
||||
private final byte[] fingerprintBytes;
|
||||
|
||||
public PGPv4Fingerprint(PGPPublicKey publicKey) {
|
||||
if (publicKey.getVersion() != 4) {
|
||||
throw new IllegalArgumentException("PublicKey is not a OpenPGP v4 Public Key.");
|
||||
}
|
||||
this.fingerprintBytes = publicKey.getFingerprint();
|
||||
}
|
||||
|
||||
public PGPv4Fingerprint(PGPSecretKey secretKey) {
|
||||
this(secretKey.getPublicKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(o instanceof PGPv4Fingerprint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(fingerprintBytes, ((PGPv4Fingerprint) o).fingerprintBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(fingerprintBytes);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,139 @@
|
|||
package de.vanitasvitae.crypto.pgpainless;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
|
||||
public class PainlessResult {
|
||||
Set<Long> signingKeys;
|
||||
Long decryptingKey;
|
||||
|
||||
private final Set<Long> recipientKeyIds;
|
||||
private final Long decryptionKeyId;
|
||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||
private final CompressionAlgorithm compressionAlgorithm;
|
||||
private final boolean integrityProtected;
|
||||
private final Set<Long> signatureKeyIds;
|
||||
private final Set<Long> verifiedSignatureKeyIds;
|
||||
|
||||
public PainlessResult(Set<Long> recipientKeyIds,
|
||||
Long decryptionKeyId,
|
||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||
CompressionAlgorithm algorithm,
|
||||
boolean integrityProtected,
|
||||
Set<Long> signatureKeyIds,
|
||||
Set<Long> verifiedSignatureKeyIds) {
|
||||
|
||||
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
||||
this.decryptionKeyId = decryptionKeyId;
|
||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
this.compressionAlgorithm = algorithm;
|
||||
this.integrityProtected = integrityProtected;
|
||||
this.signatureKeyIds = Collections.unmodifiableSet(signatureKeyIds);
|
||||
this.verifiedSignatureKeyIds = Collections.unmodifiableSet(verifiedSignatureKeyIds);
|
||||
}
|
||||
|
||||
public Set<Long> getRecipientKeyIds() {
|
||||
return recipientKeyIds;
|
||||
}
|
||||
|
||||
public Long getDecryptionKeyId() {
|
||||
return decryptionKeyId;
|
||||
}
|
||||
|
||||
public SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
|
||||
return symmetricKeyAlgorithm;
|
||||
}
|
||||
|
||||
public CompressionAlgorithm getCompressionAlgorithm() {
|
||||
return compressionAlgorithm;
|
||||
}
|
||||
|
||||
public boolean isIntegrityProtected() {
|
||||
return integrityProtected;
|
||||
}
|
||||
|
||||
public Set<Long> getSignatureKeyIds() {
|
||||
return signatureKeyIds;
|
||||
}
|
||||
|
||||
public Set<Long> getVerifiedSignatureKeyIds() {
|
||||
return verifiedSignatureKeyIds;
|
||||
}
|
||||
|
||||
public static Builder getBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Set<Long> recipientKeyIds = new HashSet<>();
|
||||
private Long decryptionKeyId;
|
||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||
private boolean integrityProtected = false;
|
||||
private final Set<Long> signatureKeyIds = new HashSet<>();
|
||||
private final Set<Long> verifiedSignatureKeyIds = new HashSet<>();
|
||||
|
||||
public Builder addRecipientKeyId(long id) {
|
||||
this.recipientKeyIds.add(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDecryptionKeyId(long id) {
|
||||
this.decryptionKeyId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
|
||||
this.compressionAlgorithm = algorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addSignatureKeyId(long id) {
|
||||
this.signatureKeyIds.add(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addVerifiedSignatureKeyId(long id) {
|
||||
this.verifiedSignatureKeyIds.add(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIntegrityProtected(boolean integrityProtected) {
|
||||
this.integrityProtected = integrityProtected;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PainlessResult build() {
|
||||
return new PainlessResult(recipientKeyIds, decryptionKeyId, symmetricKeyAlgorithm, compressionAlgorithm, integrityProtected, signatureKeyIds, verifiedSignatureKeyIds);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResultAndInputStream {
|
||||
private final PainlessResult.Builder resultBuilder;
|
||||
private final PainlessStream.In inputStream;
|
||||
|
||||
public ResultAndInputStream(PainlessResult.Builder resultBuilder, PainlessStream.In inputStream) {
|
||||
this.resultBuilder = resultBuilder;
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
public PainlessResult getResult() {
|
||||
if (!inputStream.isClosed()) {
|
||||
throw new IllegalStateException("InputStream must be closed before the PainlessResult can be accessed.");
|
||||
}
|
||||
return resultBuilder.build();
|
||||
}
|
||||
|
||||
public PainlessStream.In getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package de.vanitasvitae.crypto.pgpainless;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface PainlessStream {
|
||||
|
||||
boolean isClosed();
|
||||
|
||||
class In extends InputStream implements PainlessStream {
|
||||
private final InputStream inputStream;
|
||||
|
||||
private boolean isClosed = false;
|
||||
|
||||
public In(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
}
|
||||
|
||||
class Out extends OutputStream implements PainlessStream {
|
||||
|
||||
private final OutputStream outputStream;
|
||||
private boolean isClosed = false;
|
||||
|
||||
public Out(OutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int i) throws IOException {
|
||||
outputStream.write(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
outputStream.close();
|
||||
this.isClosed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,97 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
public class DecryptionBuilder {
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.PainlessResult;
|
||||
import de.vanitasvitae.crypto.pgpainless.encryption_signing.SecretKeyRingDecryptor;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
|
||||
public class DecryptionBuilder implements DecryptionBuilderInterface {
|
||||
|
||||
private InputStream inputStream;
|
||||
private PGPSecretKeyRingCollection decryptionKeys;
|
||||
private SecretKeyRingDecryptor decryptionKeyDecryptor;
|
||||
private Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
|
||||
private Set<Long> trustedKeyIds = new HashSet<>();
|
||||
private MissingPublicKeyCallback missingPublicKeyCallback = null;
|
||||
|
||||
@Override
|
||||
public DecryptWith onInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
return new DecryptWithImpl();
|
||||
}
|
||||
|
||||
class DecryptWithImpl implements DecryptWith {
|
||||
|
||||
@Override
|
||||
public VerifyWith decryptWith(PGPSecretKeyRingCollection secretKeyRings, SecretKeyRingDecryptor decryptor) {
|
||||
DecryptionBuilder.this.decryptionKeys = secretKeyRings;
|
||||
DecryptionBuilder.this.decryptionKeyDecryptor = decryptor;
|
||||
return new VerifyWithImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerifyWith doNotDecrypt() {
|
||||
DecryptionBuilder.this.decryptionKeys = null;
|
||||
DecryptionBuilder.this.decryptionKeyDecryptor = null;
|
||||
return new VerifyWithImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyWithImpl implements VerifyWith {
|
||||
|
||||
@Override
|
||||
public MissingPublicKeyFeedback verifyWith(Set<Long> trustedKeyIds,
|
||||
PGPPublicKeyRingCollection publicKeyRingCollection) {
|
||||
Set<PGPPublicKeyRing> publicKeyRings = new HashSet<>();
|
||||
for (Iterator<PGPPublicKeyRing> i = publicKeyRingCollection.getKeyRings(); i.hasNext(); ) {
|
||||
publicKeyRings.add(i.next());
|
||||
}
|
||||
return verifyWith(trustedKeyIds, publicKeyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MissingPublicKeyFeedback verifyWith(Set<Long> trustedKeyIds, Set<PGPPublicKeyRing> publicKeyRings) {
|
||||
DecryptionBuilder.this.verificationKeys = publicKeyRings;
|
||||
DecryptionBuilder.this.trustedKeyIds = trustedKeyIds;
|
||||
return new MissingPublicKeyFeedbackImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Build doNotVerify() {
|
||||
DecryptionBuilder.this.verificationKeys = null;
|
||||
DecryptionBuilder.this.trustedKeyIds = null;
|
||||
return new BuildImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class MissingPublicKeyFeedbackImpl implements MissingPublicKeyFeedback {
|
||||
|
||||
@Override
|
||||
public Build handleMissingPublicKeysWith(MissingPublicKeyCallback callback) {
|
||||
DecryptionBuilder.this.missingPublicKeyCallback = callback;
|
||||
return new BuildImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Build ignoreMissingPublicKeys() {
|
||||
return new BuildImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class BuildImpl implements Build {
|
||||
|
||||
@Override
|
||||
public PainlessResult.ResultAndInputStream build() throws IOException, PGPException {
|
||||
return InputStreamFactory.create(inputStream,
|
||||
decryptionKeys, decryptionKeyDecryptor, verificationKeys, trustedKeyIds, missingPublicKeyCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.PainlessResult;
|
||||
import de.vanitasvitae.crypto.pgpainless.encryption_signing.SecretKeyRingDecryptor;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
|
||||
public interface DecryptionBuilderInterface {
|
||||
|
||||
DecryptWith onInputStream(InputStream inputStream);
|
||||
|
||||
interface DecryptWith {
|
||||
|
||||
VerifyWith decryptWith(PGPSecretKeyRingCollection secretKeyRings, SecretKeyRingDecryptor decryptor);
|
||||
|
||||
VerifyWith doNotDecrypt();
|
||||
|
||||
}
|
||||
|
||||
interface VerifyWith {
|
||||
|
||||
MissingPublicKeyFeedback verifyWith(Set<Long> trustedFingerprints, PGPPublicKeyRingCollection publicKeyRings);
|
||||
|
||||
MissingPublicKeyFeedback verifyWith(Set<Long> trustedFingerprints, Set<PGPPublicKeyRing> publicKeyRings);
|
||||
|
||||
Build doNotVerify();
|
||||
|
||||
}
|
||||
|
||||
interface MissingPublicKeyFeedback {
|
||||
|
||||
Build handleMissingPublicKeysWith(MissingPublicKeyCallback callback);
|
||||
|
||||
Build ignoreMissingPublicKeys();
|
||||
}
|
||||
|
||||
interface Build {
|
||||
|
||||
PainlessResult.ResultAndInputStream build() throws IOException, PGPException;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.PainlessResult;
|
||||
import de.vanitasvitae.crypto.pgpainless.PainlessStream;
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import de.vanitasvitae.crypto.pgpainless.encryption_signing.SecretKeyRingDecryptor;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||
|
||||
public class InputStreamFactory {
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
private final PGPSecretKeyRingCollection decryptionKeys;
|
||||
private final SecretKeyRingDecryptor decryptionKeyDecryptor;
|
||||
private final Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
|
||||
private final Set<Long> trustedKeyIds = new HashSet<>();
|
||||
private final MissingPublicKeyCallback missingPublicKeyCallback;
|
||||
|
||||
private final PainlessResult.Builder resultBuilder = PainlessResult.getBuilder();
|
||||
private final PGPContentVerifierBuilderProvider verifierBuilderProvider = new BcPGPContentVerifierBuilderProvider();
|
||||
private final Map<Long, PGPOnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
||||
|
||||
private InputStreamFactory(PGPSecretKeyRingCollection decryptionKeys,
|
||||
SecretKeyRingDecryptor decryptor,
|
||||
Set<PGPPublicKeyRing> verificationKeys,
|
||||
Set<Long> trustedKeyIds,
|
||||
MissingPublicKeyCallback missingPublicKeyCallback)
|
||||
throws IOException {
|
||||
this.decryptionKeys = decryptionKeys;
|
||||
this.decryptionKeyDecryptor = decryptor;
|
||||
this.verificationKeys.addAll(verificationKeys != null ? verificationKeys : Collections.emptyList());
|
||||
this.trustedKeyIds.addAll(trustedKeyIds != null ? trustedKeyIds : Collections.emptyList());
|
||||
this.missingPublicKeyCallback = missingPublicKeyCallback;
|
||||
}
|
||||
|
||||
public static PainlessResult.ResultAndInputStream create(InputStream inputStream,
|
||||
PGPSecretKeyRingCollection decryptionKeys,
|
||||
SecretKeyRingDecryptor decryptor,
|
||||
Set<PGPPublicKeyRing> verificationKeys,
|
||||
Set<Long> trustedKeyIds,
|
||||
MissingPublicKeyCallback missingPublicKeyCallback)
|
||||
throws IOException, PGPException {
|
||||
|
||||
InputStreamFactory factory = new InputStreamFactory(decryptionKeys,
|
||||
decryptor,
|
||||
verificationKeys,
|
||||
trustedKeyIds,
|
||||
missingPublicKeyCallback);
|
||||
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(
|
||||
PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator());
|
||||
|
||||
return new PainlessResult.ResultAndInputStream(
|
||||
factory.resultBuilder,
|
||||
new PainlessStream.In(factory.wrap(objectFactory)));
|
||||
}
|
||||
|
||||
private InputStream wrap(PGPObjectFactory objectFactory) throws IOException, PGPException {
|
||||
KeyFingerPrintCalculator fingerCalc = new BcKeyFingerprintCalculator();
|
||||
|
||||
Object pgpObj = null;
|
||||
while ((pgpObj = objectFactory.nextObject()) != null) {
|
||||
|
||||
if (pgpObj instanceof PGPEncryptedDataList) {
|
||||
PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpObj;
|
||||
InputStream nextStream = decrypt(encDataList);
|
||||
objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
|
||||
return wrap(objectFactory);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPCompressedData) {
|
||||
PGPCompressedData compressedData = (PGPCompressedData) pgpObj;
|
||||
InputStream nextStream = compressedData.getDataStream();
|
||||
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
|
||||
objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
|
||||
return wrap(objectFactory);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPOnePassSignatureList) {
|
||||
PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) pgpObj;
|
||||
verify(onePassSignatures);
|
||||
}
|
||||
|
||||
if (pgpObj instanceof PGPLiteralData) {
|
||||
PGPLiteralData literalData = (PGPLiteralData) pgpObj;
|
||||
InputStream literalDataInputStream = literalData.getInputStream();
|
||||
|
||||
if (verifiableOnePassSignatures.isEmpty()) {
|
||||
return literalDataInputStream;
|
||||
}
|
||||
|
||||
return new SignatureVerifyingInputStream(literalDataInputStream,
|
||||
objectFactory, verifiableOnePassSignatures, resultBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PGPException("No Literal Data Packet found!");
|
||||
}
|
||||
|
||||
private InputStream decrypt(PGPEncryptedDataList encryptedDataList)
|
||||
throws PGPException {
|
||||
Iterator<?> iterator = encryptedDataList.getEncryptedDataObjects();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Decryption failed - No encrypted data found!");
|
||||
}
|
||||
|
||||
PGPPrivateKey decryptionKey = null;
|
||||
PGPPublicKeyEncryptedData encryptedSessionKey = null;
|
||||
while (iterator.hasNext()) {
|
||||
encryptedSessionKey = (PGPPublicKeyEncryptedData) iterator.next();
|
||||
long keyId = encryptedSessionKey.getKeyID();
|
||||
|
||||
resultBuilder.addRecipientKeyId(keyId);
|
||||
|
||||
if (decryptionKey != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId);
|
||||
if (secretKey != null) {
|
||||
decryptionKey = secretKey.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(keyId));
|
||||
resultBuilder.setDecryptionKeyId(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
if (decryptionKey == null) {
|
||||
throw new PGPException("Decryption failed - No suitable decryption key found!");
|
||||
}
|
||||
|
||||
PublicKeyDataDecryptorFactory keyDecryptor = new BcPublicKeyDataDecryptorFactory(decryptionKey);
|
||||
resultBuilder.setSymmetricKeyAlgorithm(
|
||||
SymmetricKeyAlgorithm.forId(encryptedSessionKey.getSymmetricAlgorithm(keyDecryptor)));
|
||||
resultBuilder.setIntegrityProtected(encryptedSessionKey.isIntegrityProtected());
|
||||
|
||||
InputStream decryptionStream = encryptedSessionKey.getDataStream(keyDecryptor);
|
||||
|
||||
return decryptionStream;
|
||||
}
|
||||
|
||||
private void verify(PGPOnePassSignatureList onePassSignatureList) throws PGPException {
|
||||
Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new PGPException("Verification failed - No OnePassSignatures found!");
|
||||
}
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
PGPOnePassSignature signature = iterator.next();
|
||||
long keyId = signature.getKeyID();
|
||||
resultBuilder.addSignatureKeyId(keyId);
|
||||
|
||||
// Find public key
|
||||
PGPPublicKey verificationKey = null;
|
||||
for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
|
||||
verificationKey = publicKeyRing.getPublicKey(signature.getKeyID());
|
||||
}
|
||||
|
||||
if (verificationKey != null) {
|
||||
signature.init(verifierBuilderProvider, verificationKey);
|
||||
verifiableOnePassSignatures.put(signature.getKeyID(), signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
public interface MissingPublicKeyCallback {
|
||||
|
||||
void onMissingPublicKeyEncountered(Long keyId);
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.PainlessResult;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
|
||||
public class SignatureVerifyingInputStream extends FilterInputStream {
|
||||
|
||||
private final PGPObjectFactory objectFactory;
|
||||
private final Map<Long, PGPOnePassSignature> onePassSignatures;
|
||||
private final PainlessResult.Builder resultBuilder;
|
||||
|
||||
protected SignatureVerifyingInputStream(InputStream inputStream,
|
||||
PGPObjectFactory objectFactory,
|
||||
Map<Long, PGPOnePassSignature> onePassSignatures,
|
||||
PainlessResult.Builder resultBuilder) {
|
||||
super(inputStream);
|
||||
this.objectFactory = objectFactory;
|
||||
this.resultBuilder = resultBuilder;
|
||||
this.onePassSignatures = onePassSignatures;
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte data) {
|
||||
for (PGPOnePassSignature signature : onePassSignatures.values()) {
|
||||
signature.update(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnePassSignatures(byte[] b, int off, int len) {
|
||||
for (PGPOnePassSignature signature : onePassSignatures.values()) {
|
||||
signature.update(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOnePassSignatures() throws IOException {
|
||||
if (onePassSignatures.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PGPSignatureList signatureList = null;
|
||||
Iterator objectIterator = objectFactory.iterator();
|
||||
while (objectIterator.hasNext() && signatureList == null) {
|
||||
Object object = objectIterator.next();
|
||||
if (object instanceof PGPSignatureList) {
|
||||
signatureList = (PGPSignatureList) object;
|
||||
}
|
||||
}
|
||||
|
||||
if (signatureList == null || signatureList.isEmpty()) {
|
||||
throw new IOException("Verification failed - No Signatures found!");
|
||||
}
|
||||
|
||||
for (PGPSignature signature : signatureList) {
|
||||
PGPOnePassSignature onePassSignature = onePassSignatures.get(signature.getKeyID());
|
||||
if (onePassSignature == null) {
|
||||
continue;
|
||||
}
|
||||
if (!onePassSignature.verify(signature)) {
|
||||
throw new SignatureException("Bad Signature of key " + signature.getKeyID());
|
||||
} else {
|
||||
resultBuilder.addVerifiedSignatureKeyId(signature.getKeyID());
|
||||
}
|
||||
}
|
||||
} catch (PGPException | SignatureException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
final int data = super.read();
|
||||
final boolean endOfStream = data == -1;
|
||||
if (endOfStream) {
|
||||
validateOnePassSignatures();
|
||||
} else {
|
||||
updateOnePassSignatures((byte) data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
|
||||
final boolean endOfStream = read == -1;
|
||||
if (endOfStream) {
|
||||
validateOnePassSignatures();
|
||||
} else {
|
||||
updateOnePassSignatures(b, off, read);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) {
|
||||
throw new UnsupportedOperationException("skip() is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException("mark() not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() {
|
||||
throw new UnsupportedOperationException("reset() is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
|
||||
|
||||
public class VerificationFeedbackCallback {
|
||||
|
||||
|
||||
}
|
Loading…
Reference in a new issue