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}