001// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.decryption_verification;
006
007import java.io.BufferedInputStream;
008import java.io.EOFException;
009import java.io.IOException;
010import java.io.InputStream;
011import java.util.ArrayList;
012import java.util.HashMap;
013import java.util.HashSet;
014import java.util.Iterator;
015import java.util.List;
016import java.util.Map;
017import java.util.Set;
018import javax.annotation.Nonnull;
019import javax.annotation.Nullable;
020
021import org.bouncycastle.bcpg.ArmoredInputStream;
022import org.bouncycastle.openpgp.PGPCompressedData;
023import org.bouncycastle.openpgp.PGPEncryptedData;
024import org.bouncycastle.openpgp.PGPEncryptedDataList;
025import org.bouncycastle.openpgp.PGPException;
026import org.bouncycastle.openpgp.PGPLiteralData;
027import org.bouncycastle.openpgp.PGPObjectFactory;
028import org.bouncycastle.openpgp.PGPOnePassSignature;
029import org.bouncycastle.openpgp.PGPOnePassSignatureList;
030import org.bouncycastle.openpgp.PGPPBEEncryptedData;
031import org.bouncycastle.openpgp.PGPPrivateKey;
032import org.bouncycastle.openpgp.PGPPublicKey;
033import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
034import org.bouncycastle.openpgp.PGPPublicKeyRing;
035import org.bouncycastle.openpgp.PGPSecretKey;
036import org.bouncycastle.openpgp.PGPSecretKeyRing;
037import org.bouncycastle.openpgp.PGPSessionKey;
038import org.bouncycastle.openpgp.PGPSignature;
039import org.bouncycastle.openpgp.PGPUtil;
040import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
041import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
042import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
043import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
044import org.pgpainless.PGPainless;
045import org.pgpainless.algorithm.CompressionAlgorithm;
046import org.pgpainless.algorithm.EncryptionPurpose;
047import org.pgpainless.algorithm.StreamEncoding;
048import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
049import org.pgpainless.exception.MessageNotIntegrityProtectedException;
050import org.pgpainless.exception.MissingDecryptionMethodException;
051import org.pgpainless.exception.MissingLiteralDataException;
052import org.pgpainless.exception.MissingPassphraseException;
053import org.pgpainless.exception.SignatureValidationException;
054import org.pgpainless.exception.UnacceptableAlgorithmException;
055import org.pgpainless.exception.WrongConsumingMethodException;
056import org.pgpainless.implementation.ImplementationFactory;
057import org.pgpainless.key.SubkeyIdentifier;
058import org.pgpainless.key.info.KeyRingInfo;
059import org.pgpainless.key.protection.SecretKeyRingProtector;
060import org.pgpainless.key.protection.UnlockSecretKey;
061import org.pgpainless.signature.SignatureUtils;
062import org.pgpainless.signature.consumer.DetachedSignatureCheck;
063import org.pgpainless.signature.consumer.OnePassSignatureCheck;
064import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
065import org.pgpainless.util.PGPUtilWrapper;
066import org.pgpainless.util.Passphrase;
067import org.pgpainless.util.SessionKey;
068import org.pgpainless.util.Tuple;
069import org.slf4j.Logger;
070import org.slf4j.LoggerFactory;
071
072public final class DecryptionStreamFactory {
073
074
075    private static final Logger LOGGER = LoggerFactory.getLogger(DecryptionStreamFactory.class);
076    // Maximum nesting depth of packets (e.g. compression, encryption...)
077    private static final int MAX_PACKET_NESTING_DEPTH = 16;
078
079    private final ConsumerOptions options;
080    private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
081    private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>();
082    private final List<DetachedSignatureCheck> detachedSignatureChecks = new ArrayList<>();
083    private final Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert = new HashMap<>();
084
085    private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
086            ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
087    private IntegrityProtectedInputStream integrityProtectedEncryptedInputStream;
088
089
090    public static DecryptionStream create(@Nonnull InputStream inputStream,
091                                          @Nonnull ConsumerOptions options)
092            throws PGPException, IOException {
093        DecryptionStreamFactory factory = new DecryptionStreamFactory(options);
094        return factory.parseOpenPGPDataAndCreateDecryptionStream(inputStream);
095    }
096
097    public DecryptionStreamFactory(ConsumerOptions options) {
098        this.options = options;
099        initializeDetachedSignatures(options.getDetachedSignatures());
100    }
101
102    private void initializeDetachedSignatures(Set<PGPSignature> signatures) {
103        for (PGPSignature signature : signatures) {
104            long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature);
105            PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
106            if (signingKeyRing == null) {
107                SignatureValidationException ex = new SignatureValidationException(
108                        "Missing verification certificate " + Long.toHexString(issuerKeyId));
109                resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex);
110                continue;
111            }
112            PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
113            SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
114            try {
115                signature.init(verifierBuilderProvider, signingKey);
116                DetachedSignatureCheck detachedSignature =
117                        new DetachedSignatureCheck(signature, signingKeyRing, signingKeyIdentifier);
118                detachedSignatureChecks.add(detachedSignature);
119            } catch (PGPException e) {
120                SignatureValidationException ex = new SignatureValidationException(
121                        "Cannot verify detached signature made by " + signingKeyIdentifier + ".", e);
122                resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, signingKeyIdentifier), ex);
123            }
124        }
125    }
126
127    private DecryptionStream parseOpenPGPDataAndCreateDecryptionStream(InputStream inputStream)
128            throws IOException, PGPException {
129        // Make sure we handle armored and non-armored data properly
130        BufferedInputStream bufferedIn = new BufferedInputStream(inputStream, 512);
131        bufferedIn.mark(512);
132        InputStream decoderStream;
133        PGPObjectFactory objectFactory;
134
135        if (options.isCleartextSigned()) {
136            inputStream = wrapInVerifySignatureStream(bufferedIn, null);
137            return new DecryptionStream(inputStream, resultBuilder, integrityProtectedEncryptedInputStream,
138                    null);
139        }
140
141        try {
142            decoderStream = PGPUtilWrapper.getDecoderStream(bufferedIn);
143            decoderStream = CRCingArmoredInputStreamWrapper.possiblyWrap(decoderStream);
144
145            if (decoderStream instanceof ArmoredInputStream) {
146                ArmoredInputStream armor = (ArmoredInputStream) decoderStream;
147
148                if (armor.isClearText()) {
149                    throw new WrongConsumingMethodException("Message appears to be using the Cleartext Signature Framework. " +
150                            "Use PGPainless.verifyCleartextSignedMessage() to verify this message instead.");
151                }
152            }
153
154            objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
155            // Parse OpenPGP message
156            inputStream = processPGPPackets(objectFactory, 1);
157        } catch (EOFException e) {
158            throw e;
159        } catch (MissingLiteralDataException e) {
160            // Not an OpenPGP message.
161            //  Reset the buffered stream to parse the message as arbitrary binary data
162            //  to allow for detached signature verification.
163            LOGGER.debug("The message appears to not be an OpenPGP message. This is probably data signed with detached signatures?");
164            bufferedIn.reset();
165            decoderStream = bufferedIn;
166            objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
167            inputStream = wrapInVerifySignatureStream(bufferedIn, objectFactory);
168        } catch (IOException e) {
169            if (e.getMessage().contains("invalid armor") || e.getMessage().contains("invalid header encountered")) {
170                // We falsely assumed the data to be armored.
171                LOGGER.debug("The message is apparently not armored.");
172                bufferedIn.reset();
173                decoderStream = bufferedIn;
174                objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
175                inputStream = wrapInVerifySignatureStream(bufferedIn, objectFactory);
176            } else {
177                throw e;
178            }
179        }
180
181        return new DecryptionStream(inputStream, resultBuilder, integrityProtectedEncryptedInputStream,
182                (decoderStream instanceof ArmoredInputStream) ? decoderStream : null);
183    }
184
185    private InputStream wrapInVerifySignatureStream(InputStream bufferedIn, @Nullable PGPObjectFactory objectFactory) {
186        return new SignatureInputStream.VerifySignatures(
187                bufferedIn, objectFactory, onePassSignatureChecks,
188                onePassSignaturesWithMissingCert, detachedSignatureChecks, options,
189                resultBuilder);
190    }
191
192    private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth)
193            throws IOException, PGPException {
194        if (depth >= MAX_PACKET_NESTING_DEPTH) {
195            throw new PGPException("Maximum depth of nested packages exceeded.");
196        }
197        Object nextPgpObject;
198        while ((nextPgpObject = objectFactory.nextObject()) != null) {
199            if (nextPgpObject instanceof PGPEncryptedDataList) {
200                return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject, depth);
201            }
202            if (nextPgpObject instanceof PGPCompressedData) {
203                return processPGPCompressedData((PGPCompressedData) nextPgpObject, depth);
204            }
205            if (nextPgpObject instanceof PGPOnePassSignatureList) {
206                return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject, depth);
207            }
208            if (nextPgpObject instanceof PGPLiteralData) {
209                return processPGPLiteralData(objectFactory, (PGPLiteralData) nextPgpObject, depth);
210            }
211        }
212
213        throw new MissingLiteralDataException("No Literal Data Packet found");
214    }
215
216    private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
217            throws PGPException, IOException {
218        LOGGER.debug("Depth {}: Encountered PGPEncryptedDataList", depth);
219
220        SessionKey sessionKey = options.getSessionKey();
221        if (sessionKey != null) {
222            integrityProtectedEncryptedInputStream = decryptWithProvidedSessionKey(pgpEncryptedDataList, sessionKey);
223            InputStream decodedDataStream = PGPUtil.getDecoderStream(integrityProtectedEncryptedInputStream);
224            PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
225            return processPGPPackets(factory, ++depth);
226        }
227
228        InputStream decryptedDataStream = decryptSessionKey(pgpEncryptedDataList);
229        InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
230        PGPObjectFactory factory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
231        return processPGPPackets(factory, ++depth);
232    }
233
234    private IntegrityProtectedInputStream decryptWithProvidedSessionKey(
235            PGPEncryptedDataList pgpEncryptedDataList,
236            SessionKey sessionKey)
237            throws PGPException {
238        PGPSessionKey pgpSessionKey = new PGPSessionKey(sessionKey.getAlgorithm().getAlgorithmId(), sessionKey.getKey());
239        SessionKeyDataDecryptorFactory decryptorFactory =
240                ImplementationFactory.getInstance().provideSessionKeyDataDecryptorFactory(pgpSessionKey);
241        InputStream decryptedDataStream = null;
242        PGPEncryptedData encryptedData = null;
243        for (PGPEncryptedData pgpEncryptedData : pgpEncryptedDataList) {
244            encryptedData = pgpEncryptedData;
245            if (!options.isIgnoreMDCErrors() && !encryptedData.isIntegrityProtected()) {
246                throw new MessageNotIntegrityProtectedException();
247            }
248
249            if (encryptedData instanceof PGPPBEEncryptedData) {
250                PGPPBEEncryptedData pbeEncrypted = (PGPPBEEncryptedData) encryptedData;
251                decryptedDataStream = pbeEncrypted.getDataStream(decryptorFactory);
252                break;
253            } else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
254                PGPPublicKeyEncryptedData pkEncrypted = (PGPPublicKeyEncryptedData) encryptedData;
255                decryptedDataStream = pkEncrypted.getDataStream(decryptorFactory);
256                break;
257            }
258        }
259
260        if (decryptedDataStream == null) {
261            throw new PGPException("No valid PGP data encountered.");
262        }
263
264        resultBuilder.setSessionKey(sessionKey);
265        throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
266        integrityProtectedEncryptedInputStream =
267                new IntegrityProtectedInputStream(decryptedDataStream, encryptedData, options);
268        return integrityProtectedEncryptedInputStream;
269    }
270
271    private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
272            throws PGPException, IOException {
273        CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
274        LOGGER.debug("Depth {}: Encountered PGPCompressedData: {}", depth, compressionAlgorithm);
275        resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
276
277        InputStream inflatedDataStream = pgpCompressedData.getDataStream();
278        InputStream decodedDataStream = PGPUtil.getDecoderStream(inflatedDataStream);
279        PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decodedDataStream);
280
281        return processPGPPackets(objectFactory, ++depth);
282    }
283
284    private InputStream processOnePassSignatureList(
285            @Nonnull PGPObjectFactory objectFactory,
286            PGPOnePassSignatureList onePassSignatures,
287            int depth)
288            throws PGPException, IOException {
289        LOGGER.debug("Depth {}: Encountered PGPOnePassSignatureList of size {}", depth, onePassSignatures.size());
290        initOnePassSignatures(onePassSignatures);
291        return processPGPPackets(objectFactory, depth);
292    }
293
294    private InputStream processPGPLiteralData(
295            @Nonnull PGPObjectFactory objectFactory,
296            PGPLiteralData pgpLiteralData,
297            int depth) {
298        LOGGER.debug("Depth {}: Found PGPLiteralData", depth);
299        InputStream literalDataInputStream = pgpLiteralData.getInputStream();
300
301        resultBuilder.setFileName(pgpLiteralData.getFileName())
302                .setModificationDate(pgpLiteralData.getModificationTime())
303                .setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
304
305        if (onePassSignatureChecks.isEmpty() && onePassSignaturesWithMissingCert.isEmpty()) {
306            LOGGER.debug("No OnePassSignatures found -> We are done");
307            return literalDataInputStream;
308        }
309
310        return new SignatureInputStream.VerifySignatures(literalDataInputStream, objectFactory,
311                onePassSignatureChecks, onePassSignaturesWithMissingCert, detachedSignatureChecks, options, resultBuilder) {
312        };
313    }
314
315    private InputStream decryptSessionKey(@Nonnull PGPEncryptedDataList encryptedDataList)
316            throws PGPException {
317        Iterator<PGPEncryptedData> encryptedDataIterator = encryptedDataList.getEncryptedDataObjects();
318        if (!encryptedDataIterator.hasNext()) {
319            throw new PGPException("Decryption failed - EncryptedDataList has no items");
320        }
321
322        PGPPrivateKey decryptionKey = null;
323        PGPPublicKeyEncryptedData encryptedSessionKey = null;
324
325        List<PGPPBEEncryptedData> passphraseProtected = new ArrayList<>();
326        List<PGPPublicKeyEncryptedData> publicKeyProtected = new ArrayList<>();
327        List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponedDueToMissingPassphrase = new ArrayList<>();
328
329        // Sort PKESK and SKESK packets
330        while (encryptedDataIterator.hasNext()) {
331            PGPEncryptedData encryptedData = encryptedDataIterator.next();
332
333            if (!encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
334                throw new MessageNotIntegrityProtectedException();
335            }
336
337            // SKESK
338            if (encryptedData instanceof PGPPBEEncryptedData) {
339                passphraseProtected.add((PGPPBEEncryptedData) encryptedData);
340            }
341            // PKESK
342            else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
343                publicKeyProtected.add((PGPPublicKeyEncryptedData) encryptedData);
344            }
345        }
346
347        // Try decryption with passphrases first
348        for (PGPPBEEncryptedData pbeEncryptedData : passphraseProtected) {
349            for (Passphrase passphrase : options.getDecryptionPassphrases()) {
350                PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance()
351                        .getPBEDataDecryptorFactory(passphrase);
352                try {
353                    InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
354
355                    PGPSessionKey pgpSessionKey = pbeEncryptedData.getSessionKey(passphraseDecryptor);
356                    SessionKey sessionKey = new SessionKey(pgpSessionKey);
357                    resultBuilder.setSessionKey(sessionKey);
358
359                    throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
360
361                    integrityProtectedEncryptedInputStream =
362                            new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
363
364                    return integrityProtectedEncryptedInputStream;
365                } catch (PGPException e) {
366                    LOGGER.debug("Probable passphrase mismatch, skip PBE encrypted data block", e);
367                }
368            }
369        }
370
371        // Then try decryption with public key encryption
372        for (PGPPublicKeyEncryptedData publicKeyEncryptedData : publicKeyProtected) {
373            PGPPrivateKey privateKey = null;
374            if (options.getDecryptionKeys().isEmpty()) {
375                break;
376            }
377
378            long keyId = publicKeyEncryptedData.getKeyID();
379            // Wildcard KeyID
380            if (keyId == 0L) {
381                LOGGER.debug("Hidden recipient detected. Try to decrypt with all available secret keys.");
382                for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
383                    if (privateKey != null) {
384                        break;
385                    }
386                    KeyRingInfo info = new KeyRingInfo(secretKeys);
387                    List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
388                    for (PGPPublicKey pubkey : encryptionSubkeys) {
389                        PGPSecretKey secretKey = secretKeys.getSecretKey(pubkey.getKeyID());
390                        // Skip missing secret key
391                        if (secretKey == null) {
392                            continue;
393                        }
394
395                        privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
396                                postponedDueToMissingPassphrase, true);
397                    }
398                }
399            }
400            // Non-wildcard key-id
401            else {
402                LOGGER.debug("PGPEncryptedData is encrypted for key {}", Long.toHexString(keyId));
403                resultBuilder.addRecipientKeyId(keyId);
404
405                PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId);
406                if (secretKeys == null) {
407                    LOGGER.debug("Missing certificate of {}. Skip.", Long.toHexString(keyId));
408                    continue;
409                }
410
411                // Make sure that the recipient key is encryption capable and non-expired
412                KeyRingInfo info = new KeyRingInfo(secretKeys);
413                List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
414
415                PGPSecretKey secretKey = null;
416                for (PGPPublicKey pubkey : encryptionSubkeys) {
417                    if (pubkey.getKeyID() == keyId) {
418                        secretKey = secretKeys.getSecretKey(keyId);
419                        break;
420                    }
421                }
422
423                if (secretKey == null) {
424                    LOGGER.debug("Key " + Long.toHexString(keyId) + " is not valid or not capable for decryption.");
425                } else {
426                    privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
427                            postponedDueToMissingPassphrase, true);
428                }
429            }
430            if (privateKey == null) {
431                continue;
432            }
433            decryptionKey = privateKey;
434            encryptedSessionKey = publicKeyEncryptedData;
435            break;
436        }
437
438        // Try postponed keys with missing passphrases (will cause missing passphrase callbacks to fire)
439        if (encryptedSessionKey == null) {
440
441            if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
442                // Non-interactive mode: Throw an exception with all locked decryption keys
443                Set<SubkeyIdentifier> keyIds = new HashSet<>();
444                for (Tuple<SubkeyIdentifier, ?> k : postponedDueToMissingPassphrase) {
445                    keyIds.add(k.getA());
446                }
447                if (!keyIds.isEmpty()) {
448                    throw new MissingPassphraseException(keyIds);
449                }
450            }
451            else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
452                // Interactive mode: Fire protector callbacks to get passphrases interactively
453                for (Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData> missingPassphrases : postponedDueToMissingPassphrase) {
454                    SubkeyIdentifier keyId = missingPassphrases.getA();
455                    PGPPublicKeyEncryptedData publicKeyEncryptedData = missingPassphrases.getB();
456                    PGPSecretKeyRing secretKeys = findDecryptionKeyRing(keyId.getKeyId());
457                    PGPSecretKey secretKey = secretKeys.getSecretKey(keyId.getSubkeyId());
458
459                    PGPPrivateKey privateKey = tryPublicKeyDecryption(secretKeys, secretKey, publicKeyEncryptedData,
460                            postponedDueToMissingPassphrase, false);
461                    if (privateKey == null) {
462                        continue;
463                    }
464
465                    decryptionKey = privateKey;
466                    encryptedSessionKey = publicKeyEncryptedData;
467                    break;
468                }
469            } else {
470                throw new IllegalStateException("Invalid PostponedKeysStrategy set in consumer options.");
471            }
472
473        }
474
475        return decryptWith(encryptedSessionKey, decryptionKey);
476    }
477
478    /**
479     * Try decryption of the provided public-key-encrypted-data using the given secret key.
480     * If the secret key is encrypted and the secret key protector does not have a passphrase available and the boolean
481     * postponeIfMissingPassphrase is true, data decryption is postponed by pushing a tuple of the encrypted data decryption key
482     * identifier to the postponed list.
483     *
484     * This method only returns a non-null private key, if the private key is able to decrypt the message successfully.
485     *
486     * @param secretKeys secret key ring
487     * @param secretKey secret key
488     * @param publicKeyEncryptedData encrypted data which is tried to decrypt using the secret key
489     * @param postponed list of postponed decryptions due to missing secret key passphrases
490     * @param postponeIfMissingPassphrase flag to specify whether missing secret key passphrases should result in postponed decryption
491     * @return private key if decryption is successful, null if decryption is unsuccessful or postponed
492     *
493     * @throws PGPException in case of an OpenPGP error
494     */
495    private PGPPrivateKey tryPublicKeyDecryption(
496            PGPSecretKeyRing secretKeys,
497            PGPSecretKey secretKey,
498            PGPPublicKeyEncryptedData publicKeyEncryptedData,
499            List<Tuple<SubkeyIdentifier, PGPPublicKeyEncryptedData>> postponed,
500            boolean postponeIfMissingPassphrase) throws PGPException {
501        SecretKeyRingProtector protector = options.getSecretKeyProtector(secretKeys);
502
503        if (postponeIfMissingPassphrase && !protector.hasPassphraseFor(secretKey.getKeyID())) {
504            // Postpone decryption with key with missing passphrase
505            SubkeyIdentifier identifier = new SubkeyIdentifier(secretKeys, secretKey.getKeyID());
506            postponed.add(new Tuple<>(identifier, publicKeyEncryptedData));
507            return null;
508        }
509
510        PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(
511                secretKey, protector.getDecryptor(secretKey.getKeyID()));
512
513        // test if we have the right private key
514        PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
515                .getPublicKeyDataDecryptorFactory(privateKey);
516        try {
517            publicKeyEncryptedData.getSymmetricAlgorithm(decryptorFactory); // will only succeed if we have the right secret key
518            LOGGER.debug("Found correct decryption key {}.", Long.toHexString(secretKey.getKeyID()));
519            resultBuilder.setDecryptionKey(new SubkeyIdentifier(secretKeys, privateKey.getKeyID()));
520            return privateKey;
521        } catch (PGPException | ClassCastException e) {
522            return null;
523        }
524    }
525
526    private InputStream decryptWith(PGPPublicKeyEncryptedData encryptedSessionKey, PGPPrivateKey decryptionKey)
527            throws PGPException {
528        if (decryptionKey == null || encryptedSessionKey == null) {
529            throw new MissingDecryptionMethodException("Decryption failed - No suitable decryption key or passphrase found");
530        }
531
532        PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
533                .getPublicKeyDataDecryptorFactory(decryptionKey);
534
535        PGPSessionKey pgpSessionKey = encryptedSessionKey.getSessionKey(dataDecryptor);
536        SessionKey sessionKey = new SessionKey(pgpSessionKey);
537        resultBuilder.setSessionKey(sessionKey);
538
539        SymmetricKeyAlgorithm symmetricKeyAlgorithm = sessionKey.getAlgorithm();
540        if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
541            LOGGER.debug("Message is unencrypted");
542        } else {
543            LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
544        }
545        throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
546
547        integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(
548                encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
549        return integrityProtectedEncryptedInputStream;
550    }
551
552    private void throwIfAlgorithmIsRejected(SymmetricKeyAlgorithm algorithm)
553            throws UnacceptableAlgorithmException {
554        if (!PGPainless.getPolicy().getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) {
555            throw new UnacceptableAlgorithmException("Data is "
556                    + (algorithm == SymmetricKeyAlgorithm.NULL ?
557                    "unencrypted" :
558                    "encrypted with symmetric algorithm " + algorithm) + " which is not acceptable as per PGPainless' policy.\n" +
559                    "To mark this algorithm as acceptable, use PGPainless.getPolicy().setSymmetricKeyDecryptionAlgorithmPolicy().");
560        }
561    }
562
563    private void initOnePassSignatures(@Nonnull PGPOnePassSignatureList onePassSignatureList)
564            throws PGPException {
565        Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
566        if (!iterator.hasNext()) {
567            throw new PGPException("Verification failed - No OnePassSignatures found");
568        }
569
570        processOnePassSignatures(iterator);
571    }
572
573    private void processOnePassSignatures(Iterator<PGPOnePassSignature> signatures)
574            throws PGPException {
575        while (signatures.hasNext()) {
576            PGPOnePassSignature signature = signatures.next();
577            processOnePassSignature(signature);
578        }
579    }
580
581    private void processOnePassSignature(PGPOnePassSignature signature)
582            throws PGPException {
583        final long keyId = signature.getKeyID();
584
585        LOGGER.debug("Encountered OnePassSignature from {}", Long.toHexString(keyId));
586
587        // Find public key
588        PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
589        if (verificationKeyRing == null) {
590            onePassSignaturesWithMissingCert.put(keyId, new OnePassSignatureCheck(signature, null));
591            return;
592        }
593        PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
594
595        signature.init(verifierBuilderProvider, verificationKey);
596        OnePassSignatureCheck onePassSignature = new OnePassSignatureCheck(signature, verificationKeyRing);
597        onePassSignatureChecks.add(onePassSignature);
598    }
599
600    private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
601        for (PGPSecretKeyRing key : options.getDecryptionKeys()) {
602            if (key.getSecretKey(keyId) != null) {
603                return key;
604            }
605        }
606        return null;
607    }
608
609    private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) {
610        PGPPublicKeyRing verificationKeyRing = null;
611        for (PGPPublicKeyRing publicKeyRing : options.getCertificates()) {
612            PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId);
613            if (verificationKey != null) {
614                LOGGER.debug("Found public key {} for signature verification", Long.toHexString(keyId));
615                verificationKeyRing = publicKeyRing;
616                break;
617            }
618        }
619
620        if (verificationKeyRing == null && options.getMissingCertificateCallback() != null) {
621            verificationKeyRing = options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId);
622        }
623
624        return verificationKeyRing;
625    }
626}