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.util.ArrayList; 008import java.util.Collections; 009import java.util.Date; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014import java.util.concurrent.ConcurrentHashMap; 015import javax.annotation.Nonnull; 016import javax.annotation.Nullable; 017 018import org.bouncycastle.openpgp.PGPLiteralData; 019import org.bouncycastle.openpgp.PGPPublicKey; 020import org.bouncycastle.openpgp.PGPPublicKeyRing; 021import org.bouncycastle.openpgp.PGPSignature; 022import org.pgpainless.algorithm.CompressionAlgorithm; 023import org.pgpainless.algorithm.StreamEncoding; 024import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 025import org.pgpainless.exception.SignatureValidationException; 026import org.pgpainless.key.OpenPgpFingerprint; 027import org.pgpainless.key.SubkeyIdentifier; 028import org.pgpainless.util.SessionKey; 029 030public class OpenPgpMetadata { 031 032 private final Set<Long> recipientKeyIds; 033 private final SubkeyIdentifier decryptionKey; 034 private final List<SignatureVerification> verifiedInbandSignatures; 035 private final List<SignatureVerification.Failure> invalidInbandSignatures; 036 private final List<SignatureVerification> verifiedDetachedSignatures; 037 private final List<SignatureVerification.Failure> invalidDetachedSignatures; 038 private final SessionKey sessionKey; 039 private final CompressionAlgorithm compressionAlgorithm; 040 private final String fileName; 041 private final Date modificationDate; 042 private final StreamEncoding fileEncoding; 043 044 public OpenPgpMetadata(Set<Long> recipientKeyIds, 045 SubkeyIdentifier decryptionKey, 046 SessionKey sessionKey, 047 CompressionAlgorithm algorithm, 048 List<SignatureVerification> verifiedInbandSignatures, 049 List<SignatureVerification.Failure> invalidInbandSignatures, 050 List<SignatureVerification> verifiedDetachedSignatures, 051 List<SignatureVerification.Failure> invalidDetachedSignatures, 052 String fileName, 053 Date modificationDate, 054 StreamEncoding fileEncoding) { 055 056 this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds); 057 this.decryptionKey = decryptionKey; 058 this.sessionKey = sessionKey; 059 this.compressionAlgorithm = algorithm; 060 this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures); 061 this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures); 062 this.verifiedDetachedSignatures = Collections.unmodifiableList(verifiedDetachedSignatures); 063 this.invalidDetachedSignatures = Collections.unmodifiableList(invalidDetachedSignatures); 064 this.fileName = fileName; 065 this.modificationDate = modificationDate; 066 this.fileEncoding = fileEncoding; 067 } 068 069 /** 070 * Return a set of key-ids the messages was encrypted for. 071 * 072 * @return recipient ids 073 */ 074 public @Nonnull Set<Long> getRecipientKeyIds() { 075 return recipientKeyIds; 076 } 077 078 /** 079 * Return true, if the message was encrypted. 080 * 081 * @return true if encrypted, false otherwise 082 */ 083 public boolean isEncrypted() { 084 return sessionKey != null && sessionKey.getAlgorithm() != SymmetricKeyAlgorithm.NULL && !getRecipientKeyIds().isEmpty(); 085 } 086 087 /** 088 * Return the {@link SubkeyIdentifier} of the key that was used to decrypt the message. 089 * This can be null if the message was decrypted using a {@link org.pgpainless.util.Passphrase}, or if it was not 090 * encrypted at all (e.g. signed only). 091 * 092 * @return subkey identifier of decryption key 093 */ 094 public @Nullable SubkeyIdentifier getDecryptionKey() { 095 return decryptionKey; 096 } 097 098 /** 099 * Return the algorithm that was used to symmetrically encrypt the message. 100 * 101 * @return encryption algorithm 102 */ 103 public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() { 104 return sessionKey == null ? null : sessionKey.getAlgorithm(); 105 } 106 107 public @Nullable SessionKey getSessionKey() { 108 return sessionKey; 109 } 110 111 /** 112 * Return the {@link CompressionAlgorithm} that was used to compress the message. 113 * 114 * @return compression algorithm 115 */ 116 public @Nullable CompressionAlgorithm getCompressionAlgorithm() { 117 return compressionAlgorithm; 118 } 119 120 /** 121 * Return a set of all signatures on the message. 122 * Note: This method returns just the signatures. There is no guarantee that the signatures are verified or even correct. 123 * 124 * Use {@link #getVerifiedSignatures()} instead to get all verified signatures. 125 * @return unverified and verified signatures 126 */ 127 public @Nonnull Set<PGPSignature> getSignatures() { 128 Set<PGPSignature> signatures = new HashSet<>(); 129 for (SignatureVerification v : getVerifiedDetachedSignatures()) { 130 signatures.add(v.getSignature()); 131 } 132 for (SignatureVerification v : getVerifiedInbandSignatures()) { 133 signatures.add(v.getSignature()); 134 } 135 for (SignatureVerification.Failure f : getInvalidDetachedSignatures()) { 136 signatures.add(f.getSignatureVerification().getSignature()); 137 } 138 for (SignatureVerification.Failure f : getInvalidInbandSignatures()) { 139 signatures.add(f.getSignatureVerification().getSignature()); 140 } 141 return signatures; 142 } 143 144 /** 145 * Return true if the message contained at least one signature. 146 * 147 * Note: This method does not reflect, whether the signature on the message is correct. 148 * Use {@link #isVerified()} instead to determine, if the message carries a verifiable signature. 149 * 150 * @return true if message contains at least one unverified or verified signature, false otherwise. 151 */ 152 public boolean isSigned() { 153 return !getSignatures().isEmpty(); 154 } 155 156 /** 157 * Return a map of all verified signatures on the message. 158 * The map contains verified signatures as value, with the {@link SubkeyIdentifier} of the key that was used to verify 159 * the signature as the maps keys. 160 * 161 * @return verified detached and one-pass signatures 162 */ 163 public Map<SubkeyIdentifier, PGPSignature> getVerifiedSignatures() { 164 Map<SubkeyIdentifier, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>(); 165 for (SignatureVerification detachedSignature : getVerifiedDetachedSignatures()) { 166 verifiedSignatures.put(detachedSignature.getSigningKey(), detachedSignature.getSignature()); 167 } 168 for (SignatureVerification inbandSignatures : verifiedInbandSignatures) { 169 verifiedSignatures.put(inbandSignatures.getSigningKey(), inbandSignatures.getSignature()); 170 } 171 172 return verifiedSignatures; 173 } 174 175 public List<SignatureVerification> getVerifiedInbandSignatures() { 176 return verifiedInbandSignatures; 177 } 178 179 public List<SignatureVerification> getVerifiedDetachedSignatures() { 180 return verifiedDetachedSignatures; 181 } 182 183 public List<SignatureVerification.Failure> getInvalidInbandSignatures() { 184 return invalidInbandSignatures; 185 } 186 187 public List<SignatureVerification.Failure> getInvalidDetachedSignatures() { 188 return invalidDetachedSignatures; 189 } 190 191 /** 192 * Return true, if the message is signed and at least one signature on the message was verified successfully. 193 * 194 * @return true if message is verified, false otherwise 195 */ 196 public boolean isVerified() { 197 return !getVerifiedSignatures().isEmpty(); 198 } 199 200 /** 201 * Return true, if the message contains at least one verified signature made by a key in the 202 * given certificate. 203 * 204 * @param certificate certificate 205 * @return true if message was signed by the certificate (and the signature is valid), false otherwise 206 */ 207 public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing certificate) { 208 for (PGPPublicKey key : certificate) { 209 OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(key); 210 if (containsVerifiedSignatureFrom(fingerprint)) { 211 return true; 212 } 213 } 214 return false; 215 } 216 217 /** 218 * Return true, if the message contains at least one valid signature made by the key with the given 219 * fingerprint, false otherwise. 220 * 221 * The fingerprint might be of the signing subkey, or the primary key of the signing certificate. 222 * 223 * @param fingerprint fingerprint of primary key or signing subkey 224 * @return true if validly signed, false otherwise 225 */ 226 public boolean containsVerifiedSignatureFrom(OpenPgpFingerprint fingerprint) { 227 for (SubkeyIdentifier verifiedSigningKey : getVerifiedSignatures().keySet()) { 228 if (verifiedSigningKey.getPrimaryKeyFingerprint().equals(fingerprint) || 229 verifiedSigningKey.getSubkeyFingerprint().equals(fingerprint)) { 230 return true; 231 } 232 } 233 return false; 234 } 235 236 /** 237 * Return the name of the encrypted / signed file. 238 * 239 * @return file name 240 */ 241 public String getFileName() { 242 return fileName; 243 } 244 245 /** 246 * Return true, if the encrypted data is intended for your eyes only. 247 * 248 * @return true if for-your-eyes-only 249 */ 250 public boolean isForYourEyesOnly() { 251 return PGPLiteralData.CONSOLE.equals(getFileName()); 252 } 253 254 /** 255 * Return the modification date of the encrypted / signed file. 256 * 257 * @return modification date 258 */ 259 public Date getModificationDate() { 260 return modificationDate; 261 } 262 263 /** 264 * Return the encoding format of the encrypted / signed file. 265 * 266 * @return encoding 267 */ 268 public StreamEncoding getFileEncoding() { 269 return fileEncoding; 270 } 271 272 public static Builder getBuilder() { 273 return new Builder(); 274 } 275 276 public static class Builder { 277 278 private final Set<Long> recipientFingerprints = new HashSet<>(); 279 private SessionKey sessionKey; 280 private SubkeyIdentifier decryptionKey; 281 private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; 282 private String fileName; 283 private StreamEncoding fileEncoding; 284 private Date modificationDate; 285 286 private final List<SignatureVerification> verifiedInbandSignatures = new ArrayList<>(); 287 private final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>(); 288 private final List<SignatureVerification.Failure> invalidInbandSignatures = new ArrayList<>(); 289 private final List<SignatureVerification.Failure> invalidDetachedSignatures = new ArrayList<>(); 290 291 292 public Builder addRecipientKeyId(Long keyId) { 293 this.recipientFingerprints.add(keyId); 294 return this; 295 } 296 297 public Builder setDecryptionKey(SubkeyIdentifier decryptionKey) { 298 this.decryptionKey = decryptionKey; 299 return this; 300 } 301 302 public Builder setSessionKey(SessionKey sessionKey) { 303 this.sessionKey = sessionKey; 304 return this; 305 } 306 307 public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) { 308 this.compressionAlgorithm = algorithm; 309 return this; 310 } 311 312 public Builder setFileName(@Nonnull String fileName) { 313 this.fileName = fileName; 314 return this; 315 } 316 317 public Builder setModificationDate(Date modificationDate) { 318 this.modificationDate = modificationDate; 319 return this; 320 } 321 322 public Builder setFileEncoding(StreamEncoding encoding) { 323 this.fileEncoding = encoding; 324 return this; 325 } 326 327 public OpenPgpMetadata build() { 328 return new OpenPgpMetadata( 329 recipientFingerprints, decryptionKey, 330 sessionKey, compressionAlgorithm, 331 verifiedInbandSignatures, invalidInbandSignatures, 332 verifiedDetachedSignatures, invalidDetachedSignatures, 333 fileName, modificationDate, fileEncoding); 334 } 335 336 public void addVerifiedInbandSignature(SignatureVerification signatureVerification) { 337 this.verifiedInbandSignatures.add(signatureVerification); 338 } 339 340 public void addVerifiedDetachedSignature(SignatureVerification signatureVerification) { 341 this.verifiedDetachedSignatures.add(signatureVerification); 342 } 343 344 public void addInvalidInbandSignature(SignatureVerification signatureVerification, SignatureValidationException e) { 345 this.invalidInbandSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); 346 } 347 348 public void addInvalidDetachedSignature(SignatureVerification signatureVerification, SignatureValidationException e) { 349 this.invalidDetachedSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); 350 } 351 } 352}