Abort decryption process at a depth of 16 nested packets

This commit is contained in:
Paul Schaub 2021-02-19 21:37:54 +01:00
parent 217609679d
commit 81393a7285
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
2 changed files with 186 additions and 11 deletions

View File

@ -64,6 +64,7 @@ public final class DecryptionStreamFactory {
private static final Logger LOGGER = Logger.getLogger(DecryptionStreamFactory.class.getName());
private static final Level LEVEL = Level.FINE;
private static final int MAX_RECURSION_DEPTH = 16;
private final PGPSecretKeyRingCollection decryptionKeys;
private final SecretKeyRingProtector decryptionKeyDecryptor;
@ -115,22 +116,25 @@ public final class DecryptionStreamFactory {
} else {
PGPObjectFactory objectFactory = new PGPObjectFactory(
PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator);
pgpInputStream = factory.processPGPPackets(objectFactory);
pgpInputStream = factory.processPGPPackets(objectFactory, 1);
}
return new DecryptionStream(pgpInputStream, factory.resultBuilder, factory.integrityProtectedStreams);
}
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory) throws IOException, PGPException {
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth) throws IOException, PGPException {
if (depth >= MAX_RECURSION_DEPTH) {
throw new PGPException("Maximum recursion depth of packages exceeded.");
}
Object nextPgpObject;
while ((nextPgpObject = objectFactory.nextObject()) != null) {
if (nextPgpObject instanceof PGPEncryptedDataList) {
return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject);
return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject, depth);
}
if (nextPgpObject instanceof PGPCompressedData) {
return processPGPCompressedData((PGPCompressedData) nextPgpObject);
return processPGPCompressedData((PGPCompressedData) nextPgpObject, depth);
}
if (nextPgpObject instanceof PGPOnePassSignatureList) {
return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject);
return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject, depth);
}
if (nextPgpObject instanceof PGPLiteralData) {
return processPGPLiteralData(objectFactory, (PGPLiteralData) nextPgpObject);
@ -140,14 +144,14 @@ public final class DecryptionStreamFactory {
throw new PGPException("No Literal Data Packet found");
}
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList)
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
throws PGPException, IOException {
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
InputStream decryptedDataStream = decrypt(pgpEncryptedDataList);
return processPGPPackets(new PGPObjectFactory(PGPUtil.getDecoderStream(decryptedDataStream), keyFingerprintCalculator));
return processPGPPackets(new PGPObjectFactory(PGPUtil.getDecoderStream(decryptedDataStream), keyFingerprintCalculator), ++depth);
}
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData)
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
throws PGPException, IOException {
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " + compressionAlgorithm);
@ -156,14 +160,14 @@ public final class DecryptionStreamFactory {
InputStream dataStream = pgpCompressedData.getDataStream();
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(dataStream), keyFingerprintCalculator);
return processPGPPackets(objectFactory);
return processPGPPackets(objectFactory, ++depth);
}
private InputStream processOnePassSignatureList(@Nonnull PGPObjectFactory objectFactory, PGPOnePassSignatureList onePassSignatures)
private InputStream processOnePassSignatureList(@Nonnull PGPObjectFactory objectFactory, PGPOnePassSignatureList onePassSignatures, int depth)
throws PGPException, IOException {
LOGGER.log(LEVEL, "Encountered PGPOnePassSignatureList of size " + onePassSignatures.size());
initOnePassSignatures(onePassSignatures);
return processPGPPackets(objectFactory);
return processPGPPackets(objectFactory, ++depth);
}
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData) {

View File

@ -0,0 +1,171 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.decryption_verification;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.protection.SecretKeyRingProtector;
public class RecursionDepthTest {
/**
* Test that decryption is aborted when maximum recursion depth of nested packets is exceeded.
*
* @see <a href="https://tests.sequoia-pgp.org/#Maximum_recursion_depth">Sequoia-PGP Test Suite</a>
* @throws IOException
* @throws PGPException
*/
@Test
public void decryptionAbortsWhenMaximumRecursionDepthReachedTest() throws IOException, PGPException {
String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Comment: Bob's OpenPGP Transferable Secret Key\n" +
"\n" +
"lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
"vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" +
"cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" +
"3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" +
"Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" +
"hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" +
"bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" +
"i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" +
"1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" +
"fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" +
"fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" +
"LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" +
"+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" +
"hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" +
"WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" +
"MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" +
"mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" +
"YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" +
"he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" +
"zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" +
"NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" +
"t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" +
"ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" +
"F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" +
"2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" +
"yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" +
"doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" +
"BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" +
"sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" +
"4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" +
"L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" +
"ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad\n" +
"BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" +
"bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" +
"29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" +
"WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" +
"leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" +
"g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" +
"Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" +
"JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" +
"IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" +
"SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" +
"OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" +
"Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" +
"+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" +
"tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" +
"BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" +
"zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" +
"clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" +
"zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" +
"gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" +
"aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" +
"fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" +
"ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" +
"HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" +
"SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" +
"5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" +
"E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" +
"GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" +
"vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" +
"26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP\n" +
"eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" +
"c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" +
"rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" +
"JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" +
"71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" +
"s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" +
"NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" +
"6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" +
"xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" +
"=miES\n" +
"-----END PGP PRIVATE KEY BLOCK-----\n";
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(Collections.singletonList(secretKey));
// message contains compressed data that contains compressed data that contains... 64 times.
String msg = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wcDMA3wvqk35PDeyAQwAr3+n4eqpdArKx0YFn3hFutlMYhXWzAjlyXrhezxj3RsT\n" +
"bvUDEt634rWVZQ5aHYEUu45jtgWD7snzvoRBt0V8PfI0wEbozDGhyASktWZszn3t\n" +
"iifbBioBLdLrprm643OSRpmFeGPLJQzzbrgZE1C+2DaqTZ3RpFBoi5UOxlXa9kav\n" +
"k5TpqrLs+ejVMNX1ozprq+YxMEqIIheGekNd93RdLME1WErGlzNY2L9F9R0h/Mcr\n" +
"OB/i1HB1cG/kQUPu5OyQv1eairkxDa0dOYBWiFjeEhW+ydLqgATPly3bPfzTj2Hp\n" +
"7ojT34W+FwnnYk6L9mOXXfry0McZPknI6C1cymEQuLeT8BOWEY6QreruvyOdN/rY\n" +
"1zd9+cHC6gfsKPuOossO0Lcx2jhY7EfPSak2RgPeI0x4l5WRAftodBmCLpeWd4oe\n" +
"Jf7r0DmMW2zsiZnV/hiy+m1MsgIE1f28FF5tmTOsrSanrkmjjne15H8YpmHCtxJw\n" +
"5EFEENY9sBOifI3wmS230sGQAffKmrcHZidia9+vXDx+ezVyPR7M7oHOqa+O0bY/\n" +
"uUrWabcA/CPWskun3A3ZQqpm1j6zAEE8CbBsaLSWYz4LgNvmpJX7fQiutk92Y3gO\n" +
"ClxcTZRk6fsUpFx8F4dKTXnnHpe1EmRqJJVpwu4nICQ5nHhAO5gLBTUQ4WfENf/J\n" +
"0t61KF6ehebxHUAng5ssX+sw5I+BXQUfmY3LyTNsHwhAG0CZa5fhg2tZzSAYdBQk\n" +
"pJljvL8klHo44TUfSKuRPmuoRZgCoEgqAS/XlR788tJxudL/EhShENPRylHAUSaa\n" +
"mzasdjV4TeSpvMl4/02ofdkwLBZGN0V7lGex3pTNukNtkdh1kNnCVdr+cOwufWxw\n" +
"bjOh76hUrU9GXvnLm3OKa20YUjeDzLk8lGerhwU9U/nJ8N3omnD+AELFHtdBPq1Z\n" +
"il2pvjJ6lOtMV7G1dZIRbIZHyOUgEyrSwsBcv0rxZYcidKrhX/FnZFEvBsUnRoNO\n" +
"ec9JX9rBoItBDjAjNydjeBPDjo3XYmEf0XFkTsXfEatvarPJ3QcbfkG/zpUgn85f\n" +
"d0VYB9O163ws2C1fPeVbsBqlXSVS8IVtq+Gja2twOljz9OMK5QRRj2lB1Bo8zQ1/\n" +
"lDSKkQJuZ3eomr3KTpyVR+Y+Yrje8FQTsGAEVRexqoVKLZEaEjV96vRtrKwxo+oO\n" +
"KQfFkJQ1pTLRGO6Kf+3YI76RzR+SVsWMwkqOq9Bx0T4UVBEqK/KYeBW+TJruercc\n" +
"LopiuQRJtotZ7sC1/CGwAsWBDG1NmRGrldRhRwB4FVU2Jg==\n" +
"=z63W\n" +
"-----END PGP ARMORED FILE-----\n";
assertThrows(PGPException.class, () -> {
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
.decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
.doNotVerify()
.build();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Streams.pipeAll(decryptionStream, outputStream);
});
}
}