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}