001// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.key.modification.secretkeyring; 006 007import static org.pgpainless.util.CollectionUtils.concat; 008 009import java.io.IOException; 010import java.security.InvalidAlgorithmParameterException; 011import java.security.NoSuchAlgorithmException; 012import java.util.ArrayList; 013import java.util.Collections; 014import java.util.Date; 015import java.util.Iterator; 016import java.util.List; 017import java.util.Map; 018import java.util.NoSuchElementException; 019import java.util.Set; 020import javax.annotation.Nonnull; 021import javax.annotation.Nullable; 022 023import org.bouncycastle.bcpg.S2K; 024import org.bouncycastle.bcpg.SecretKeyPacket; 025import org.bouncycastle.bcpg.sig.KeyExpirationTime; 026import org.bouncycastle.openpgp.PGPException; 027import org.bouncycastle.openpgp.PGPKeyPair; 028import org.bouncycastle.openpgp.PGPKeyRingGenerator; 029import org.bouncycastle.openpgp.PGPPublicKey; 030import org.bouncycastle.openpgp.PGPSecretKey; 031import org.bouncycastle.openpgp.PGPSecretKeyRing; 032import org.bouncycastle.openpgp.PGPSignature; 033import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; 034import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; 035import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; 036import org.pgpainless.PGPainless; 037import org.pgpainless.algorithm.AlgorithmSuite; 038import org.pgpainless.algorithm.CompressionAlgorithm; 039import org.pgpainless.algorithm.Feature; 040import org.pgpainless.algorithm.HashAlgorithm; 041import org.pgpainless.algorithm.KeyFlag; 042import org.pgpainless.algorithm.PublicKeyAlgorithm; 043import org.pgpainless.algorithm.SignatureType; 044import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 045import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; 046import org.pgpainless.implementation.ImplementationFactory; 047import org.pgpainless.key.generation.KeyRingBuilder; 048import org.pgpainless.key.generation.KeySpec; 049import org.pgpainless.key.info.KeyRingInfo; 050import org.pgpainless.key.protection.CachingSecretKeyRingProtector; 051import org.pgpainless.key.protection.KeyRingProtectionSettings; 052import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; 053import org.pgpainless.key.protection.SecretKeyRingProtector; 054import org.pgpainless.key.protection.UnprotectedKeysProtector; 055import org.pgpainless.key.protection.fixes.S2KUsageFix; 056import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; 057import org.pgpainless.key.util.KeyRingUtils; 058import org.pgpainless.key.util.RevocationAttributes; 059import org.pgpainless.signature.builder.DirectKeySignatureBuilder; 060import org.pgpainless.signature.builder.RevocationSignatureBuilder; 061import org.pgpainless.signature.builder.SelfSignatureBuilder; 062import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; 063import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; 064import org.pgpainless.signature.subpackets.SignatureSubpackets; 065import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; 066import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; 067import org.pgpainless.util.BCUtil; 068import org.pgpainless.util.CollectionUtils; 069import org.pgpainless.util.Passphrase; 070import org.pgpainless.util.selection.userid.SelectUserId; 071 072public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { 073 074 private PGPSecretKeyRing secretKeyRing; 075 076 public SecretKeyRingEditor(PGPSecretKeyRing secretKeyRing) { 077 if (secretKeyRing == null) { 078 throw new NullPointerException("SecretKeyRing MUST NOT be null."); 079 } 080 this.secretKeyRing = secretKeyRing; 081 } 082 083 @Override 084 public SecretKeyRingEditorInterface addUserId( 085 @Nonnull CharSequence userId, 086 @Nonnull SecretKeyRingProtector secretKeyRingProtector) 087 throws PGPException { 088 return addUserId(userId, null, secretKeyRingProtector); 089 } 090 091 @Override 092 public SecretKeyRingEditorInterface addUserId( 093 @Nonnull CharSequence userId, 094 @Nullable SelfSignatureSubpackets.Callback signatureSubpacketCallback, 095 @Nonnull SecretKeyRingProtector protector) 096 throws PGPException { 097 String sanitizeUserId = sanitizeUserId(userId); 098 099 // user-id certifications live on the primary key 100 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 101 102 // retain key flags from previous signature 103 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 104 if (info.isHardRevoked(userId.toString())) { 105 throw new IllegalArgumentException("User-ID " + userId + " is hard revoked and cannot be re-certified."); 106 } 107 List<KeyFlag> keyFlags = info.getKeyFlagsOf(info.getKeyId()); 108 109 Set<HashAlgorithm> hashAlgorithmPreferences; 110 Set<SymmetricKeyAlgorithm> symmetricKeyAlgorithmPreferences; 111 Set<CompressionAlgorithm> compressionAlgorithmPreferences; 112 try { 113 hashAlgorithmPreferences = info.getPreferredHashAlgorithms(); 114 symmetricKeyAlgorithmPreferences = info.getPreferredSymmetricKeyAlgorithms(); 115 compressionAlgorithmPreferences = info.getPreferredCompressionAlgorithms(); 116 } catch (IllegalStateException e) { 117 // missing user-id sig 118 AlgorithmSuite algorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite(); 119 hashAlgorithmPreferences = algorithmSuite.getHashAlgorithms(); 120 symmetricKeyAlgorithmPreferences = algorithmSuite.getSymmetricKeyAlgorithms(); 121 compressionAlgorithmPreferences = algorithmSuite.getCompressionAlgorithms(); 122 } 123 124 SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, protector); 125 builder.setSignatureType(SignatureType.POSITIVE_CERTIFICATION); 126 127 // Retain signature subpackets of previous signatures 128 builder.getHashedSubpackets().setKeyFlags(keyFlags); 129 builder.getHashedSubpackets().setPreferredHashAlgorithms(hashAlgorithmPreferences); 130 builder.getHashedSubpackets().setPreferredSymmetricKeyAlgorithms(symmetricKeyAlgorithmPreferences); 131 builder.getHashedSubpackets().setPreferredCompressionAlgorithms(compressionAlgorithmPreferences); 132 builder.getHashedSubpackets().setFeatures(Feature.MODIFICATION_DETECTION); 133 134 builder.applyCallback(signatureSubpacketCallback); 135 136 PGPSignature signature = builder.build(primaryKey.getPublicKey(), sanitizeUserId); 137 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, sanitizeUserId, signature); 138 139 return this; 140 } 141 142 @Override 143 public SecretKeyRingEditorInterface addPrimaryUserId( 144 @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector protector) 145 throws PGPException { 146 147 // Determine previous key expiration date 148 PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); 149 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 150 String primaryUserId = info.getPrimaryUserId(); 151 PGPSignature signature = primaryUserId == null ? 152 info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId); 153 final Date previousKeyExpiration = signature == null ? null : 154 SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey); 155 156 // Add new primary user-id signature 157 addUserId( 158 userId, 159 new SelfSignatureSubpackets.Callback() { 160 @Override 161 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 162 hashedSubpackets.setPrimaryUserId(); 163 if (previousKeyExpiration != null) { 164 hashedSubpackets.setKeyExpirationTime(primaryKey, previousKeyExpiration); 165 } else { 166 hashedSubpackets.setKeyExpirationTime(null); 167 } 168 } 169 }, 170 protector); 171 172 // unmark previous primary user-ids to be non-primary 173 info = PGPainless.inspectKeyRing(secretKeyRing); 174 for (String otherUserId : info.getValidAndExpiredUserIds()) { 175 if (userId.toString().equals(otherUserId)) { 176 continue; 177 } 178 179 // We need to unmark this user-id as primary 180 if (info.getLatestUserIdCertification(otherUserId).getHashedSubPackets().isPrimaryUserID()) { 181 addUserId(otherUserId, new SelfSignatureSubpackets.Callback() { 182 @Override 183 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 184 hashedSubpackets.setPrimaryUserId(null); 185 hashedSubpackets.setKeyExpirationTime(null); // non-primary 186 } 187 }, protector); 188 } 189 } 190 return this; 191 } 192 193 @Override 194 public SecretKeyRingEditorInterface removeUserId( 195 SelectUserId userIdSelector, 196 SecretKeyRingProtector protector) 197 throws PGPException { 198 RevocationAttributes revocationAttributes = RevocationAttributes.createCertificateRevocation() 199 .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) 200 .withoutDescription(); 201 return revokeUserIds(userIdSelector, 202 protector, 203 revocationAttributes); 204 } 205 206 @Override 207 public SecretKeyRingEditorInterface removeUserId( 208 CharSequence userId, 209 SecretKeyRingProtector protector) throws PGPException { 210 return removeUserId( 211 SelectUserId.exactMatch(userId.toString()), 212 protector); 213 } 214 215 // TODO: Move to utility class? 216 private String sanitizeUserId(@Nonnull CharSequence userId) { 217 // TODO: Further research how to sanitize user IDs. 218 // eg. what about newlines? 219 return userId.toString().trim(); 220 } 221 222 @Override 223 public SecretKeyRingEditorInterface addSubKey( 224 @Nonnull KeySpec keySpec, 225 @Nonnull Passphrase subKeyPassphrase, 226 @Nonnull SecretKeyRingProtector secretKeyRingProtector) 227 throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { 228 PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); 229 230 SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector 231 .forKeyId(keyPair.getKeyID(), subKeyPassphrase); 232 233 SelfSignatureSubpackets.Callback callback = new SelfSignatureSubpackets.Callback() { 234 @Override 235 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 236 SignatureSubpacketsHelper.applyFrom(keySpec.getSubpackets(), (SignatureSubpackets) hashedSubpackets); 237 } 238 }; 239 240 List<KeyFlag> keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); 241 KeyFlag firstFlag = keyFlags.remove(0); 242 KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); 243 244 return addSubKey(keyPair, callback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); 245 } 246 247 @Override 248 public SecretKeyRingEditorInterface addSubKey( 249 @Nonnull KeySpec keySpec, 250 @Nullable Passphrase subkeyPassphrase, 251 @Nullable SelfSignatureSubpackets.Callback subpacketsCallback, 252 @Nonnull SecretKeyRingProtector secretKeyRingProtector) 253 throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { 254 PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); 255 256 SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector 257 .forKeyId(keyPair.getKeyID(), subkeyPassphrase); 258 259 List<KeyFlag> keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); 260 KeyFlag firstFlag = keyFlags.remove(0); 261 KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); 262 263 return addSubKey(keyPair, subpacketsCallback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); 264 } 265 266 @Override 267 public SecretKeyRingEditorInterface addSubKey( 268 @Nonnull PGPKeyPair subkey, 269 @Nullable SelfSignatureSubpackets.Callback bindingSignatureCallback, 270 @Nonnull SecretKeyRingProtector subkeyProtector, 271 @Nonnull SecretKeyRingProtector primaryKeyProtector, 272 @Nonnull KeyFlag keyFlag, 273 KeyFlag... additionalKeyFlags) 274 throws PGPException, IOException, NoSuchAlgorithmException { 275 KeyFlag[] flags = concat(keyFlag, additionalKeyFlags); 276 PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm()); 277 SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm); 278 279 // check key against public key algorithm policy 280 PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm()); 281 int bitStrength = BCUtil.getBitStrength(subkey.getPublicKey()); 282 if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { 283 throw new IllegalArgumentException("Public key algorithm policy violation: " + 284 publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); 285 } 286 287 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 288 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 289 PublicKeyAlgorithm signingKeyAlgorithm = PublicKeyAlgorithm.fromId(primaryKey.getPublicKey().getAlgorithm()); 290 HashAlgorithm hashAlgorithm = HashAlgorithmNegotiator 291 .negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) 292 .negotiateHashAlgorithm(info.getPreferredHashAlgorithms()); 293 294 // While we'd like to rely on our own BindingSignatureBuilder implementation, 295 // unfortunately we have to use BCs PGPKeyRingGenerator class since there is no public constructor 296 // for subkeys. See https://github.com/bcgit/bc-java/pull/1063 297 PGPKeyRingGenerator ringGenerator = new PGPKeyRingGenerator( 298 secretKeyRing, 299 primaryKeyProtector.getDecryptor(primaryKey.getKeyID()), 300 ImplementationFactory.getInstance().getV4FingerprintCalculator(), 301 ImplementationFactory.getInstance().getPGPContentSignerBuilder( 302 signingKeyAlgorithm, hashAlgorithm), 303 subkeyProtector.getEncryptor(subkey.getKeyID())); 304 305 SelfSignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(primaryKey.getPublicKey()); 306 SelfSignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets(); 307 hashedSubpackets.setKeyFlags(flags); 308 309 if (bindingSignatureCallback != null) { 310 bindingSignatureCallback.modifyHashedSubpackets(hashedSubpackets); 311 bindingSignatureCallback.modifyUnhashedSubpackets(unhashedSubpackets); 312 } 313 314 boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) || 315 CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER); 316 PGPContentSignerBuilder primaryKeyBindingSigner = null; 317 if (isSigningKey) { 318 primaryKeyBindingSigner = ImplementationFactory.getInstance().getPGPContentSignerBuilder(subkeyAlgorithm, hashAlgorithm); 319 } 320 321 ringGenerator.addSubKey(subkey, 322 SignatureSubpacketsHelper.toVector((SignatureSubpackets) hashedSubpackets), 323 SignatureSubpacketsHelper.toVector((SignatureSubpackets) unhashedSubpackets), 324 primaryKeyBindingSigner); 325 326 secretKeyRing = ringGenerator.generateSecretKeyRing(); 327 328 return this; 329 } 330 331 @Override 332 public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, 333 @Nullable RevocationAttributes revocationAttributes) 334 throws PGPException { 335 RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); 336 return revoke(secretKeyRingProtector, callback); 337 } 338 339 @Override 340 public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, 341 @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) 342 throws PGPException { 343 return revokeSubKey(secretKeyRing.getSecretKey().getKeyID(), secretKeyRingProtector, subpacketsCallback); 344 } 345 346 @Override 347 public SecretKeyRingEditorInterface revokeSubKey(long subKeyId, 348 SecretKeyRingProtector protector, 349 RevocationAttributes revocationAttributes) 350 throws PGPException { 351 RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); 352 return revokeSubKey(subKeyId, protector, callback); 353 } 354 355 @Override 356 public SecretKeyRingEditorInterface revokeSubKey(long keyID, 357 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 358 @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) 359 throws PGPException { 360 // retrieve subkey to be revoked 361 PGPPublicKey revokeeSubKey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, keyID); 362 // create revocation 363 PGPSignature subKeyRevocation = generateRevocation(secretKeyRingProtector, revokeeSubKey, 364 subpacketsCallback); 365 // inject revocation sig into key ring 366 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, revokeeSubKey, subKeyRevocation); 367 return this; 368 } 369 370 @Override 371 public PGPSignature createRevocationCertificate(@Nonnull SecretKeyRingProtector secretKeyRingProtector, 372 @Nullable RevocationAttributes revocationAttributes) 373 throws PGPException { 374 PGPPublicKey revokeeSubKey = secretKeyRing.getPublicKey(); 375 PGPSignature revocationCertificate = generateRevocation( 376 secretKeyRingProtector, revokeeSubKey, callbackFromRevocationAttributes(revocationAttributes)); 377 return revocationCertificate; 378 } 379 380 @Override 381 public PGPSignature createRevocationCertificate( 382 long subkeyId, 383 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 384 @Nullable RevocationAttributes revocationAttributes) 385 throws PGPException { 386 PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); 387 RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); 388 return generateRevocation(secretKeyRingProtector, revokeeSubkey, callback); 389 } 390 391 @Override 392 public PGPSignature createRevocationCertificate( 393 long subkeyId, 394 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 395 @Nullable RevocationSignatureSubpackets.Callback certificateSubpacketsCallback) 396 throws PGPException { 397 PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); 398 return generateRevocation(secretKeyRingProtector, revokeeSubkey, certificateSubpacketsCallback); 399 } 400 401 private PGPSignature generateRevocation(@Nonnull SecretKeyRingProtector protector, 402 @Nonnull PGPPublicKey revokeeSubKey, 403 @Nullable RevocationSignatureSubpackets.Callback callback) 404 throws PGPException { 405 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 406 SignatureType signatureType = revokeeSubKey.isMasterKey() ? 407 SignatureType.KEY_REVOCATION : SignatureType.SUBKEY_REVOCATION; 408 409 RevocationSignatureBuilder signatureBuilder = 410 new RevocationSignatureBuilder(signatureType, primaryKey, protector); 411 signatureBuilder.applyCallback(callback); 412 PGPSignature revocation = signatureBuilder.build(revokeeSubKey); 413 return revocation; 414 } 415 416 private static RevocationSignatureSubpackets.Callback callbackFromRevocationAttributes( 417 @Nullable RevocationAttributes attributes) { 418 return new RevocationSignatureSubpackets.Callback() { 419 @Override 420 public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { 421 if (attributes != null) { 422 hashedSubpackets.setRevocationReason(attributes); 423 } 424 } 425 }; 426 } 427 428 @Override 429 public SecretKeyRingEditorInterface revokeUserId( 430 @Nonnull CharSequence userId, 431 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 432 @Nullable RevocationAttributes revocationAttributes) 433 throws PGPException { 434 if (revocationAttributes != null) { 435 RevocationAttributes.Reason reason = revocationAttributes.getReason(); 436 if (reason != RevocationAttributes.Reason.NO_REASON 437 && reason != RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { 438 throw new IllegalArgumentException("Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID"); 439 } 440 } 441 442 RevocationSignatureSubpackets.Callback callback = new RevocationSignatureSubpackets.Callback() { 443 @Override 444 public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { 445 if (revocationAttributes != null) { 446 hashedSubpackets.setRevocationReason(false, revocationAttributes); 447 } 448 } 449 }; 450 451 return revokeUserId(userId, secretKeyRingProtector, callback); 452 } 453 454 @Override 455 public SecretKeyRingEditorInterface revokeUserId( 456 @Nonnull CharSequence userId, 457 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 458 @Nullable RevocationSignatureSubpackets.Callback subpacketCallback) 459 throws PGPException { 460 String sanitized = sanitizeUserId(userId); 461 return revokeUserIds( 462 SelectUserId.exactMatch(sanitized), 463 secretKeyRingProtector, 464 subpacketCallback); 465 } 466 467 @Override 468 public SecretKeyRingEditorInterface revokeUserIds( 469 @Nonnull SelectUserId userIdSelector, 470 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 471 @Nullable RevocationAttributes revocationAttributes) 472 throws PGPException { 473 474 return revokeUserIds( 475 userIdSelector, 476 secretKeyRingProtector, 477 new RevocationSignatureSubpackets.Callback() { 478 @Override 479 public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { 480 hashedSubpackets.setRevocationReason(revocationAttributes); 481 } 482 }); 483 } 484 485 @Override 486 public SecretKeyRingEditorInterface revokeUserIds( 487 @Nonnull SelectUserId userIdSelector, 488 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 489 @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) 490 throws PGPException { 491 List<String> selected = userIdSelector.selectUserIds(secretKeyRing); 492 if (selected.isEmpty()) { 493 throw new NoSuchElementException("No matching user-ids found on the key."); 494 } 495 496 for (String userId : selected) { 497 doRevokeUserId(userId, secretKeyRingProtector, subpacketsCallback); 498 } 499 500 return this; 501 } 502 503 private SecretKeyRingEditorInterface doRevokeUserId( 504 @Nonnull String userId, 505 @Nonnull SecretKeyRingProtector protector, 506 @Nullable RevocationSignatureSubpackets.Callback callback) 507 throws PGPException { 508 PGPSecretKey primarySecretKey = secretKeyRing.getSecretKey(); 509 RevocationSignatureBuilder signatureBuilder = new RevocationSignatureBuilder( 510 SignatureType.CERTIFICATION_REVOCATION, 511 primarySecretKey, 512 protector); 513 514 signatureBuilder.applyCallback(callback); 515 516 PGPSignature revocationSignature = signatureBuilder.build(userId); 517 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, userId, revocationSignature); 518 return this; 519 } 520 521 @Override 522 public SecretKeyRingEditorInterface setExpirationDate( 523 @Nullable Date expiration, 524 @Nonnull SecretKeyRingProtector secretKeyRingProtector) 525 throws PGPException { 526 527 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 528 if (!primaryKey.isMasterKey()) { 529 throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key."); 530 } 531 532 // reissue direct key sig 533 PGPSignature prevDirectKeySig = getPreviousDirectKeySignature(); 534 if (prevDirectKeySig != null) { 535 PGPSignature directKeySig = reissueDirectKeySignature(expiration, secretKeyRingProtector, prevDirectKeySig); 536 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryKey.getPublicKey(), directKeySig); 537 } 538 539 // reissue primary user-id sig 540 String primaryUserId = PGPainless.inspectKeyRing(secretKeyRing).getPossiblyExpiredPrimaryUserId(); 541 if (primaryUserId != null) { 542 PGPSignature prevUserIdSig = getPreviousUserIdSignatures(primaryUserId); 543 PGPSignature userIdSig = reissuePrimaryUserIdSig(expiration, secretKeyRingProtector, primaryUserId, prevUserIdSig); 544 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); 545 } 546 547 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 548 for (String userId : info.getValidUserIds()) { 549 if (userId.equals(primaryUserId)) { 550 continue; 551 } 552 553 PGPSignature prevUserIdSig = info.getLatestUserIdCertification(userId); 554 if (prevUserIdSig == null) { 555 throw new AssertionError("A valid user-id shall never have no user-id signature."); 556 } 557 558 if (prevUserIdSig.getHashedSubPackets().isPrimaryUserID()) { 559 PGPSignature userIdSig = reissueNonPrimaryUserId(secretKeyRingProtector, userId, prevUserIdSig); 560 secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); 561 } 562 } 563 564 return this; 565 } 566 567 private PGPSignature reissueNonPrimaryUserId( 568 SecretKeyRingProtector secretKeyRingProtector, 569 String userId, 570 PGPSignature prevUserIdSig) 571 throws PGPException { 572 SelfSignatureBuilder builder = new SelfSignatureBuilder(secretKeyRing.getSecretKey(), secretKeyRingProtector, prevUserIdSig); 573 builder.applyCallback(new SelfSignatureSubpackets.Callback() { 574 @Override 575 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 576 // unmark as primary 577 hashedSubpackets.setPrimaryUserId(null); 578 } 579 }); 580 return builder.build(secretKeyRing.getPublicKey(), userId); 581 } 582 583 private PGPSignature reissuePrimaryUserIdSig( 584 @Nullable Date expiration, 585 @Nonnull SecretKeyRingProtector secretKeyRingProtector, 586 @Nonnull String primaryUserId, 587 @Nonnull PGPSignature prevUserIdSig) 588 throws PGPException { 589 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 590 PGPPublicKey publicKey = primaryKey.getPublicKey(); 591 592 SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevUserIdSig); 593 builder.applyCallback(new SelfSignatureSubpackets.Callback() { 594 @Override 595 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 596 if (expiration != null) { 597 hashedSubpackets.setKeyExpirationTime(true, publicKey.getCreationTime(), expiration); 598 } else { 599 hashedSubpackets.setKeyExpirationTime(new KeyExpirationTime(true, 0)); 600 } 601 hashedSubpackets.setPrimaryUserId(); 602 } 603 }); 604 return builder.build(publicKey, primaryUserId); 605 } 606 607 private PGPSignature reissueDirectKeySignature( 608 Date expiration, 609 SecretKeyRingProtector secretKeyRingProtector, 610 PGPSignature prevDirectKeySig) 611 throws PGPException { 612 PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); 613 PGPPublicKey publicKey = primaryKey.getPublicKey(); 614 final Date keyCreationTime = publicKey.getCreationTime(); 615 616 DirectKeySignatureBuilder builder = new DirectKeySignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig); 617 builder.applyCallback(new SelfSignatureSubpackets.Callback() { 618 @Override 619 public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { 620 if (expiration != null) { 621 hashedSubpackets.setKeyExpirationTime(keyCreationTime, expiration); 622 } else { 623 hashedSubpackets.setKeyExpirationTime(null); 624 } 625 } 626 }); 627 628 return builder.build(publicKey); 629 } 630 631 private PGPSignature getPreviousDirectKeySignature() { 632 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 633 return info.getLatestDirectKeySelfSignature(); 634 } 635 636 private PGPSignature getPreviousUserIdSignatures(String userId) { 637 KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); 638 return info.getLatestUserIdCertification(userId); 639 } 640 641 @Override 642 public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( 643 @Nullable Passphrase oldPassphrase, 644 @Nonnull KeyRingProtectionSettings oldProtectionSettings) { 645 SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector( 646 oldProtectionSettings, 647 new SolitaryPassphraseProvider(oldPassphrase)); 648 649 return new WithKeyRingEncryptionSettingsImpl(null, protector); 650 } 651 652 @Override 653 public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( 654 @Nonnull Long keyId, 655 @Nullable Passphrase oldPassphrase, 656 @Nonnull KeyRingProtectionSettings oldProtectionSettings) { 657 Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase); 658 SecretKeyRingProtector protector = new CachingSecretKeyRingProtector( 659 passphraseMap, oldProtectionSettings, null); 660 661 return new WithKeyRingEncryptionSettingsImpl(keyId, protector); 662 } 663 664 @Override 665 public PGPSecretKeyRing done() { 666 return secretKeyRing; 667 } 668 669 private final class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings { 670 671 private final Long keyId; 672 // Protector to unlock the key with the old passphrase 673 private final SecretKeyRingProtector oldProtector; 674 675 /** 676 * Builder for selecting protection settings. 677 * 678 * If the keyId is null, the whole keyRing will get the same new passphrase. 679 * 680 * @param keyId id of the subkey whose passphrase will be changed, or null. 681 * @param oldProtector protector do unlock the key/ring. 682 */ 683 private WithKeyRingEncryptionSettingsImpl(Long keyId, SecretKeyRingProtector oldProtector) { 684 this.keyId = keyId; 685 this.oldProtector = oldProtector; 686 } 687 688 @Override 689 public WithPassphrase withSecureDefaultSettings() { 690 return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()); 691 } 692 693 @Override 694 public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) { 695 return new WithPassphraseImpl(keyId, oldProtector, settings); 696 } 697 } 698 699 private final class WithPassphraseImpl implements WithPassphrase { 700 701 private final SecretKeyRingProtector oldProtector; 702 private final KeyRingProtectionSettings newProtectionSettings; 703 private final Long keyId; 704 705 private WithPassphraseImpl( 706 Long keyId, 707 SecretKeyRingProtector oldProtector, 708 KeyRingProtectionSettings newProtectionSettings) { 709 this.keyId = keyId; 710 this.oldProtector = oldProtector; 711 this.newProtectionSettings = newProtectionSettings; 712 } 713 714 @Override 715 public SecretKeyRingEditorInterface toNewPassphrase(Passphrase passphrase) 716 throws PGPException { 717 SecretKeyRingProtector newProtector = new PasswordBasedSecretKeyRingProtector( 718 newProtectionSettings, new SolitaryPassphraseProvider(passphrase)); 719 720 PGPSecretKeyRing secretKeys = changePassphrase( 721 keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); 722 SecretKeyRingEditor.this.secretKeyRing = secretKeys; 723 724 return SecretKeyRingEditor.this; 725 } 726 727 @Override 728 public SecretKeyRingEditorInterface toNoPassphrase() 729 throws PGPException { 730 SecretKeyRingProtector newProtector = new UnprotectedKeysProtector(); 731 732 PGPSecretKeyRing secretKeys = changePassphrase( 733 keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); 734 SecretKeyRingEditor.this.secretKeyRing = secretKeys; 735 736 return SecretKeyRingEditor.this; 737 } 738 } 739 740 private PGPSecretKeyRing changePassphrase(Long keyId, 741 PGPSecretKeyRing secretKeys, 742 SecretKeyRingProtector oldProtector, 743 SecretKeyRingProtector newProtector) 744 throws PGPException { 745 List<PGPSecretKey> secretKeyList = new ArrayList<>(); 746 if (keyId == null) { 747 // change passphrase of whole key ring 748 Iterator<PGPSecretKey> secretKeyIterator = secretKeys.getSecretKeys(); 749 while (secretKeyIterator.hasNext()) { 750 PGPSecretKey secretKey = secretKeyIterator.next(); 751 secretKey = reencryptPrivateKey(secretKey, oldProtector, newProtector); 752 secretKeyList.add(secretKey); 753 } 754 } else { 755 // change passphrase of selected subkey only 756 Iterator<PGPSecretKey> secretKeyIterator = secretKeys.getSecretKeys(); 757 while (secretKeyIterator.hasNext()) { 758 PGPSecretKey secretKey = secretKeyIterator.next(); 759 if (secretKey.getPublicKey().getKeyID() == keyId) { 760 // Re-encrypt only the selected subkey 761 secretKey = reencryptPrivateKey(secretKey, oldProtector, newProtector); 762 } 763 secretKeyList.add(secretKey); 764 } 765 } 766 767 PGPSecretKeyRing newRing = new PGPSecretKeyRing(secretKeyList); 768 newRing = s2kUsageFixIfNecessary(newRing, newProtector); 769 return newRing; 770 } 771 772 private PGPSecretKeyRing s2kUsageFixIfNecessary(PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) 773 throws PGPException { 774 boolean hasS2KUsageChecksum = false; 775 for (PGPSecretKey secKey : secretKeys) { 776 if (secKey.getS2KUsage() == SecretKeyPacket.USAGE_CHECKSUM) { 777 hasS2KUsageChecksum = true; 778 break; 779 } 780 } 781 if (hasS2KUsageChecksum) { 782 secretKeys = S2KUsageFix.replaceUsageChecksumWithUsageSha1( 783 secretKeys, protector, true); 784 } 785 return secretKeys; 786 } 787 788 private static PGPSecretKey reencryptPrivateKey( 789 PGPSecretKey secretKey, 790 SecretKeyRingProtector oldProtector, 791 SecretKeyRingProtector newProtector) 792 throws PGPException { 793 S2K s2k = secretKey.getS2K(); 794 // If the key uses GNU_DUMMY_S2K, we leave it as is and skip this block 795 if (s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) { 796 long secretKeyId = secretKey.getKeyID(); 797 PBESecretKeyDecryptor decryptor = oldProtector.getDecryptor(secretKeyId); 798 PBESecretKeyEncryptor encryptor = newProtector.getEncryptor(secretKeyId); 799 secretKey = PGPSecretKey.copyWithNewPassword(secretKey, decryptor, encryptor); 800 } 801 return secretKey; 802 } 803}