001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.decryption_verification; 006 007import java.io.IOException; 008import java.io.InputStream; 009import java.util.Collections; 010import java.util.Date; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.List; 014import java.util.Map; 015import java.util.Set; 016import javax.annotation.Nonnull; 017import javax.annotation.Nullable; 018 019import org.bouncycastle.openpgp.PGPException; 020import org.bouncycastle.openpgp.PGPPublicKeyRing; 021import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 022import org.bouncycastle.openpgp.PGPSecretKeyRing; 023import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 024import org.bouncycastle.openpgp.PGPSignature; 025import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy; 026import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; 027import org.pgpainless.key.protection.SecretKeyRingProtector; 028import org.pgpainless.signature.SignatureUtils; 029import org.pgpainless.util.Passphrase; 030import org.pgpainless.util.SessionKey; 031 032/** 033 * Options for decryption and signature verification. 034 */ 035public class ConsumerOptions { 036 037 038 private boolean ignoreMDCErrors = false; 039 040 private Date verifyNotBefore = null; 041 private Date verifyNotAfter = new Date(); 042 043 // Set of verification keys 044 private final Set<PGPPublicKeyRing> certificates = new HashSet<>(); 045 private final Set<PGPSignature> detachedSignatures = new HashSet<>(); 046 private MissingPublicKeyCallback missingCertificateCallback = null; 047 048 // Session key for decryption without passphrase/key 049 private SessionKey sessionKey = null; 050 051 private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>(); 052 private final Set<Passphrase> decryptionPassphrases = new HashSet<>(); 053 private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE; 054 055 private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy(); 056 private boolean cleartextSigned; 057 058 /** 059 * Consider signatures on the message made before the given timestamp invalid. 060 * Null means no limitation. 061 * 062 * @param timestamp timestamp 063 * @return options 064 */ 065 public ConsumerOptions verifyNotBefore(Date timestamp) { 066 this.verifyNotBefore = timestamp; 067 return this; 068 } 069 070 /** 071 * Return the earliest creation date on which signatures on the message are considered valid. 072 * Signatures made earlier than this date are considered invalid. 073 * 074 * @return earliest allowed signature creation date or null 075 */ 076 public @Nullable Date getVerifyNotBefore() { 077 return verifyNotBefore; 078 } 079 080 /** 081 * Consider signatures on the message made after the given timestamp invalid. 082 * Null means no limitation. 083 * 084 * @param timestamp timestamp 085 * @return options 086 */ 087 public ConsumerOptions verifyNotAfter(Date timestamp) { 088 this.verifyNotAfter = timestamp; 089 return this; 090 } 091 092 /** 093 * Return the latest possible creation date on which signatures made on the message are considered valid. 094 * Signatures made later than this date are considered invalid. 095 * 096 * @return Latest possible creation date or null. 097 */ 098 public Date getVerifyNotAfter() { 099 return verifyNotAfter; 100 } 101 102 /** 103 * Add a certificate (public key ring) for signature verification. 104 * 105 * @param verificationCert certificate for signature verification 106 * @return options 107 */ 108 public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) { 109 this.certificates.add(verificationCert); 110 return this; 111 } 112 113 /** 114 * Add a set of certificates (public key rings) for signature verification. 115 * 116 * @param verificationCerts certificates for signature verification 117 * @return options 118 */ 119 public ConsumerOptions addVerificationCerts(PGPPublicKeyRingCollection verificationCerts) { 120 for (PGPPublicKeyRing certificate : verificationCerts) { 121 addVerificationCert(certificate); 122 } 123 return this; 124 } 125 126 public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException { 127 List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream); 128 return addVerificationOfDetachedSignatures(signatures); 129 } 130 131 public ConsumerOptions addVerificationOfDetachedSignatures(List<PGPSignature> detachedSignatures) { 132 for (PGPSignature signature : detachedSignatures) { 133 addVerificationOfDetachedSignature(signature); 134 } 135 return this; 136 } 137 138 /** 139 * Add a detached signature for the signature verification process. 140 * 141 * @param detachedSignature detached signature 142 * @return options 143 */ 144 public ConsumerOptions addVerificationOfDetachedSignature(PGPSignature detachedSignature) { 145 detachedSignatures.add(detachedSignature); 146 return this; 147 } 148 149 /** 150 * Set a callback that's used when a certificate (public key) is missing for signature verification. 151 * 152 * @param callback callback 153 * @return options 154 */ 155 public ConsumerOptions setMissingCertificateCallback(MissingPublicKeyCallback callback) { 156 this.missingCertificateCallback = callback; 157 return this; 158 } 159 160 161 /** 162 * Attempt decryption using a session key. 163 * 164 * Note: PGPainless does not yet support decryption with session keys. 165 * 166 * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a> 167 * 168 * @param sessionKey session key 169 * @return options 170 */ 171 public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) { 172 this.sessionKey = sessionKey; 173 return this; 174 } 175 176 /** 177 * Return the session key. 178 * 179 * @return session key or null 180 */ 181 public @Nullable SessionKey getSessionKey() { 182 return sessionKey; 183 } 184 185 /** 186 * Add a key for message decryption. 187 * The key is expected to be unencrypted. 188 * 189 * @param key unencrypted key 190 * @return options 191 */ 192 public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key) { 193 return addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys()); 194 } 195 196 /** 197 * Add a key for message decryption. If the key is encrypted, the {@link SecretKeyRingProtector} is used to decrypt it 198 * when needed. 199 * 200 * @param key key 201 * @param keyRingProtector protector for the secret key 202 * @return options 203 */ 204 public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key, @Nonnull SecretKeyRingProtector keyRingProtector) { 205 decryptionKeys.put(key, keyRingProtector); 206 return this; 207 } 208 209 /** 210 * Add the keys in the provided key collection for message decryption. 211 * 212 * @param keys key collection 213 * @param keyRingProtector protector for encrypted secret keys 214 * @return options 215 */ 216 public ConsumerOptions addDecryptionKeys(@Nonnull PGPSecretKeyRingCollection keys, @Nonnull SecretKeyRingProtector keyRingProtector) { 217 for (PGPSecretKeyRing key : keys) { 218 addDecryptionKey(key, keyRingProtector); 219 } 220 return this; 221 } 222 223 /** 224 * Add a passphrase for message decryption. 225 * This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase. 226 * 227 * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.7">Symmetrically Encrypted Data Packet</a> 228 * 229 * @param passphrase passphrase 230 * @return options 231 */ 232 public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) { 233 decryptionPassphrases.add(passphrase); 234 return this; 235 } 236 237 public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() { 238 return Collections.unmodifiableSet(decryptionKeys.keySet()); 239 } 240 241 public @Nonnull Set<Passphrase> getDecryptionPassphrases() { 242 return Collections.unmodifiableSet(decryptionPassphrases); 243 } 244 245 public @Nonnull Set<PGPPublicKeyRing> getCertificates() { 246 return Collections.unmodifiableSet(certificates); 247 } 248 249 public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() { 250 return missingCertificateCallback; 251 } 252 253 public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) { 254 return decryptionKeys.get(decryptionKeyRing); 255 } 256 257 public @Nonnull Set<PGPSignature> getDetachedSignatures() { 258 return Collections.unmodifiableSet(detachedSignatures); 259 } 260 261 /** 262 * By default, PGPainless will require encrypted messages to make use of SEIP data packets. 263 * Those are Symmetrically Encrypted Integrity Protected Data packets. 264 * Symmetrically Encrypted Data Packets without integrity protection are rejected by default. 265 * Furthermore, PGPainless will throw an exception if verification of the MDC error detection code of the SEIP packet 266 * fails. 267 * 268 * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an attack or data corruption. 269 * 270 * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data without integrity protection. 271 * If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will 272 * <ul> 273 * <li>not throw exceptions for SEIP packets with tampered ciphertext</li> 274 * <li>not throw exceptions for SEIP packets with tampered MDC</li> 275 * <li>not throw exceptions for MDCs with bad CTB</li> 276 * <li>not throw exceptions for MDCs with bad length</li> 277 * </ul> 278 * 279 * It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC 280 * 281 * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.13">Sym. Encrypted Integrity Protected Data Packet</a> 282 * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. 283 * @return options 284 */ 285 @Deprecated 286 public ConsumerOptions setIgnoreMDCErrors(boolean ignoreMDCErrors) { 287 this.ignoreMDCErrors = ignoreMDCErrors; 288 return this; 289 } 290 291 /** 292 * Return true, if PGPainless is ignoring MDC errors. 293 * 294 * @return ignore mdc errors 295 */ 296 boolean isIgnoreMDCErrors() { 297 return ignoreMDCErrors; 298 } 299 300 /** 301 * Specify the {@link MissingKeyPassphraseStrategy}. 302 * This strategy defines, how missing passphrases for unlocking secret keys are handled. 303 * In interactive mode ({@link MissingKeyPassphraseStrategy#INTERACTIVE}) PGPainless will try to obtain missing 304 * passphrases for secret keys via the {@link SecretKeyRingProtector SecretKeyRingProtectors} 305 * {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider} callback. 306 * 307 * In non-interactice mode ({@link MissingKeyPassphraseStrategy#THROW_EXCEPTION}, PGPainless will instead 308 * throw a {@link org.pgpainless.exception.MissingPassphraseException} containing the ids of all keys for which 309 * there are missing passphrases. 310 * 311 * @param strategy strategy 312 * @return options 313 */ 314 public ConsumerOptions setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy strategy) { 315 this.missingKeyPassphraseStrategy = strategy; 316 return this; 317 } 318 319 /** 320 * Return the currently configured {@link MissingKeyPassphraseStrategy}. 321 * 322 * @return missing key passphrase strategy 323 */ 324 MissingKeyPassphraseStrategy getMissingKeyPassphraseStrategy() { 325 return missingKeyPassphraseStrategy; 326 } 327 328 /** 329 * Set a custom multi-pass strategy for processing cleartext-signed messages. 330 * Uses {@link InMemoryMultiPassStrategy} by default. 331 * 332 * @param multiPassStrategy multi-pass caching strategy 333 * @return builder 334 */ 335 public ConsumerOptions setMultiPassStrategy(@Nonnull MultiPassStrategy multiPassStrategy) { 336 this.multiPassStrategy = multiPassStrategy; 337 return this; 338 } 339 340 /** 341 * Return the currently configured {@link MultiPassStrategy}. 342 * Defaults to {@link InMemoryMultiPassStrategy}. 343 * 344 * @return multi-pass strategy 345 */ 346 public MultiPassStrategy getMultiPassStrategy() { 347 return multiPassStrategy; 348 } 349 350 /** 351 * INTERNAL method to mark cleartext signed messages. 352 * Do not call this manually. 353 */ 354 public void setIsCleartextSigned() { 355 this.cleartextSigned = true; 356 } 357 358 /** 359 * Return true if the message is cleartext signed. 360 * @return cleartext signed 361 */ 362 public boolean isCleartextSigned() { 363 return this.cleartextSigned; 364 } 365}