1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-30 23:36:44 +02:00

Wip: Work on OPS verification

This commit is contained in:
Paul Schaub 2022-09-28 16:55:08 +02:00
parent e56233d09a
commit 2285ba5ee9

View file

@ -56,6 +56,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -64,15 +65,19 @@ public class OpenPgpMessageInputStream extends InputStream {
private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class); private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class);
protected final PDA automaton = new PDA(); // Options to consume the data
protected final ConsumerOptions options; protected final ConsumerOptions options;
protected final OpenPgpMetadata.Builder resultBuilder; protected final OpenPgpMetadata.Builder resultBuilder;
protected final BCPGInputStream bcpgIn; // Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message
protected InputStream in; protected final PDA automaton = new PDA();
// InputStream of OpenPGP packets of the current layer
protected final BCPGInputStream packetInputStream;
// InputStream of a nested data packet
protected InputStream nestedInputStream;
private boolean closed = false; private boolean closed = false;
private Signatures signatures; private final Signatures signatures;
private MessageMetadata.Layer metadata; private MessageMetadata.Layer metadata;
public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options) public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options)
@ -82,31 +87,45 @@ public class OpenPgpMessageInputStream extends InputStream {
OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options, MessageMetadata.Layer metadata) OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options, MessageMetadata.Layer metadata)
throws PGPException, IOException { throws PGPException, IOException {
// TODO: Use BCPGInputStream.wrap(inputStream);
if (inputStream instanceof BCPGInputStream) {
this.bcpgIn = (BCPGInputStream) inputStream;
} else {
this.bcpgIn = new BCPGInputStream(inputStream);
}
this.options = options; this.options = options;
this.metadata = metadata; this.metadata = metadata;
this.resultBuilder = OpenPgpMetadata.getBuilder(); this.resultBuilder = OpenPgpMetadata.getBuilder();
this.signatures = new Signatures(options); this.signatures = new Signatures(options);
this.signatures.addDetachedSignatures(options.getDetachedSignatures());
consumePackets(); // nom nom nom // Add detached signatures only on the outermost OpenPgpMessageInputStream
if (metadata instanceof MessageMetadata.Message) {
this.signatures.addDetachedSignatures(options.getDetachedSignatures());
}
// TODO: Use BCPGInputStream.wrap(inputStream);
BCPGInputStream bcpg = null;
if (inputStream instanceof BCPGInputStream) {
bcpg = (BCPGInputStream) inputStream;
} else {
bcpg = new BCPGInputStream(inputStream);
}
this.packetInputStream = new TeeBCPGInputStream(bcpg, signatures);
// *omnomnom*
consumePackets();
} }
/** /**
* This method consumes OpenPGP packets from the current {@link BCPGInputStream}. * Consume OpenPGP packets from the current {@link BCPGInputStream}.
* Once it reaches a "nested" OpenPGP packet (Literal Data, Compressed Data, Encrypted Data), it sets <pre>in</pre> * Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached,
* to the nested stream and breaks the loop. * set <pre>nestedInputStream</pre> to the nested stream and breaks the loop.
* The nested stream is either a simple {@link InputStream} (in case of Literal Data), or another * The nested stream is either a simple {@link InputStream} (in case of Literal Data), or another
* {@link OpenPgpMessageInputStream} in case of Compressed and Encrypted Data. * {@link OpenPgpMessageInputStream} in case of Compressed and Encrypted Data.
* Once the nested data is processed, this method is called again to consume the remainder
* of packets following the nested data packet.
* *
* @throws IOException * @throws IOException in case of an IO error
* @throws PGPException * @throws PGPException in case of an OpenPGP error
* @throws MissingDecryptionMethodException if there is an encrypted data packet which cannot be decrypted
* due to missing decryption methods (no key, no password, no sessionkey)
* @throws MalformedOpenPgpMessageException if the message is made of an invalid packet sequence which
* does not follow the packet syntax of RFC4880.
*/ */
private void consumePackets() private void consumePackets()
throws IOException, PGPException { throws IOException, PGPException {
@ -127,17 +146,23 @@ public class OpenPgpMessageInputStream extends InputStream {
processCompressedData(); processCompressedData();
break loop; break loop;
// One Pass Signatures // One Pass Signature
case OPS: case OPS:
automaton.next(InputAlphabet.OnePassSignatures); automaton.next(InputAlphabet.OnePassSignatures);
signatures.addOnePassSignatures(readOnePassSignatures()); signatures.addOnePassSignature(readOnePassSignature());
// signatures.addOnePassSignatures(readOnePassSignatures());
break; break;
// Signatures - either prepended to the message, or corresponding to the One Pass Signatures // Signature - either prepended to the message, or corresponding to a One Pass Signature
case SIG: case SIG:
boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops; boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops;
automaton.next(InputAlphabet.Signatures); automaton.next(InputAlphabet.Signatures);
processSignature(isSigForOPS); PGPSignature signature = readSignature();
processSignature(signature, isSigForOPS);
/*
PGPSignatureList signatureList = readSignatures();
processSignatures(signatureList, isSigForOPS);
*/
break; break;
// Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC) // Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC)
@ -154,7 +179,7 @@ public class OpenPgpMessageInputStream extends InputStream {
// Marker Packets need to be skipped and ignored // Marker Packets need to be skipped and ignored
case MARKER: case MARKER:
bcpgIn.readPacket(); // skip packetInputStream.readPacket(); // skip
break; break;
// Key Packets are illegal in this context // Key Packets are illegal in this context
@ -182,8 +207,15 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
} }
private void processSignature(boolean isSigForOPS) throws IOException { private void processSignature(PGPSignature signature, boolean isSigForOPS) {
PGPSignatureList signatureList = readSignatures(); if (isSigForOPS) {
signatures.addOnePassCorrespondingSignature(signature);
} else {
signatures.addPrependedSignature(signature);
}
}
private void processSignatures(PGPSignatureList signatureList, boolean isSigForOPS) throws IOException {
if (isSigForOPS) { if (isSigForOPS) {
signatures.addOnePassCorrespondingSignatures(signatureList); signatures.addOnePassCorrespondingSignatures(signatureList);
} else { } else {
@ -192,21 +224,21 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
private void processCompressedData() throws IOException, PGPException { private void processCompressedData() throws IOException, PGPException {
PGPCompressedData compressedData = new PGPCompressedData(bcpgIn); PGPCompressedData compressedData = new PGPCompressedData(packetInputStream);
MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData( MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData(
CompressionAlgorithm.fromId(compressedData.getAlgorithm())); CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer); nestedInputStream = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer);
} }
private void processLiteralData() throws IOException { private void processLiteralData() throws IOException {
PGPLiteralData literalData = new PGPLiteralData(bcpgIn); PGPLiteralData literalData = new PGPLiteralData(packetInputStream);
this.metadata.setChild(new MessageMetadata.LiteralData(literalData.getFileName(), literalData.getModificationTime(), this.metadata.setChild(new MessageMetadata.LiteralData(literalData.getFileName(), literalData.getModificationTime(),
StreamEncoding.requireFromCode(literalData.getFormat()))); StreamEncoding.requireFromCode(literalData.getFormat())));
in = literalData.getDataStream(); nestedInputStream = literalData.getDataStream();
} }
private boolean processEncryptedData() throws IOException, PGPException { private boolean processEncryptedData() throws IOException, PGPException {
PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn); PGPEncryptedDataList encDataList = new PGPEncryptedDataList(packetInputStream);
// TODO: Replace with !encDataList.isIntegrityProtected() // TODO: Replace with !encDataList.isIntegrityProtected()
if (!encDataList.get(0).isIntegrityProtected()) { if (!encDataList.get(0).isIntegrityProtected()) {
@ -225,11 +257,11 @@ public class OpenPgpMessageInputStream extends InputStream {
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(options.getSessionKey().getAlgorithm()); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(options.getSessionKey().getAlgorithm());
if (esk instanceof PGPPBEEncryptedData) { if (esk instanceof PGPPBEEncryptedData) {
PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk; PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk;
in = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData); nestedInputStream = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData);
return true; return true;
} else if (esk instanceof PGPPublicKeyEncryptedData) { } else if (esk instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk; PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk;
in = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData); nestedInputStream = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData);
return true; return true;
} else { } else {
throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName()); throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName());
@ -248,7 +280,7 @@ public class OpenPgpMessageInputStream extends InputStream {
InputStream decrypted = skesk.getDataStream(decryptorFactory); InputStream decrypted = skesk.getDataStream(decryptorFactory);
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(skesk.getSymmetricAlgorithm(decryptorFactory))); SymmetricKeyAlgorithm.requireFromId(skesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// password mismatch? Try next password // password mismatch? Try next password
@ -274,7 +306,7 @@ public class OpenPgpMessageInputStream extends InputStream {
InputStream decrypted = pkesk.getDataStream(decryptorFactory); InputStream decrypted = pkesk.getDataStream(decryptorFactory);
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory))); SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// hm :/ // hm :/
@ -293,7 +325,7 @@ public class OpenPgpMessageInputStream extends InputStream {
InputStream decrypted = pkesk.getDataStream(decryptorFactory); InputStream decrypted = pkesk.getDataStream(decryptorFactory);
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory))); SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// hm :/ // hm :/
@ -307,7 +339,7 @@ public class OpenPgpMessageInputStream extends InputStream {
private int nextTag() throws IOException { private int nextTag() throws IOException {
try { try {
return bcpgIn.nextPacketTag(); return packetInputStream.nextPacketTag();
} catch (IOException e) { } catch (IOException e) {
if ("Stream closed".equals(e.getMessage())) { if ("Stream closed".equals(e.getMessage())) {
// ZipInflater Streams sometimes close under our feet -.- // ZipInflater Streams sometimes close under our feet -.-
@ -345,13 +377,23 @@ public class OpenPgpMessageInputStream extends InputStream {
return null; return null;
} }
private PGPOnePassSignature readOnePassSignature()
throws PGPException, IOException {
return new PGPOnePassSignature(packetInputStream);
}
private PGPSignature readSignature()
throws PGPException, IOException {
return new PGPSignature(packetInputStream);
}
private PGPOnePassSignatureListWrapper readOnePassSignatures() throws IOException { private PGPOnePassSignatureListWrapper readOnePassSignatures() throws IOException {
List<Boolean> encapsulating = new ArrayList<>(); List<Boolean> encapsulating = new ArrayList<>();
ByteArrayOutputStream buf = new ByteArrayOutputStream(); ByteArrayOutputStream buf = new ByteArrayOutputStream();
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
int tag; int tag;
while ((tag = nextTag()) == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) { while ((tag = nextTag()) == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) {
Packet packet = bcpgIn.readPacket(); Packet packet = packetInputStream.readPacket();
if (tag == PacketTags.ONE_PASS_SIGNATURE) { if (tag == PacketTags.ONE_PASS_SIGNATURE) {
OnePassSignaturePacket sigPacket = (OnePassSignaturePacket) packet; OnePassSignaturePacket sigPacket = (OnePassSignaturePacket) packet;
byte[] bytes = sigPacket.getEncoded(); byte[] bytes = sigPacket.getEncoded();
@ -371,7 +413,7 @@ public class OpenPgpMessageInputStream extends InputStream {
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
int tag = nextTag(); int tag = nextTag();
while (tag == PacketTags.SIGNATURE || tag == PacketTags.MARKER) { while (tag == PacketTags.SIGNATURE || tag == PacketTags.MARKER) {
Packet packet = bcpgIn.readPacket(); Packet packet = packetInputStream.readPacket();
if (tag == PacketTags.SIGNATURE) { if (tag == PacketTags.SIGNATURE) {
SignaturePacket sigPacket = (SignaturePacket) packet; SignaturePacket sigPacket = (SignaturePacket) packet;
sigPacket.encode(bcpgOut); sigPacket.encode(bcpgOut);
@ -387,14 +429,14 @@ public class OpenPgpMessageInputStream extends InputStream {
@Override @Override
public int read() throws IOException { public int read() throws IOException {
if (in == null) { if (nestedInputStream == null) {
automaton.assertValid(); automaton.assertValid();
return -1; return -1;
} }
int r; int r;
try { try {
r = in.read(); r = nestedInputStream.read();
} catch (IOException e) { } catch (IOException e) {
r = -1; r = -1;
} }
@ -403,9 +445,9 @@ public class OpenPgpMessageInputStream extends InputStream {
byte b = (byte) r; byte b = (byte) r;
signatures.update(b); signatures.update(b);
} else { } else {
in.close(); nestedInputStream.close();
collectMetadata(); collectMetadata();
in = null; nestedInputStream = null;
try { try {
consumePackets(); consumePackets();
@ -421,16 +463,16 @@ public class OpenPgpMessageInputStream extends InputStream {
public int read(byte[] b, int off, int len) public int read(byte[] b, int off, int len)
throws IOException { throws IOException {
if (in == null) { if (nestedInputStream == null) {
automaton.assertValid(); automaton.assertValid();
return -1; return -1;
} }
int r = in.read(b, off, len); int r = nestedInputStream.read(b, off, len);
if (r == -1) { if (r == -1) {
in.close(); nestedInputStream.close();
collectMetadata(); collectMetadata();
in = null; nestedInputStream = null;
try { try {
consumePackets(); consumePackets();
@ -449,10 +491,10 @@ public class OpenPgpMessageInputStream extends InputStream {
return; return;
} }
if (in != null) { if (nestedInputStream != null) {
in.close(); nestedInputStream.close();
collectMetadata(); collectMetadata();
in = null; nestedInputStream = null;
} }
try { try {
@ -467,8 +509,8 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
private void collectMetadata() { private void collectMetadata() {
if (in instanceof OpenPgpMessageInputStream) { if (nestedInputStream instanceof OpenPgpMessageInputStream) {
OpenPgpMessageInputStream child = (OpenPgpMessageInputStream) in; OpenPgpMessageInputStream child = (OpenPgpMessageInputStream) nestedInputStream;
MessageMetadata.Layer childLayer = child.metadata; MessageMetadata.Layer childLayer = child.metadata;
this.metadata.setChild((MessageMetadata.Nested) childLayer); this.metadata.setChild((MessageMetadata.Nested) childLayer);
} }
@ -533,13 +575,14 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
} }
private static final class Signatures { private static final class Signatures extends OutputStream {
final ConsumerOptions options; final ConsumerOptions options;
List<PGPSignature> detachedSignatures = new ArrayList<>(); List<PGPSignature> detachedSignatures = new ArrayList<>();
List<PGPSignature> prependedSignatures = new ArrayList<>(); List<PGPSignature> prependedSignatures = new ArrayList<>();
List<PGPOnePassSignature> onePassSignatures = new ArrayList<>(); List<List<PGPOnePassSignature>> onePassSignatures = new ArrayList<>();
List<PGPSignature> correspondingSignatures = new ArrayList<>(); List<PGPSignature> correspondingSignatures = new ArrayList<>();
boolean lastOpsIsContaining = true;
private Signatures(ConsumerOptions options) { private Signatures(ConsumerOptions options) {
this.options = options; this.options = options;
@ -547,28 +590,44 @@ public class OpenPgpMessageInputStream extends InputStream {
void addDetachedSignatures(Collection<PGPSignature> signatures) { void addDetachedSignatures(Collection<PGPSignature> signatures) {
for (PGPSignature signature : signatures) { for (PGPSignature signature : signatures) {
long keyId = SignatureUtils.determineIssuerKeyId(signature); addDetachedSignature(signature);
PGPPublicKeyRing certificate = findCertificate(keyId);
initialize(signature, certificate, keyId);
} }
this.detachedSignatures.addAll(signatures); }
void addDetachedSignature(PGPSignature signature) {
long keyId = SignatureUtils.determineIssuerKeyId(signature);
PGPPublicKeyRing certificate = findCertificate(keyId);
initialize(signature, certificate, keyId);
this.detachedSignatures.add(signature);
} }
void addPrependedSignatures(PGPSignatureList signatures) { void addPrependedSignatures(PGPSignatureList signatures) {
for (PGPSignature signature : signatures) { for (PGPSignature signature : signatures) {
long keyId = SignatureUtils.determineIssuerKeyId(signature); addPrependedSignature(signature);
PGPPublicKeyRing certificate = findCertificate(keyId);
initialize(signature, certificate, keyId);
this.prependedSignatures.add(signature);
} }
} }
void addOnePassSignatures(PGPOnePassSignatureListWrapper signatures) { void addPrependedSignature(PGPSignature signature) {
for (PGPOnePassSignature ops : signatures.list) { long keyId = SignatureUtils.determineIssuerKeyId(signature);
PGPPublicKeyRing certificate = findCertificate(ops.getKeyID()); PGPPublicKeyRing certificate = findCertificate(keyId);
initialize(ops, certificate); initialize(signature, certificate, keyId);
this.onePassSignatures.add(ops); this.prependedSignatures.add(signature);
}
void addOnePassSignature(PGPOnePassSignature signature) {
List<PGPOnePassSignature> list;
if (lastOpsIsContaining) {
list = new ArrayList<>();
onePassSignatures.add(list);
} else {
list = onePassSignatures.get(onePassSignatures.size() - 1);
} }
PGPPublicKeyRing certificate = findCertificate(signature.getKeyID());
initialize(signature, certificate);
list.add(signature);
// lastOpsIsContaining = signature.isContaining();
} }
void addOnePassCorrespondingSignatures(PGPSignatureList signatures) { void addOnePassCorrespondingSignatures(PGPSignatureList signatures) {
@ -618,8 +677,10 @@ public class OpenPgpMessageInputStream extends InputStream {
for (PGPSignature prepended : prependedSignatures) { for (PGPSignature prepended : prependedSignatures) {
prepended.update(b); prepended.update(b);
} }
for (PGPOnePassSignature ops : onePassSignatures) { for (List<PGPOnePassSignature> opss : onePassSignatures) {
ops.update(b); for (PGPOnePassSignature ops : opss) {
ops.update(b);
}
} }
for (PGPSignature detached : detachedSignatures) { for (PGPSignature detached : detachedSignatures) {
detached.update(b); detached.update(b);
@ -660,5 +721,10 @@ public class OpenPgpMessageInputStream extends InputStream {
LOGGER.debug("One-Pass-Signature by " + Long.toHexString(ops.getKeyID()) + " is " + (verified ? "verified" : "unverified")); LOGGER.debug("One-Pass-Signature by " + Long.toHexString(ops.getKeyID()) + " is " + (verified ? "verified" : "unverified"));
} }
} }
@Override
public void write(int b) throws IOException {
update((byte) b);
}
} }
} }