001/*
002 * Copyright 2018 Paul Schaub.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.pgpainless.decryption_verification;
017
018import javax.annotation.Nonnull;
019import javax.annotation.Nullable;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.Set;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030
031import org.bouncycastle.openpgp.PGPCompressedData;
032import org.bouncycastle.openpgp.PGPEncryptedDataList;
033import org.bouncycastle.openpgp.PGPException;
034import org.bouncycastle.openpgp.PGPLiteralData;
035import org.bouncycastle.openpgp.PGPObjectFactory;
036import org.bouncycastle.openpgp.PGPOnePassSignature;
037import org.bouncycastle.openpgp.PGPOnePassSignatureList;
038import org.bouncycastle.openpgp.PGPPrivateKey;
039import org.bouncycastle.openpgp.PGPPublicKey;
040import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
041import org.bouncycastle.openpgp.PGPPublicKeyRing;
042import org.bouncycastle.openpgp.PGPSecretKey;
043import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
044import org.bouncycastle.openpgp.PGPUtil;
045import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
046import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
047import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
048import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
049import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
050import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
051import org.pgpainless.algorithm.CompressionAlgorithm;
052import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
053import org.pgpainless.key.OpenPgpV4Fingerprint;
054import org.pgpainless.key.protection.SecretKeyRingProtector;
055
056public final class DecryptionStreamFactory {
057
058    private static final Logger LOGGER = Logger.getLogger(DecryptionStreamFactory.class.getName());
059    private static final Level LEVEL = Level.FINE;
060
061    private final PGPSecretKeyRingCollection decryptionKeys;
062    private final SecretKeyRingProtector decryptionKeyDecryptor;
063    private final Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
064    private final MissingPublicKeyCallback missingPublicKeyCallback;
065
066    private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
067    private final PGPContentVerifierBuilderProvider verifierBuilderProvider = new BcPGPContentVerifierBuilderProvider();
068    private final KeyFingerPrintCalculator fingerCalc = new BcKeyFingerprintCalculator();
069    private final Map<OpenPgpV4Fingerprint, PGPOnePassSignature> verifiableOnePassSignatures = new HashMap<>();
070
071    private DecryptionStreamFactory(@Nullable PGPSecretKeyRingCollection decryptionKeys,
072                                    @Nullable SecretKeyRingProtector decryptor,
073                                    @Nullable Set<PGPPublicKeyRing> verificationKeys,
074                                    @Nullable MissingPublicKeyCallback missingPublicKeyCallback) {
075        this.decryptionKeys = decryptionKeys;
076        this.decryptionKeyDecryptor = decryptor;
077        this.verificationKeys.addAll(verificationKeys != null ? verificationKeys : Collections.emptyList());
078        this.missingPublicKeyCallback = missingPublicKeyCallback;
079    }
080
081    public static DecryptionStream create(@Nonnull InputStream inputStream,
082                                          @Nullable PGPSecretKeyRingCollection decryptionKeys,
083                                          @Nullable SecretKeyRingProtector decryptor,
084                                          @Nullable Set<PGPPublicKeyRing> verificationKeys,
085                                          @Nullable MissingPublicKeyCallback missingPublicKeyCallback)
086            throws IOException, PGPException {
087
088        DecryptionStreamFactory factory =  new DecryptionStreamFactory(decryptionKeys,
089                decryptor,
090                verificationKeys,
091                missingPublicKeyCallback);
092
093        PGPObjectFactory objectFactory = new PGPObjectFactory(
094                PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator());
095
096        return new DecryptionStream(factory.wrap(objectFactory), factory.resultBuilder);
097    }
098
099    private InputStream wrap(@Nonnull PGPObjectFactory objectFactory) throws IOException, PGPException {
100
101        Object pgpObj;
102        while ((pgpObj = objectFactory.nextObject()) != null) {
103
104            if (pgpObj instanceof PGPEncryptedDataList) {
105                LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
106                PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpObj;
107                InputStream nextStream = decrypt(encDataList);
108                objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
109                return wrap(objectFactory);
110            }
111
112            if (pgpObj instanceof PGPCompressedData) {
113                PGPCompressedData compressedData = (PGPCompressedData) pgpObj;
114                InputStream nextStream = compressedData.getDataStream();
115                resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
116                objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(nextStream), fingerCalc);
117                LOGGER.log(LEVEL, "Encountered PGPCompressedData: " +
118                        CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
119                return wrap(objectFactory);
120            }
121
122            if (pgpObj instanceof PGPOnePassSignatureList) {
123                PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) pgpObj;
124                LOGGER.log(LEVEL, "Encountered PGPOnePassSignatureList of size " + onePassSignatures.size());
125                initOnePassSignatures(onePassSignatures);
126                return wrap(objectFactory);
127            }
128
129            if (pgpObj instanceof PGPLiteralData) {
130                LOGGER.log(LEVEL, "Found PGPLiteralData");
131                PGPLiteralData literalData = (PGPLiteralData) pgpObj;
132                InputStream literalDataInputStream = literalData.getInputStream();
133
134                if (verifiableOnePassSignatures.isEmpty()) {
135                    LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
136                    return literalDataInputStream;
137                }
138
139                return new SignatureVerifyingInputStream(literalDataInputStream,
140                        objectFactory, verifiableOnePassSignatures, resultBuilder);
141            }
142        }
143
144        throw new PGPException("No Literal Data Packet found");
145    }
146
147    private InputStream decrypt(@Nonnull PGPEncryptedDataList encryptedDataList)
148            throws PGPException {
149        Iterator<?> iterator = encryptedDataList.getEncryptedDataObjects();
150        if (!iterator.hasNext()) {
151            throw new PGPException("Decryption failed - EncryptedDataList has no items");
152        }
153
154        PGPPrivateKey decryptionKey = null;
155        PGPPublicKeyEncryptedData encryptedSessionKey = null;
156        while (iterator.hasNext()) {
157            PGPPublicKeyEncryptedData encryptedData = (PGPPublicKeyEncryptedData) iterator.next();
158            long keyId = encryptedData.getKeyID();
159
160            resultBuilder.addRecipientKeyId(keyId);
161            LOGGER.log(LEVEL, "PGPEncryptedData is encrypted for key " + Long.toHexString(keyId));
162
163            PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId);
164            if (secretKey != null) {
165                LOGGER.log(LEVEL, "Found respective secret key " + Long.toHexString(keyId));
166                encryptedSessionKey = encryptedData;
167                decryptionKey = secretKey.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(keyId));
168                resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(secretKey));
169            }
170        }
171
172        if (decryptionKey == null) {
173            throw new PGPException("Decryption failed - No suitable decryption key found");
174        }
175
176        PublicKeyDataDecryptorFactory keyDecryptor = new BcPublicKeyDataDecryptorFactory(decryptionKey);
177        SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm
178                .fromId(encryptedSessionKey.getSymmetricAlgorithm(keyDecryptor));
179
180        LOGGER.log(LEVEL, "Message is encrypted using " + symmetricKeyAlgorithm);
181        resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
182
183        if (encryptedSessionKey.isIntegrityProtected()) {
184            LOGGER.log(LEVEL, "Message is integrity protected");
185            resultBuilder.setIntegrityProtected(true);
186        } else {
187            LOGGER.log(LEVEL, "Message is not integrity protected");
188            resultBuilder.setIntegrityProtected(false);
189        }
190        InputStream decryptionStream = encryptedSessionKey.getDataStream(keyDecryptor);
191
192        return decryptionStream;
193    }
194
195    private void initOnePassSignatures(@Nonnull PGPOnePassSignatureList onePassSignatureList) throws PGPException {
196        Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
197        if (!iterator.hasNext()) {
198            throw new PGPException("Verification failed - No OnePassSignatures found");
199        }
200
201        while (iterator.hasNext()) {
202            PGPOnePassSignature signature = iterator.next();
203            final long keyId = signature.getKeyID();
204            resultBuilder.addUnverifiedSignatureKeyId(keyId);
205
206            LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
207
208            // Find public key
209            PGPPublicKey verificationKey = null;
210            for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
211                verificationKey = publicKeyRing.getPublicKey(keyId);
212                if (verificationKey != null) {
213                    LOGGER.log(LEVEL, "Found respective public key " + Long.toHexString(keyId));
214                    break;
215                }
216            }
217
218            if (verificationKey == null) {
219                LOGGER.log(Level.INFO, "No public key for signature of " + Long.toHexString(keyId) + " found.");
220
221                if (missingPublicKeyCallback == null) {
222                    LOGGER.log(Level.INFO, "Skip signature of " + Long.toHexString(keyId));
223                    continue;
224                }
225
226                PGPPublicKey missingPublicKey = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId);
227                if (missingPublicKey == null) {
228                    LOGGER.log(Level.INFO, "Skip signature of " + Long.toHexString(keyId));
229                    continue;
230                }
231
232                if (missingPublicKey.getKeyID() != keyId) {
233                    throw new IllegalArgumentException("KeyID of the provided public key differs from the signatures keyId. " +
234                            "The signature was created from " + Long.toHexString(keyId) + " while the provided key has ID " +
235                            Long.toHexString(missingPublicKey.getKeyID()));
236                }
237
238                verificationKey = missingPublicKey;
239            }
240
241            signature.init(verifierBuilderProvider, verificationKey);
242            verifiableOnePassSignatures.put(new OpenPgpV4Fingerprint(verificationKey), signature);
243        }
244    }
245}