1
0
Fork 0
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:
Paul Schaub 2018-06-06 18:46:41 +02:00
parent afce16e51e
commit 5d8effb0c5
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
10 changed files with 883 additions and 3 deletions

View 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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}

View file

@ -0,0 +1,7 @@
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
public interface MissingPublicKeyCallback {
void onMissingPublicKeyEncountered(Long keyId);
}

View file

@ -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;
}
}

View file

@ -0,0 +1,6 @@
package de.vanitasvitae.crypto.pgpainless.decryption_verification;
public class VerificationFeedbackCallback {
}