mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-09 10:19:39 +02:00
Port OpenPGPInputStream to Kotlin as OpenPGPAnimalSnifferInputStream
This commit is contained in:
parent
3a0ee1c101
commit
702db4d75c
8 changed files with 340 additions and 367 deletions
|
@ -1,340 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification;
|
||||
|
||||
import static org.bouncycastle.bcpg.PacketTags.AEAD_ENC_DATA;
|
||||
import static org.bouncycastle.bcpg.PacketTags.COMPRESSED_DATA;
|
||||
import static org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_1;
|
||||
import static org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_2;
|
||||
import static org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_3;
|
||||
import static org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_4;
|
||||
import static org.bouncycastle.bcpg.PacketTags.LITERAL_DATA;
|
||||
import static org.bouncycastle.bcpg.PacketTags.MARKER;
|
||||
import static org.bouncycastle.bcpg.PacketTags.MOD_DETECTION_CODE;
|
||||
import static org.bouncycastle.bcpg.PacketTags.ONE_PASS_SIGNATURE;
|
||||
import static org.bouncycastle.bcpg.PacketTags.PADDING;
|
||||
import static org.bouncycastle.bcpg.PacketTags.PUBLIC_KEY;
|
||||
import static org.bouncycastle.bcpg.PacketTags.PUBLIC_KEY_ENC_SESSION;
|
||||
import static org.bouncycastle.bcpg.PacketTags.PUBLIC_SUBKEY;
|
||||
import static org.bouncycastle.bcpg.PacketTags.RESERVED;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SECRET_KEY;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SECRET_SUBKEY;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SIGNATURE;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SYMMETRIC_KEY_ENC;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SYMMETRIC_KEY_ENC_SESSION;
|
||||
import static org.bouncycastle.bcpg.PacketTags.SYM_ENC_INTEGRITY_PRO;
|
||||
import static org.bouncycastle.bcpg.PacketTags.TRUST;
|
||||
import static org.bouncycastle.bcpg.PacketTags.USER_ATTRIBUTE;
|
||||
import static org.bouncycastle.bcpg.PacketTags.USER_ID;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.bouncycastle.bcpg.AEADEncDataPacket;
|
||||
import org.bouncycastle.bcpg.BCPGInputStream;
|
||||
import org.bouncycastle.bcpg.CompressedDataPacket;
|
||||
import org.bouncycastle.bcpg.LiteralDataPacket;
|
||||
import org.bouncycastle.bcpg.MarkerPacket;
|
||||
import org.bouncycastle.bcpg.OnePassSignaturePacket;
|
||||
import org.bouncycastle.bcpg.Packet;
|
||||
import org.bouncycastle.bcpg.PacketFormat;
|
||||
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.bcpg.SecretKeyPacket;
|
||||
import org.bouncycastle.bcpg.SignaturePacket;
|
||||
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
|
||||
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
|
||||
import org.bouncycastle.bcpg.UnsupportedPacketVersionException;
|
||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.pgpainless.algorithm.AEADAlgorithm;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
|
||||
/**
|
||||
* InputStream used to determine the nature of potential OpenPGP data.
|
||||
*/
|
||||
public class OpenPgpInputStream extends BufferedInputStream {
|
||||
|
||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||
private static final byte[] ARMOR_HEADER = "-----BEGIN PGP ".getBytes(Charset.forName("UTF8"));
|
||||
|
||||
// Buffer beginning bytes of the data
|
||||
public static final int MAX_BUFFER_SIZE = 8192 * 2;
|
||||
|
||||
private final byte[] buffer;
|
||||
private final int bufferLen;
|
||||
|
||||
private boolean containsArmorHeader;
|
||||
private boolean containsOpenPgpPackets;
|
||||
private boolean isLikelyOpenPgpMessage;
|
||||
|
||||
public OpenPgpInputStream(InputStream in, boolean check) throws IOException {
|
||||
super(in, MAX_BUFFER_SIZE);
|
||||
|
||||
mark(MAX_BUFFER_SIZE);
|
||||
buffer = new byte[MAX_BUFFER_SIZE];
|
||||
bufferLen = read(buffer);
|
||||
reset();
|
||||
|
||||
if (check) {
|
||||
inspectBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public OpenPgpInputStream(InputStream in) throws IOException {
|
||||
this(in, true);
|
||||
}
|
||||
|
||||
private void inspectBuffer() throws IOException {
|
||||
if (checkForAsciiArmor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkForBinaryOpenPgp();
|
||||
}
|
||||
|
||||
private boolean checkForAsciiArmor() {
|
||||
if (startsWithIgnoringWhitespace(buffer, ARMOR_HEADER, bufferLen)) {
|
||||
containsArmorHeader = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is still brittle.
|
||||
* Basically we try to parse OpenPGP packets from the buffer.
|
||||
* If we run into exceptions, then we know that the data is non-OpenPGP'ish.
|
||||
* <p>
|
||||
* This breaks down though if we read plausible garbage where the data accidentally makes sense,
|
||||
* or valid, yet incomplete packets (remember, we are still only working on a portion of the data).
|
||||
*/
|
||||
private void checkForBinaryOpenPgp() throws IOException {
|
||||
if (bufferLen == -1) {
|
||||
// Empty data
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayInputStream bufferIn = new ByteArrayInputStream(buffer, 0, bufferLen);
|
||||
BCPGInputStream pIn = new BCPGInputStream(bufferIn);
|
||||
try {
|
||||
nonExhaustiveParseAndCheckPlausibility(pIn);
|
||||
} catch (IOException | UnsupportedPacketVersionException | NegativeArraySizeException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void nonExhaustiveParseAndCheckPlausibility(BCPGInputStream packetIn)
|
||||
throws IOException {
|
||||
Packet packet = packetIn.readPacket();
|
||||
switch (packet.getPacketTag()) {
|
||||
case PUBLIC_KEY_ENC_SESSION:
|
||||
PublicKeyEncSessionPacket pkesk = (PublicKeyEncSessionPacket) packet;
|
||||
if (PublicKeyAlgorithm.fromId(pkesk.getAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGNATURE:
|
||||
SignaturePacket sig = (SignaturePacket) packet;
|
||||
if (SignatureType.fromCode(sig.getSignatureType()) == null) {
|
||||
return;
|
||||
}
|
||||
if (PublicKeyAlgorithm.fromId(sig.getKeyAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
if (HashAlgorithm.fromId(sig.getHashAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ONE_PASS_SIGNATURE:
|
||||
OnePassSignaturePacket ops = (OnePassSignaturePacket) packet;
|
||||
if (SignatureType.fromCode(ops.getSignatureType()) == null) {
|
||||
return;
|
||||
}
|
||||
if (PublicKeyAlgorithm.fromId(ops.getKeyAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
if (HashAlgorithm.fromId(ops.getHashAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SYMMETRIC_KEY_ENC_SESSION:
|
||||
SymmetricKeyEncSessionPacket skesk = (SymmetricKeyEncSessionPacket) packet;
|
||||
if (SymmetricKeyAlgorithm.fromId(skesk.getEncAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SECRET_KEY:
|
||||
SecretKeyPacket secKey = (SecretKeyPacket) packet;
|
||||
PublicKeyPacket sPubKey = secKey.getPublicKeyPacket();
|
||||
if (PublicKeyAlgorithm.fromId(sPubKey.getAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
if (sPubKey.getVersion() < 3 && sPubKey.getVersion() > 6) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case PUBLIC_KEY:
|
||||
PublicKeyPacket pubKey = (PublicKeyPacket) packet;
|
||||
if (PublicKeyAlgorithm.fromId(pubKey.getAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
if (pubKey.getVersion() < 3 && pubKey.getVersion() > 6) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case COMPRESSED_DATA:
|
||||
CompressedDataPacket comp = (CompressedDataPacket) packet;
|
||||
if (CompressionAlgorithm.fromId(comp.getAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SYMMETRIC_KEY_ENC:
|
||||
// Not much we can check here
|
||||
break;
|
||||
|
||||
case MARKER:
|
||||
MarkerPacket m = (MarkerPacket) packet;
|
||||
if (!Arrays.areEqual(
|
||||
m.getEncoded(PacketFormat.CURRENT),
|
||||
new byte[] {(byte) 0xca, 0x03, 0x50, 0x47, 0x50})) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case LITERAL_DATA:
|
||||
LiteralDataPacket lit = (LiteralDataPacket) packet;
|
||||
if (lit.getFormat() != 'b' &&
|
||||
lit.getFormat() != 'u' &&
|
||||
lit.getFormat() != 't' &&
|
||||
lit.getFormat() != 'l' &&
|
||||
lit.getFormat() != '1' &&
|
||||
lit.getFormat() != 'm') {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SYM_ENC_INTEGRITY_PRO:
|
||||
SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) packet;
|
||||
if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1) {
|
||||
break; // not much to check here
|
||||
}
|
||||
if (seipd.getVersion() != SymmetricEncIntegrityPacket.VERSION_2) {
|
||||
if (SymmetricKeyAlgorithm.fromId(seipd.getCipherAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
if (AEADAlgorithm.fromId(seipd.getAeadAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AEAD_ENC_DATA:
|
||||
AEADEncDataPacket oed = (AEADEncDataPacket) packet;
|
||||
if (SymmetricKeyAlgorithm.fromId(oed.getAlgorithm()) == null) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case RESERVED: // this Packet Type ID MUST NOT be used
|
||||
case PUBLIC_SUBKEY: // Never found at the start of a stream
|
||||
case SECRET_SUBKEY: // Never found at the start of a stream
|
||||
case TRUST: // Never found at the start of a stream
|
||||
case MOD_DETECTION_CODE: // At the end of SED data - Never found at the start of a stream
|
||||
case USER_ID: // Never found at the start of a stream
|
||||
case USER_ATTRIBUTE: // Never found at the start of a stream
|
||||
case PADDING: // At the end of messages (optionally padded message) or certificates
|
||||
case EXPERIMENTAL_1: // experimental
|
||||
case EXPERIMENTAL_2: // experimental
|
||||
case EXPERIMENTAL_3: // experimental
|
||||
case EXPERIMENTAL_4: // experimental
|
||||
containsOpenPgpPackets = true;
|
||||
isLikelyOpenPgpMessage = false;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
containsOpenPgpPackets = true;
|
||||
if (packet.getPacketTag() != SYMMETRIC_KEY_ENC) {
|
||||
isLikelyOpenPgpMessage = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startsWithIgnoringWhitespace(byte[] bytes, byte[] subsequence, int bufferLen) {
|
||||
if (bufferLen == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bufferLen; i++) {
|
||||
// Working on bytes is not trivial with unicode data, but its good enough here
|
||||
if (Character.isWhitespace(bytes[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((i + subsequence.length) > bytes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int j = 0; j < subsequence.length; j++) {
|
||||
if (bytes[i + j] != subsequence[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isAsciiArmored() {
|
||||
return containsArmorHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the data is possibly binary OpenPGP.
|
||||
* The criterion for this are less strict than for {@link #isLikelyOpenPgpMessage()},
|
||||
* as it also accepts other OpenPGP packets at the beginning of the data stream.
|
||||
* <p>
|
||||
* Use with caution.
|
||||
*
|
||||
* @return true if data appears to be binary OpenPGP data
|
||||
*/
|
||||
public boolean isBinaryOpenPgp() {
|
||||
return containsOpenPgpPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the underlying data is very likely (more than 99,9%) an OpenPGP message.
|
||||
* OpenPGP Message means here that it starts with either an {@link PGPEncryptedData},
|
||||
* {@link PGPCompressedData}, {@link PGPOnePassSignature} or {@link PGPLiteralData} packet.
|
||||
* The plausibility of these data packets is checked as far as possible.
|
||||
*
|
||||
* @return true if likely OpenPGP message
|
||||
*/
|
||||
public boolean isLikelyOpenPgpMessage() {
|
||||
return isLikelyOpenPgpMessage;
|
||||
}
|
||||
|
||||
public boolean isNonOpenPgp() {
|
||||
return !isAsciiArmored() && !isBinaryOpenPgp();
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Classes used to decryption and verification of OpenPGP encrypted / signed data.
|
||||
*/
|
||||
package org.pgpainless.decryption_verification;
|
|
@ -0,0 +1,321 @@
|
|||
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import org.bouncycastle.bcpg.AEADEncDataPacket
|
||||
import org.bouncycastle.bcpg.BCPGInputStream
|
||||
import org.bouncycastle.bcpg.CompressedDataPacket
|
||||
import org.bouncycastle.bcpg.LiteralDataPacket
|
||||
import org.bouncycastle.bcpg.MarkerPacket
|
||||
import org.bouncycastle.bcpg.OnePassSignaturePacket
|
||||
import org.bouncycastle.bcpg.PacketFormat
|
||||
import org.bouncycastle.bcpg.PacketTags.AEAD_ENC_DATA
|
||||
import org.bouncycastle.bcpg.PacketTags.COMPRESSED_DATA
|
||||
import org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_1
|
||||
import org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_2
|
||||
import org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_3
|
||||
import org.bouncycastle.bcpg.PacketTags.EXPERIMENTAL_4
|
||||
import org.bouncycastle.bcpg.PacketTags.LITERAL_DATA
|
||||
import org.bouncycastle.bcpg.PacketTags.MARKER
|
||||
import org.bouncycastle.bcpg.PacketTags.MOD_DETECTION_CODE
|
||||
import org.bouncycastle.bcpg.PacketTags.ONE_PASS_SIGNATURE
|
||||
import org.bouncycastle.bcpg.PacketTags.PADDING
|
||||
import org.bouncycastle.bcpg.PacketTags.PUBLIC_KEY
|
||||
import org.bouncycastle.bcpg.PacketTags.PUBLIC_KEY_ENC_SESSION
|
||||
import org.bouncycastle.bcpg.PacketTags.PUBLIC_SUBKEY
|
||||
import org.bouncycastle.bcpg.PacketTags.RESERVED
|
||||
import org.bouncycastle.bcpg.PacketTags.SECRET_KEY
|
||||
import org.bouncycastle.bcpg.PacketTags.SECRET_SUBKEY
|
||||
import org.bouncycastle.bcpg.PacketTags.SIGNATURE
|
||||
import org.bouncycastle.bcpg.PacketTags.SYMMETRIC_KEY_ENC
|
||||
import org.bouncycastle.bcpg.PacketTags.SYMMETRIC_KEY_ENC_SESSION
|
||||
import org.bouncycastle.bcpg.PacketTags.SYM_ENC_INTEGRITY_PRO
|
||||
import org.bouncycastle.bcpg.PacketTags.TRUST
|
||||
import org.bouncycastle.bcpg.PacketTags.USER_ATTRIBUTE
|
||||
import org.bouncycastle.bcpg.PacketTags.USER_ID
|
||||
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket
|
||||
import org.bouncycastle.bcpg.SecretKeyPacket
|
||||
import org.bouncycastle.bcpg.SignaturePacket
|
||||
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
|
||||
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket
|
||||
import org.bouncycastle.util.Arrays
|
||||
import org.pgpainless.algorithm.AEADAlgorithm
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.algorithm.SignatureType
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
|
||||
/**
|
||||
* InputStream used to determine the nature of potential OpenPGP data.
|
||||
*
|
||||
* @param input underlying input stream
|
||||
* @param check whether to perform the costly checking inside the constructor
|
||||
*/
|
||||
class OpenPGPAnimalSnifferInputStream(input: InputStream, check: Boolean) :
|
||||
BufferedInputStream(input) {
|
||||
|
||||
private val buffer: ByteArray
|
||||
private val bufferLen: Int
|
||||
|
||||
private var containsArmorHeader: Boolean = false
|
||||
private var containsOpenPgpPackets: Boolean = false
|
||||
private var resemblesMessage: Boolean = false
|
||||
|
||||
init {
|
||||
mark(MAX_BUFFER_SIZE)
|
||||
buffer = ByteArray(MAX_BUFFER_SIZE)
|
||||
bufferLen = read(buffer)
|
||||
reset()
|
||||
|
||||
if (check) {
|
||||
inspectBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(input: InputStream) : this(input, true)
|
||||
|
||||
/** Return true, if the underlying data is ASCII armored. */
|
||||
val isAsciiArmored: Boolean
|
||||
get() = containsArmorHeader
|
||||
|
||||
/**
|
||||
* Return true, if the data is possibly binary OpenPGP. The criterion for this are less strict
|
||||
* than for [resemblesMessage], as it also accepts other OpenPGP packets at the beginning of the
|
||||
* data stream.
|
||||
*
|
||||
* <p>
|
||||
* Use with caution.
|
||||
*
|
||||
* @return true if data appears to be binary OpenPGP data
|
||||
*/
|
||||
val isBinaryOpenPgp: Boolean
|
||||
get() = containsOpenPgpPackets
|
||||
|
||||
/**
|
||||
* Returns true, if the underlying data is very likely (more than 99,9%) an OpenPGP message.
|
||||
* OpenPGP Message means here that it starts with either a [PGPEncryptedData],
|
||||
* [PGPCompressedData], [PGPOnePassSignature] or [PGPLiteralData] packet. The plausibility of
|
||||
* these data packets is checked as far as possible.
|
||||
*
|
||||
* @return true if likely OpenPGP message
|
||||
*/
|
||||
val isLikelyOpenPgpMessage: Boolean
|
||||
get() = resemblesMessage
|
||||
|
||||
/** Return true, if the underlying data is non-OpenPGP data. */
|
||||
val isNonOpenPgp: Boolean
|
||||
get() = !isAsciiArmored && !isBinaryOpenPgp
|
||||
|
||||
/** Costly perform a plausibility check of the first encountered OpenPGP packet. */
|
||||
fun inspectBuffer() {
|
||||
if (checkForAsciiArmor()) {
|
||||
return
|
||||
}
|
||||
|
||||
checkForBinaryOpenPgp()
|
||||
}
|
||||
|
||||
private fun checkForAsciiArmor(): Boolean {
|
||||
if (startsWithIgnoringWhitespace(buffer, ARMOR_HEADER, bufferLen)) {
|
||||
containsArmorHeader = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is still brittle. Basically we try to parse OpenPGP packets from the buffer. If
|
||||
* we run into exceptions, then we know that the data is non-OpenPGP'ish.
|
||||
*
|
||||
* <p>
|
||||
* This breaks down though if we read plausible garbage where the data accidentally makes sense,
|
||||
* or valid, yet incomplete packets (remember, we are still only working on a portion of the
|
||||
* data).
|
||||
*/
|
||||
private fun checkForBinaryOpenPgp() {
|
||||
if (bufferLen == -1) {
|
||||
// empty data
|
||||
return
|
||||
}
|
||||
|
||||
val bufferIn = ByteArrayInputStream(buffer, 0, bufferLen)
|
||||
val pIn = BCPGInputStream(bufferIn)
|
||||
try {
|
||||
nonExhaustiveParseAndCheckPlausibility(pIn)
|
||||
} catch (e: Exception) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun nonExhaustiveParseAndCheckPlausibility(packetIn: BCPGInputStream) {
|
||||
val packet = packetIn.readPacket()
|
||||
when (packet.packetTag) {
|
||||
PUBLIC_KEY_ENC_SESSION -> {
|
||||
packet as PublicKeyEncSessionPacket
|
||||
if (PublicKeyAlgorithm.fromId(packet.algorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
SIGNATURE -> {
|
||||
packet as SignaturePacket
|
||||
if (SignatureType.fromCode(packet.signatureType) == null) {
|
||||
return
|
||||
}
|
||||
if (PublicKeyAlgorithm.fromId(packet.keyAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
if (HashAlgorithm.fromId(packet.hashAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
ONE_PASS_SIGNATURE -> {
|
||||
packet as OnePassSignaturePacket
|
||||
if (SignatureType.fromCode(packet.signatureType) == null) {
|
||||
return
|
||||
}
|
||||
if (PublicKeyAlgorithm.fromId(packet.keyAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
if (HashAlgorithm.fromId(packet.hashAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
SYMMETRIC_KEY_ENC_SESSION -> {
|
||||
packet as SymmetricKeyEncSessionPacket
|
||||
if (SymmetricKeyAlgorithm.fromId(packet.encAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
SECRET_KEY -> {
|
||||
packet as SecretKeyPacket
|
||||
val publicKey = packet.publicKeyPacket
|
||||
if (PublicKeyAlgorithm.fromId(publicKey.algorithm) == null) {
|
||||
return
|
||||
}
|
||||
if (publicKey.version !in 3..6) {
|
||||
return
|
||||
}
|
||||
}
|
||||
PUBLIC_KEY -> {
|
||||
packet as PublicKeyPacket
|
||||
if (PublicKeyAlgorithm.fromId(packet.algorithm) == null) {
|
||||
return
|
||||
}
|
||||
if (packet.version !in 3..6) {
|
||||
return
|
||||
}
|
||||
}
|
||||
COMPRESSED_DATA -> {
|
||||
packet as CompressedDataPacket
|
||||
if (CompressionAlgorithm.fromId(packet.algorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
SYMMETRIC_KEY_ENC -> {
|
||||
// Not much we can check here
|
||||
}
|
||||
MARKER -> {
|
||||
packet as MarkerPacket
|
||||
if (!Arrays.areEqual(
|
||||
packet.getEncoded(PacketFormat.CURRENT),
|
||||
byteArrayOf(0xca.toByte(), 0x03, 0x50, 0x47, 0x50),
|
||||
)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
LITERAL_DATA -> {
|
||||
packet as LiteralDataPacket
|
||||
if (packet.format.toChar() !in charArrayOf('b', 'u', 't', 'l', '1', 'm')) {
|
||||
return
|
||||
}
|
||||
}
|
||||
SYM_ENC_INTEGRITY_PRO -> {
|
||||
packet as SymmetricEncIntegrityPacket
|
||||
if (packet.version !in
|
||||
intArrayOf(
|
||||
SymmetricEncIntegrityPacket.VERSION_1,
|
||||
SymmetricEncIntegrityPacket.VERSION_2)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (packet.version == SymmetricEncIntegrityPacket.VERSION_2) {
|
||||
if (SymmetricKeyAlgorithm.fromId(packet.cipherAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
if (AEADAlgorithm.fromId(packet.aeadAlgorithm) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
AEAD_ENC_DATA -> {
|
||||
packet as AEADEncDataPacket
|
||||
if (SymmetricKeyAlgorithm.fromId(packet.algorithm.toInt()) == null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
RESERVED, // this Packet Type ID MUST NOT be used
|
||||
PUBLIC_SUBKEY, // Never found at the start of a stream
|
||||
SECRET_SUBKEY, // Never found at the start of a stream
|
||||
TRUST, // Never found at the start of a stream
|
||||
MOD_DETECTION_CODE, // At the end of SED data - Never found at the start of a stream
|
||||
USER_ID, // Never found at the start of a stream
|
||||
USER_ATTRIBUTE, // Never found at the start of a stream
|
||||
PADDING, // At the end of messages (optionally padded message) or certificates
|
||||
EXPERIMENTAL_1, // experimental
|
||||
EXPERIMENTAL_2, // experimental
|
||||
EXPERIMENTAL_3, // experimental
|
||||
EXPERIMENTAL_4 -> { // experimental
|
||||
containsOpenPgpPackets = true
|
||||
resemblesMessage = false
|
||||
return
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
|
||||
containsOpenPgpPackets = true
|
||||
if (packet.packetTag != SYMMETRIC_KEY_ENC) {
|
||||
resemblesMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun startsWithIgnoringWhitespace(
|
||||
bytes: ByteArray,
|
||||
subSequence: CharSequence,
|
||||
bufferLen: Int
|
||||
): Boolean {
|
||||
if (bufferLen == -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (i in 0 until bufferLen) {
|
||||
// Working on bytes is not trivial with unicode data, but its good enough here
|
||||
if (Character.isWhitespace(bytes[i].toInt())) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ((i + subSequence.length) > bytes.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (j in subSequence.indices) {
|
||||
if (bytes[i + j].toInt().toChar() != subSequence[j]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARMOR_HEADER = "-----BEGIN PGP "
|
||||
const val MAX_BUFFER_SIZE = 8192 * 2
|
||||
}
|
||||
}
|
|
@ -1114,7 +1114,7 @@ class OpenPgpMessageInputStream(
|
|||
metadata: Layer,
|
||||
api: PGPainless
|
||||
): OpenPgpMessageInputStream {
|
||||
val openPgpIn = OpenPgpInputStream(inputStream)
|
||||
val openPgpIn = OpenPGPAnimalSnifferInputStream(inputStream)
|
||||
openPgpIn.reset()
|
||||
|
||||
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.bouncycastle.openpgp.PGPSignature
|
|||
import org.bouncycastle.openpgp.PGPUtil
|
||||
import org.bouncycastle.util.io.Streams
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.decryption_verification.OpenPgpInputStream
|
||||
import org.pgpainless.decryption_verification.OpenPGPAnimalSnifferInputStream
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import org.pgpainless.key.util.KeyRingUtils
|
||||
|
||||
|
@ -422,7 +422,7 @@ class ArmorUtils {
|
|||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun getDecoderStream(inputStream: InputStream): InputStream =
|
||||
OpenPgpInputStream(inputStream).let {
|
||||
OpenPGPAnimalSnifferInputStream(inputStream).let {
|
||||
if (it.isAsciiArmored) {
|
||||
PGPUtil.getDecoderStream(ArmoredInputStreamFactory.get(it))
|
||||
} else {
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.pgpainless.encryption_signing.ProducerOptions;
|
|||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
||||
public class OpenPgpInputStreamTest {
|
||||
public class OpenPGPAnimalSnifferInputStreamTest {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class OpenPgpInputStreamTest {
|
|||
RANDOM.nextBytes(randomBytes);
|
||||
ByteArrayInputStream randomIn = new ByteArrayInputStream(randomBytes);
|
||||
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(randomIn);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(randomIn);
|
||||
assertFalse(openPgpInputStream.isAsciiArmored());
|
||||
assertFalse(openPgpInputStream.isLikelyOpenPgpMessage(),
|
||||
Hex.toHexString(randomBytes, 0, 150));
|
||||
|
@ -56,7 +56,7 @@ public class OpenPgpInputStreamTest {
|
|||
public void largeCompressedDataIsBinaryOpenPgp() throws IOException {
|
||||
// Since we are compressing RANDOM data, the output will likely be roughly the same size
|
||||
// So we very likely will end up with data larger than the MAX_BUFFER_SIZE
|
||||
byte[] randomBytes = new byte[OpenPgpInputStream.MAX_BUFFER_SIZE * 10];
|
||||
byte[] randomBytes = new byte[OpenPGPAnimalSnifferInputStream.MAX_BUFFER_SIZE * 10];
|
||||
RANDOM.nextBytes(randomBytes);
|
||||
|
||||
ByteArrayOutputStream compressedDataPacket = new ByteArrayOutputStream();
|
||||
|
@ -65,7 +65,7 @@ public class OpenPgpInputStreamTest {
|
|||
compressor.write(randomBytes);
|
||||
compressor.close();
|
||||
|
||||
OpenPgpInputStream inputStream = new OpenPgpInputStream(new ByteArrayInputStream(compressedDataPacket.toByteArray()));
|
||||
OpenPGPAnimalSnifferInputStream inputStream = new OpenPGPAnimalSnifferInputStream(new ByteArrayInputStream(compressedDataPacket.toByteArray()));
|
||||
assertFalse(inputStream.isAsciiArmored());
|
||||
assertFalse(inputStream.isNonOpenPgp());
|
||||
assertTrue(inputStream.isBinaryOpenPgp());
|
||||
|
@ -90,7 +90,7 @@ public class OpenPgpInputStreamTest {
|
|||
"-----END PGP MESSAGE-----";
|
||||
|
||||
ByteArrayInputStream asciiIn = new ByteArrayInputStream(asciiArmoredMessage.getBytes(StandardCharsets.UTF_8));
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(asciiIn);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(asciiIn);
|
||||
|
||||
assertTrue(openPgpInputStream.isAsciiArmored());
|
||||
assertFalse(openPgpInputStream.isNonOpenPgp());
|
||||
|
@ -663,9 +663,9 @@ public class OpenPgpInputStreamTest {
|
|||
@Test
|
||||
public void longAsciiArmoredMessageIsAsciiArmored() throws IOException {
|
||||
byte[] asciiArmoredBytes = longAsciiArmoredMessage.getBytes(StandardCharsets.UTF_8);
|
||||
assertTrue(asciiArmoredBytes.length > OpenPgpInputStream.MAX_BUFFER_SIZE);
|
||||
assertTrue(asciiArmoredBytes.length > OpenPGPAnimalSnifferInputStream.MAX_BUFFER_SIZE);
|
||||
ByteArrayInputStream asciiIn = new ByteArrayInputStream(asciiArmoredBytes);
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(asciiIn);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(asciiIn);
|
||||
|
||||
assertTrue(openPgpInputStream.isAsciiArmored());
|
||||
assertFalse(openPgpInputStream.isNonOpenPgp());
|
||||
|
@ -694,7 +694,7 @@ public class OpenPgpInputStreamTest {
|
|||
|
||||
byte[] binaryBytes = binaryOut.toByteArray();
|
||||
ByteArrayInputStream binaryIn = new ByteArrayInputStream(binaryBytes);
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(binaryIn);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(binaryIn);
|
||||
|
||||
assertTrue(openPgpInputStream.isBinaryOpenPgp());
|
||||
assertFalse(openPgpInputStream.isAsciiArmored());
|
||||
|
@ -714,7 +714,7 @@ public class OpenPgpInputStreamTest {
|
|||
|
||||
byte[] binaryBytes = binaryOut.toByteArray();
|
||||
ByteArrayInputStream binaryIn = new ByteArrayInputStream(binaryBytes);
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(binaryIn);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(binaryIn);
|
||||
|
||||
assertTrue(openPgpInputStream.isBinaryOpenPgp());
|
||||
assertFalse(openPgpInputStream.isAsciiArmored());
|
||||
|
@ -728,7 +728,7 @@ public class OpenPgpInputStreamTest {
|
|||
@Test
|
||||
public void emptyStreamTest() throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
OpenPgpInputStream openPgpInputStream = new OpenPgpInputStream(in);
|
||||
OpenPGPAnimalSnifferInputStream openPgpInputStream = new OpenPGPAnimalSnifferInputStream(in);
|
||||
|
||||
assertFalse(openPgpInputStream.isBinaryOpenPgp());
|
||||
assertFalse(openPgpInputStream.isAsciiArmored());
|
||||
|
@ -755,7 +755,7 @@ public class OpenPgpInputStreamTest {
|
|||
|
||||
byte[] binary = signedOut.toByteArray();
|
||||
|
||||
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(new ByteArrayInputStream(binary));
|
||||
OpenPGPAnimalSnifferInputStream openPgpIn = new OpenPGPAnimalSnifferInputStream(new ByteArrayInputStream(binary));
|
||||
assertFalse(openPgpIn.isAsciiArmored());
|
||||
assertTrue(openPgpIn.isLikelyOpenPgpMessage());
|
||||
}
|
|
@ -10,7 +10,7 @@ import java.io.OutputStream
|
|||
import kotlin.jvm.Throws
|
||||
import org.bouncycastle.util.io.Streams
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.decryption_verification.OpenPgpInputStream
|
||||
import org.pgpainless.decryption_verification.OpenPGPAnimalSnifferInputStream
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException
|
||||
|
@ -27,7 +27,7 @@ class ArmorImpl(private val api: PGPainless) : Armor {
|
|||
val bufferedOutputStream = BufferedOutputStream(outputStream)
|
||||
|
||||
// Determine the nature of the given data
|
||||
val openPgpIn = OpenPgpInputStream(data)
|
||||
val openPgpIn = OpenPGPAnimalSnifferInputStream(data)
|
||||
openPgpIn.reset()
|
||||
|
||||
if (openPgpIn.isAsciiArmored) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import org.bouncycastle.openpgp.PGPOnePassSignatureList
|
|||
import org.bouncycastle.openpgp.PGPSignatureList
|
||||
import org.bouncycastle.util.io.Streams
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.decryption_verification.OpenPgpInputStream
|
||||
import org.pgpainless.decryption_verification.OpenPGPAnimalSnifferInputStream
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil
|
||||
import org.pgpainless.exception.WrongConsumingMethodException
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory
|
||||
|
@ -35,7 +35,7 @@ class InlineDetachImpl(private val api: PGPainless) : InlineDetach {
|
|||
private val sigOut = ByteArrayOutputStream()
|
||||
|
||||
override fun writeTo(outputStream: OutputStream): Signatures {
|
||||
var pgpIn = OpenPgpInputStream(messageInputStream)
|
||||
var pgpIn = OpenPGPAnimalSnifferInputStream(messageInputStream)
|
||||
if (pgpIn.isNonOpenPgp) {
|
||||
throw SOPGPException.BadData("Data appears to be non-OpenPGP.")
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class InlineDetachImpl(private val api: PGPainless) : InlineDetach {
|
|||
}
|
||||
|
||||
// else just dearmor
|
||||
pgpIn = OpenPgpInputStream(armorIn)
|
||||
pgpIn = OpenPGPAnimalSnifferInputStream(armorIn)
|
||||
}
|
||||
|
||||
// If data was not using cleartext signature framework
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue