1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-10 22:31:09 +01:00

WIP: Implement custom PGPDecryptionStream

This commit is contained in:
Paul Schaub 2022-09-12 15:28:54 +02:00
parent bc73d26118
commit bf8949d7f4
6 changed files with 654 additions and 0 deletions

View file

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import org.bouncycastle.bcpg.PacketTags;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
public enum OpenPgpPacket {
PKESK(PacketTags.PUBLIC_KEY_ENC_SESSION),
SIG(PacketTags.SIGNATURE),
SKESK(PacketTags.SYMMETRIC_KEY_ENC_SESSION),
OPS(PacketTags.ONE_PASS_SIGNATURE),
SK(PacketTags.SECRET_KEY),
PK(PacketTags.PUBLIC_KEY),
SSK(PacketTags.SECRET_SUBKEY),
COMP(PacketTags.COMPRESSED_DATA),
SED(PacketTags.SYMMETRIC_KEY_ENC),
MARKER(PacketTags.MARKER),
LIT(PacketTags.LITERAL_DATA),
TRUST(PacketTags.TRUST),
UID(PacketTags.USER_ID),
PSK(PacketTags.PUBLIC_SUBKEY),
UATTR(PacketTags.USER_ATTRIBUTE),
SEIPD(PacketTags.SYM_ENC_INTEGRITY_PRO),
MOD(PacketTags.MOD_DETECTION_CODE),
EXP_1(PacketTags.EXPERIMENTAL_1),
EXP_2(PacketTags.EXPERIMENTAL_2),
EXP_3(PacketTags.EXPERIMENTAL_3),
EXP_4(PacketTags.EXPERIMENTAL_4),
;
static final Map<Integer, OpenPgpPacket> MAP = new HashMap<>();
static {
for (OpenPgpPacket p : OpenPgpPacket.values()) {
MAP.put(p.getTag(), p);
}
}
final int tag;
@Nullable
public static OpenPgpPacket fromTag(int tag) {
return MAP.get(tag);
}
@Nonnull
public static OpenPgpPacket requireFromTag(int tag) {
OpenPgpPacket p = fromTag(tag);
if (p == null) {
throw new NoSuchElementException("No OpenPGP packet known for tag " + tag);
}
return p;
}
OpenPgpPacket(int tag) {
this.tag = tag;
}
int getTag() {
return tag;
}
}

View file

@ -0,0 +1,238 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.ModDetectionCodePacket;
import org.bouncycastle.bcpg.OnePassSignaturePacket;
import org.bouncycastle.bcpg.Packet;
import org.bouncycastle.bcpg.PacketTags;
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
import org.bouncycastle.bcpg.SignaturePacket;
import org.bouncycastle.bcpg.SymmetricEncDataPacket;
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
import org.bouncycastle.bcpg.TrustPacket;
import org.bouncycastle.bcpg.UserAttributePacket;
import org.bouncycastle.bcpg.UserIDPacket;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.pgpainless.algorithm.OpenPgpPacket;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import org.pgpainless.implementation.ImplementationFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;
import java.util.Stack;
public class PGPDecryptionStream extends InputStream {
PushdownAutomaton automaton = new PushdownAutomaton();
// nested streams, outermost at the bottom of the stack
Stack<Layer> packetLayers = new Stack<>();
public PGPDecryptionStream(InputStream inputStream) throws IOException, PGPException {
try {
packetLayers.push(Layer.initial(inputStream));
walkLayer();
} catch (MalformedOpenPgpMessageException e) {
throw e.toRuntimeException();
}
}
private void walkLayer() throws PGPException, IOException {
if (packetLayers.isEmpty()) {
return;
}
Layer layer = packetLayers.peek();
BCPGInputStream inputStream = (BCPGInputStream) layer.inputStream;
loop: while (true) {
if (inputStream.nextPacketTag() == -1) {
popLayer();
break loop;
}
OpenPgpPacket tag = nextTagOrThrow(inputStream);
switch (tag) {
case PKESK:
PublicKeyEncSessionPacket pkeskPacket = (PublicKeyEncSessionPacket) inputStream.readPacket();
PGPEncryptedDataList encList = null;
break;
case SIG:
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
while (inputStream.nextPacketTag() == PacketTags.SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
Packet packet = inputStream.readPacket();
if (packet instanceof SignaturePacket) {
SignaturePacket sig = (SignaturePacket) packet;
sig.encode(bcpgOut);
}
}
PGPSignatureList signatures = (PGPSignatureList) ImplementationFactory.getInstance()
.getPGPObjectFactory(buf.toByteArray()).nextObject();
break;
case SKESK:
SymmetricKeyEncSessionPacket skeskPacket = (SymmetricKeyEncSessionPacket) inputStream.readPacket();
break;
case OPS:
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
buf = new ByteArrayOutputStream();
bcpgOut = new BCPGOutputStream(buf);
while (inputStream.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
Packet packet = inputStream.readPacket();
if (packet instanceof OnePassSignaturePacket) {
OnePassSignaturePacket sig = (OnePassSignaturePacket) packet;
sig.encode(bcpgOut);
}
}
PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) ImplementationFactory.getInstance()
.getPGPObjectFactory(buf.toByteArray()).nextObject();
break;
case SK:
break;
case PK:
break;
case SSK:
break;
case COMP:
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
PGPCompressedData compressedData = new PGPCompressedData(inputStream);
inputStream = new BCPGInputStream(compressedData.getDataStream());
packetLayers.push(Layer.CompressedData(inputStream));
break;
case SED:
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
SymmetricEncDataPacket symmetricEncDataPacket = (SymmetricEncDataPacket) inputStream.readPacket();
break;
case MARKER:
inputStream.readPacket(); // discard
break;
case LIT:
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
PGPLiteralData literalData = new PGPLiteralData(inputStream);
packetLayers.push(Layer.LiteralMessage(literalData.getDataStream()));
break loop;
case TRUST:
TrustPacket trustPacket = (TrustPacket) inputStream.readPacket();
break;
case UID:
UserIDPacket userIDPacket = (UserIDPacket) inputStream.readPacket();
break;
case PSK:
break;
case UATTR:
UserAttributePacket userAttributePacket = (UserAttributePacket) inputStream.readPacket();
break;
case SEIPD:
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
SymmetricEncIntegrityPacket symmetricEncIntegrityPacket = (SymmetricEncIntegrityPacket) inputStream.readPacket();
break;
case MOD:
ModDetectionCodePacket modDetectionCodePacket = (ModDetectionCodePacket) inputStream.readPacket();
break;
case EXP_1:
break;
case EXP_2:
break;
case EXP_3:
break;
case EXP_4:
break;
}
}
}
private OpenPgpPacket nextTagOrThrow(BCPGInputStream inputStream)
throws IOException, InvalidOpenPgpPacketException {
try {
return OpenPgpPacket.requireFromTag(inputStream.nextPacketTag());
} catch (NoSuchElementException e) {
throw new InvalidOpenPgpPacketException(e.getMessage());
}
}
private void popLayer() throws MalformedOpenPgpMessageException {
if (packetLayers.pop().isNested)
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
}
@Override
public int read() throws IOException {
if (packetLayers.isEmpty()) {
try {
automaton.assertValid();
} catch (MalformedOpenPgpMessageException e) {
throw e.toRuntimeException();
}
return -1;
}
int r = -1;
try {
r = packetLayers.peek().inputStream.read();
} catch (IOException e) {
}
if (r == -1) {
try {
popLayer();
walkLayer();
} catch (MalformedOpenPgpMessageException e) {
throw e.toRuntimeException();
}
catch (PGPException e) {
throw new RuntimeException(e);
}
return read();
}
return r;
}
public static class InvalidOpenPgpPacketException extends PGPException {
public InvalidOpenPgpPacketException(String message) {
super(message);
}
}
private static class Layer {
InputStream inputStream;
boolean isNested;
private Layer(InputStream inputStream, boolean isNested) {
this.inputStream = inputStream;
this.isNested = isNested;
}
static Layer initial(InputStream inputStream) {
BCPGInputStream bcpgIn;
if (inputStream instanceof BCPGInputStream) {
bcpgIn = (BCPGInputStream) inputStream;
} else {
bcpgIn = new BCPGInputStream(inputStream);
}
return new Layer(bcpgIn, true);
}
static Layer LiteralMessage(InputStream inputStream) {
return new Layer(inputStream, false);
}
static Layer CompressedData(InputStream inputStream) {
return new Layer(inputStream, true);
}
}
}

View file

@ -342,6 +342,12 @@ public class PushdownAutomaton {
return getState() == State.Valid && stack.isEmpty();
}
public void assertValid() throws MalformedOpenPgpMessageException {
if (!isValid()) {
throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString());
}
}
/**
* Pop an item from the stack.
*

View file

@ -28,4 +28,15 @@ public class MalformedOpenPgpMessageException extends PGPException {
PushdownAutomaton.StackAlphabet stackItem) {
this("Invalid input: There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack.");
}
public RTE toRuntimeException() {
return new RTE(this);
}
public static class RTE extends RuntimeException {
public RTE(MalformedOpenPgpMessageException e) {
super(e);
}
}
}