001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.signature.consumer; 006 007import java.util.Collections; 008import java.util.Date; 009import java.util.Iterator; 010import java.util.List; 011 012import org.bouncycastle.openpgp.PGPKeyRing; 013import org.bouncycastle.openpgp.PGPPublicKey; 014import org.bouncycastle.openpgp.PGPSignature; 015import org.pgpainless.algorithm.SignatureType; 016import org.pgpainless.exception.SignatureValidationException; 017import org.pgpainless.policy.Policy; 018import org.pgpainless.signature.SignatureUtils; 019import org.pgpainless.util.CollectionUtils; 020 021/** 022 * Pick signatures from keys. 023 * 024 * The format of a V4 OpenPGP key is: 025 * 026 * Primary-Key 027 * [Revocation Self Signature] 028 * [Direct Key Signature...] 029 * User ID [Signature ...] 030 * [User ID [Signature ...] ...] 031 * [User Attribute [Signature ...] ...] 032 * [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] 033 */ 034public final class SignaturePicker { 035 036 private SignaturePicker() { 037 038 } 039 040 /** 041 * Pick the at validation date most recent valid key revocation signature. 042 * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after 043 * validationDate or if it is already expired. 044 * 045 * @param keyRing key ring 046 * @return most recent, valid key revocation signature 047 */ 048 public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { 049 PGPPublicKey primaryKey = keyRing.getPublicKey(); 050 051 List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION); 052 PGPSignature mostCurrentValidSig = null; 053 054 for (PGPSignature signature : signatures) { 055 try { 056 SignatureVerifier.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate); 057 } catch (SignatureValidationException e) { 058 // Signature is not valid 059 continue; 060 } 061 mostCurrentValidSig = signature; 062 } 063 064 return mostCurrentValidSig; 065 } 066 067 /** 068 * Pick the at validationDate most recent, valid direct key signature. 069 * This method might return null, if there is no direct key self-signature which is valid at validationDate. 070 * 071 * @param keyRing key ring 072 * @param validationDate validation date 073 * @return direct-key self-signature 074 */ 075 public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { 076 PGPPublicKey primaryKey = keyRing.getPublicKey(); 077 return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate); 078 } 079 080 /** 081 * Pick the at validationDate, latest, valid direct key signature made by signingKey on signedKey. 082 * This method might return null, if there is no direct key self signature which is valid at validationDate. 083 * 084 * @param signingKey key that created the signature 085 * @param signedKey key that carries the signature 086 * @param validationDate validation date 087 * @return direct key sig 088 */ 089 public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) { 090 List<PGPSignature> directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); 091 092 PGPSignature mostRecentDirectKeySigBySigningKey = null; 093 for (PGPSignature signature : directKeySignatures) { 094 try { 095 SignatureVerifier.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate); 096 } catch (SignatureValidationException e) { 097 // Direct key sig is not valid 098 continue; 099 } 100 mostRecentDirectKeySigBySigningKey = signature; 101 } 102 103 return mostRecentDirectKeySigBySigningKey; 104 } 105 106 /** 107 * Pick the at validationDate latest direct key signature. 108 * This method might return an expired signature. 109 * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired 110 * yet already effective direct-key signature will be returned. 111 * 112 * @param keyRing key ring 113 * @param validationDate validation date 114 * @return latest direct key signature 115 */ 116 public static PGPSignature pickLatestDirectKeySignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { 117 PGPPublicKey primaryKey = keyRing.getPublicKey(); 118 return pickLatestDirectKeySignature(primaryKey, primaryKey, policy, validationDate); 119 } 120 121 /** 122 * Pick the at validationDate latest direct key signature made by signingKey on signedKey. 123 * This method might return an expired signature. 124 * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key 125 * signature will be returned. 126 * 127 * @param signingKey signing key (key that made the sig) 128 * @param signedKey signed key (key that carries the sig) 129 * @param validationDate date of validation 130 * @return latest direct key sig 131 */ 132 public static PGPSignature pickLatestDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) { 133 List<PGPSignature> signatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); 134 135 PGPSignature latestDirectKeySignature = null; 136 for (PGPSignature signature : signatures) { 137 try { 138 SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature); 139 SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); 140 SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); 141 // if the currently latest signature is not yet expired, check if the next candidate is not yet expired 142 if (latestDirectKeySignature != null && !SignatureUtils.isSignatureExpired(latestDirectKeySignature, validationDate)) { 143 SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); 144 } 145 SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature); 146 } catch (SignatureValidationException e) { 147 // Direct key signature is not valid 148 continue; 149 } 150 latestDirectKeySignature = signature; 151 } 152 153 return latestDirectKeySignature; 154 } 155 156 /** 157 * Pick the at validationDate most recent, valid user-id revocation signature. 158 * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after 159 * validationDate or if it is already expired. 160 * 161 * @param keyRing key ring 162 * @param userId user-Id that gets revoked 163 * @param validationDate validation date 164 * @return revocation signature 165 */ 166 public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { 167 PGPPublicKey primaryKey = keyRing.getPublicKey(); 168 List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION); 169 170 PGPSignature latestUserIdRevocation = null; 171 for (PGPSignature signature : signatures) { 172 try { 173 SignatureVerifier.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate); 174 } catch (SignatureValidationException e) { 175 // User-id revocation is not valid 176 continue; 177 } 178 latestUserIdRevocation = signature; 179 } 180 181 return latestUserIdRevocation; 182 } 183 184 /** 185 * Pick the at validationDate latest, valid certification self-signature for the given user-id. 186 * This method might return null, if there is no certification self signature for that user-id which is valid 187 * at validationDate. 188 * 189 * @param keyRing keyring 190 * @param userId userid 191 * @param validationDate validation date 192 * @return user-id certification 193 */ 194 public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { 195 PGPPublicKey primaryKey = keyRing.getPublicKey(); 196 197 Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId); 198 List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator); 199 Collections.sort(signatures, new SignatureCreationDateComparator()); 200 201 PGPSignature mostRecentUserIdCertification = null; 202 for (PGPSignature signature : signatures) { 203 try { 204 SignatureVerifier.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate); 205 } catch (SignatureValidationException e) { 206 // User-id certification is not valid 207 continue; 208 } 209 mostRecentUserIdCertification = signature; 210 } 211 212 return mostRecentUserIdCertification; 213 } 214 215 /** 216 * Pick the at validationDate latest certification self-signature for the given user-id. 217 * This method might return an expired signature. 218 * If a non-expired user-id certification signature exists, the latest non-expired yet already effective 219 * user-id certification signature for the given user-id will be returned. 220 * 221 * @param keyRing keyring 222 * @param userId userid 223 * @param validationDate validation date 224 * @return user-id certification 225 */ 226 public static PGPSignature pickLatestUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { 227 PGPPublicKey primaryKey = keyRing.getPublicKey(); 228 229 Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId); 230 List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator); 231 Collections.sort(signatures, new SignatureCreationDateComparator()); 232 233 PGPSignature latestUserIdCert = null; 234 for (PGPSignature signature : signatures) { 235 try { 236 SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(signature); 237 SignatureValidator.signatureIsCertification().verify(signature); 238 SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); 239 SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); 240 SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature); 241 } catch (SignatureValidationException e) { 242 // User-id certification is not valid 243 continue; 244 } 245 246 latestUserIdCert = signature; 247 } 248 249 return latestUserIdCert; 250 } 251 252 /** 253 * Pick the at validationDate most recent, valid subkey revocation signature. 254 * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after 255 * validationDate or if it is already expired. 256 * 257 * @param keyRing keyring 258 * @param subkey subkey 259 * @param validationDate validation date 260 * @return subkey revocation signature 261 */ 262 public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { 263 PGPPublicKey primaryKey = keyRing.getPublicKey(); 264 if (primaryKey.getKeyID() == subkey.getKeyID()) { 265 throw new IllegalArgumentException("Primary key cannot have subkey binding revocations."); 266 } 267 268 List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION); 269 PGPSignature latestSubkeyRevocation = null; 270 271 for (PGPSignature signature : signatures) { 272 try { 273 SignatureVerifier.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate); 274 } catch (SignatureValidationException e) { 275 // subkey binding revocation is not valid 276 continue; 277 } 278 latestSubkeyRevocation = signature; 279 } 280 281 return latestSubkeyRevocation; 282 } 283 284 /** 285 * Pick the at validationDate latest, valid subkey binding signature for the given subkey. 286 * This method might return null, if there is no subkey binding signature which is valid 287 * at validationDate. 288 * 289 * @param keyRing key ring 290 * @param subkey subkey 291 * @param validationDate date of validation 292 * @return most recent valid subkey binding signature 293 */ 294 public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { 295 PGPPublicKey primaryKey = keyRing.getPublicKey(); 296 if (primaryKey.getKeyID() == subkey.getKeyID()) { 297 throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); 298 } 299 300 List<PGPSignature> subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); 301 PGPSignature mostCurrentValidSig = null; 302 303 for (PGPSignature signature : subkeyBindingSigs) { 304 try { 305 SignatureVerifier.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate); 306 } catch (SignatureValidationException validationException) { 307 // Subkey binding sig is not valid 308 continue; 309 } 310 mostCurrentValidSig = signature; 311 } 312 313 return mostCurrentValidSig; 314 } 315 316 /** 317 * Pick the at validationDate latest subkey binding signature for the given subkey. 318 * This method might return an expired signature. 319 * If a non-expired subkey binding signature exists, the latest non-expired yet already effective 320 * subkey binding signature for the given subkey will be returned. 321 * 322 * @param keyRing key ring 323 * @param subkey subkey 324 * @param validationDate validationDate 325 * @return subkey binding signature 326 */ 327 public static PGPSignature pickLatestSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { 328 PGPPublicKey primaryKey = keyRing.getPublicKey(); 329 if (primaryKey.getKeyID() == subkey.getKeyID()) { 330 throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); 331 } 332 333 List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); 334 PGPSignature latestSubkeyBinding = null; 335 336 for (PGPSignature signature : signatures) { 337 try { 338 SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature); 339 SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); 340 SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); 341 // if the currently latest signature is not yet expired, check if the next candidate is not yet expired 342 if (latestSubkeyBinding != null && !SignatureUtils.isSignatureExpired(latestSubkeyBinding, validationDate)) { 343 SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); 344 } 345 SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature); 346 } catch (SignatureValidationException e) { 347 // Subkey binding sig is not valid 348 continue; 349 } 350 latestSubkeyBinding = signature; 351 } 352 353 return latestSubkeyBinding; 354 } 355 356 /** 357 * Return a list of all signatures of the given {@link SignatureType} on the given key, sorted using a 358 * {@link SignatureCreationDateComparator}. 359 * 360 * The returned list will be sorted first by ascending signature creation time. 361 * 362 * @param key key 363 * @param type type of signatures which shall be collected and sorted 364 * @return sorted list of signatures 365 */ 366 private static List<PGPSignature> getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) { 367 Iterator<PGPSignature> signaturesOfType = key.getSignaturesOfType(type.getCode()); 368 List<PGPSignature> signatureList = CollectionUtils.iteratorToList(signaturesOfType); 369 Collections.sort(signatureList, new SignatureCreationDateComparator()); 370 return signatureList; 371 } 372}