// SPDX-FileCopyrightText: 2020 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.key.modification.secretkeyring; import static org.pgpainless.key.util.KeyRingUtils.changePassphrase; import static org.pgpainless.util.CollectionUtils.concat; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.bcpg.sig.KeyExpirationTime; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.AlgorithmSuite; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.Feature; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.PublicKeyAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.protection.CachingSecretKeyRingProtector; import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; import org.pgpainless.key.util.KeyRingUtils; import org.pgpainless.key.util.RevocationAttributes; import org.pgpainless.signature.builder.DirectKeySelfSignatureBuilder; import org.pgpainless.signature.builder.PrimaryKeyBindingSignatureBuilder; import org.pgpainless.signature.builder.RevocationSignatureBuilder; import org.pgpainless.signature.builder.SelfSignatureBuilder; import org.pgpainless.signature.builder.SubkeyBindingSignatureBuilder; import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.util.Passphrase; import org.pgpainless.util.selection.userid.SelectUserId; public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { private PGPSecretKeyRing secretKeyRing; private final Date referenceTime; public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing) { this(secretKeyRing, new Date()); } public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing, @Nonnull Date referenceTime) { this.secretKeyRing = secretKeyRing; this.referenceTime = referenceTime; } @Nonnull @Override public Date getReferenceTime() { return referenceTime; } @Override public SecretKeyRingEditorInterface addUserId( @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector secretKeyRingProtector) throws PGPException { return addUserId(userId, null, secretKeyRingProtector); } @Override public SecretKeyRingEditorInterface addUserId( @Nonnull CharSequence userId, @Nullable SelfSignatureSubpackets.Callback signatureSubpacketCallback, @Nonnull SecretKeyRingProtector protector) throws PGPException { String sanitizeUserId = sanitizeUserId(userId); // user-id certifications live on the primary key PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); // retain key flags from previous signature KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); if (info.isHardRevoked(userId.toString())) { throw new IllegalArgumentException("User-ID " + userId + " is hard revoked and cannot be re-certified."); } List keyFlags = info.getKeyFlagsOf(info.getKeyId()); Set hashAlgorithmPreferences; Set symmetricKeyAlgorithmPreferences; Set compressionAlgorithmPreferences; try { hashAlgorithmPreferences = info.getPreferredHashAlgorithms(); symmetricKeyAlgorithmPreferences = info.getPreferredSymmetricKeyAlgorithms(); compressionAlgorithmPreferences = info.getPreferredCompressionAlgorithms(); } catch (IllegalStateException e) { // missing user-id sig AlgorithmSuite algorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite(); hashAlgorithmPreferences = algorithmSuite.getHashAlgorithms(); symmetricKeyAlgorithmPreferences = algorithmSuite.getSymmetricKeyAlgorithms(); compressionAlgorithmPreferences = algorithmSuite.getCompressionAlgorithms(); } SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, protector); builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); builder.setSignatureType(SignatureType.POSITIVE_CERTIFICATION); // Retain signature subpackets of previous signatures builder.getHashedSubpackets().setKeyFlags(keyFlags); builder.getHashedSubpackets().setPreferredHashAlgorithms(hashAlgorithmPreferences); builder.getHashedSubpackets().setPreferredSymmetricKeyAlgorithms(symmetricKeyAlgorithmPreferences); builder.getHashedSubpackets().setPreferredCompressionAlgorithms(compressionAlgorithmPreferences); builder.getHashedSubpackets().setFeatures(Feature.MODIFICATION_DETECTION); builder.applyCallback(signatureSubpacketCallback); PGPSignature signature = builder.build(primaryKey.getPublicKey(), sanitizeUserId); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, sanitizeUserId, signature); return this; } @Override public SecretKeyRingEditorInterface addPrimaryUserId( @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector protector) throws PGPException { // Determine previous key expiration date PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); String primaryUserId = info.getPrimaryUserId(); PGPSignature signature = primaryUserId == null ? info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId); final Date previousKeyExpiration = signature == null ? null : SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey); // Add new primary user-id signature addUserId( userId, new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { hashedSubpackets.setPrimaryUserId(); if (previousKeyExpiration != null) { hashedSubpackets.setKeyExpirationTime(primaryKey, previousKeyExpiration); } else { hashedSubpackets.setKeyExpirationTime(null); } } }, protector); // unmark previous primary user-ids to be non-primary info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); for (String otherUserId : info.getValidAndExpiredUserIds()) { if (userId.toString().equals(otherUserId)) { continue; } // We need to unmark this user-id as primary PGPSignature userIdCertification = info.getLatestUserIdCertification(otherUserId); assert (userIdCertification != null); if (userIdCertification.getHashedSubPackets().isPrimaryUserID()) { addUserId(otherUserId, new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { hashedSubpackets.setPrimaryUserId(null); hashedSubpackets.setKeyExpirationTime(null); // non-primary } }, protector); } } return this; } @Override public SecretKeyRingEditorInterface removeUserId( SelectUserId userIdSelector, SecretKeyRingProtector protector) throws PGPException { RevocationAttributes revocationAttributes = RevocationAttributes.createCertificateRevocation() .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withoutDescription(); return revokeUserIds(userIdSelector, protector, revocationAttributes); } @Override public SecretKeyRingEditorInterface removeUserId( CharSequence userId, SecretKeyRingProtector protector) throws PGPException { return removeUserId( SelectUserId.exactMatch(userId.toString()), protector); } @Override public SecretKeyRingEditorInterface replaceUserId(@Nonnull CharSequence oldUserId, @Nonnull CharSequence newUserId, @Nonnull SecretKeyRingProtector protector) throws PGPException { String oldUID = oldUserId.toString().trim(); String newUID = newUserId.toString().trim(); if (oldUID.isEmpty()) { throw new IllegalArgumentException("Old user-id cannot be empty."); } if (newUID.isEmpty()) { throw new IllegalArgumentException("New user-id cannot be empty."); } KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); if (!info.isUserIdValid(oldUID)) { throw new NoSuchElementException("Key does not carry user-id '" + oldUID + "', or it is not valid."); } PGPSignature oldCertification = info.getLatestUserIdCertification(oldUID); if (oldCertification == null) { throw new AssertionError("Certification for old user-id MUST NOT be null."); } // Bind new user-id addUserId(newUserId, new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { SignatureSubpacketsHelper.applyFrom(oldCertification.getHashedSubPackets(), (SignatureSubpackets) hashedSubpackets); // Primary user-id if (oldUID.equals(info.getPrimaryUserId())) { // Implicit primary user-id if (!oldCertification.getHashedSubPackets().isPrimaryUserID()) { hashedSubpackets.setPrimaryUserId(); } } } @Override public void modifyUnhashedSubpackets(SelfSignatureSubpackets unhashedSubpackets) { SignatureSubpacketsHelper.applyFrom(oldCertification.getUnhashedSubPackets(), (SignatureSubpackets) unhashedSubpackets); } }, protector); return revokeUserId(oldUID, protector); } // TODO: Move to utility class? private String sanitizeUserId(@Nonnull CharSequence userId) { // TODO: Further research how to sanitize user IDs. // eg. what about newlines? return userId.toString().trim(); } @Override public SecretKeyRingEditorInterface addSubKey( @Nonnull KeySpec keySpec, @Nonnull Passphrase subKeyPassphrase, @Nonnull SecretKeyRingProtector secretKeyRingProtector) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector .forKeyId(keyPair.getKeyID(), subKeyPassphrase); SelfSignatureSubpackets.Callback callback = new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { SignatureSubpacketsHelper.applyFrom(keySpec.getSubpackets(), (SignatureSubpackets) hashedSubpackets); } }; List keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); KeyFlag firstFlag = keyFlags.remove(0); KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); return addSubKey(keyPair, callback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); } @Override public SecretKeyRingEditorInterface addSubKey( @Nonnull KeySpec keySpec, @Nullable Passphrase subkeyPassphrase, @Nullable SelfSignatureSubpackets.Callback subpacketsCallback, @Nonnull SecretKeyRingProtector secretKeyRingProtector) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector .forKeyId(keyPair.getKeyID(), subkeyPassphrase); List keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); KeyFlag firstFlag = keyFlags.remove(0); KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); return addSubKey(keyPair, subpacketsCallback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); } @Override public SecretKeyRingEditorInterface addSubKey( @Nonnull PGPKeyPair subkey, @Nullable SelfSignatureSubpackets.Callback bindingSignatureCallback, @Nonnull SecretKeyRingProtector subkeyProtector, @Nonnull SecretKeyRingProtector primaryKeyProtector, @Nonnull KeyFlag keyFlag, KeyFlag... additionalKeyFlags) throws PGPException, IOException { KeyFlag[] flags = concat(keyFlag, additionalKeyFlags); PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.requireFromId(subkey.getPublicKey().getAlgorithm()); SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm); // check key against public key algorithm policy PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(subkey.getPublicKey().getAlgorithm()); int bitStrength = subkey.getPublicKey().getBitStrength(); if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { throw new IllegalArgumentException("Public key algorithm policy violation: " + publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); } PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); HashAlgorithm hashAlgorithm = HashAlgorithmNegotiator .negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) .negotiateHashAlgorithm(info.getPreferredHashAlgorithms()); PGPSecretKey secretSubkey = new PGPSecretKey(subkey.getPrivateKey(), subkey.getPublicKey(), ImplementationFactory.getInstance() .getV4FingerprintCalculator(), false, subkeyProtector.getEncryptor(subkey.getKeyID())); SubkeyBindingSignatureBuilder skBindingBuilder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm); skBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); skBindingBuilder.getHashedSubpackets().setKeyFlags(flags); if (subkeyAlgorithm.isSigningCapable()) { PrimaryKeyBindingSignatureBuilder pkBindingBuilder = new PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm); pkBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); PGPSignature pkBinding = pkBindingBuilder.build(primaryKey.getPublicKey()); skBindingBuilder.getHashedSubpackets().addEmbeddedSignature(pkBinding); } skBindingBuilder.applyCallback(bindingSignatureCallback); PGPSignature skBinding = skBindingBuilder.build(secretSubkey.getPublicKey()); secretSubkey = KeyRingUtils.secretKeyPlusSignature(secretSubkey, skBinding); secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey); return this; } @Override public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes revocationAttributes) throws PGPException { RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); return revoke(secretKeyRingProtector, callback); } @Override public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) throws PGPException { return revokeSubKey(secretKeyRing.getSecretKey().getKeyID(), secretKeyRingProtector, subpacketsCallback); } @Override public SecretKeyRingEditorInterface revokeSubKey(long subKeyId, SecretKeyRingProtector protector, RevocationAttributes revocationAttributes) throws PGPException { RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); return revokeSubKey(subKeyId, protector, callback); } @Override public SecretKeyRingEditorInterface revokeSubKey(long keyID, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) throws PGPException { // retrieve subkey to be revoked PGPPublicKey revokeeSubKey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, keyID); // create revocation PGPSignature subKeyRevocation = generateRevocation(secretKeyRingProtector, revokeeSubKey, subpacketsCallback); // inject revocation sig into key ring secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, revokeeSubKey, subKeyRevocation); return this; } @Override public PGPSignature createRevocation(@Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes revocationAttributes) throws PGPException { PGPPublicKey revokeeSubKey = secretKeyRing.getPublicKey(); PGPSignature revocationCertificate = generateRevocation( secretKeyRingProtector, revokeeSubKey, callbackFromRevocationAttributes(revocationAttributes)); return revocationCertificate; } @Override public PGPSignature createRevocation( long subkeyId, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes revocationAttributes) throws PGPException { PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); return generateRevocation(secretKeyRingProtector, revokeeSubkey, callback); } @Override public PGPSignature createRevocation( long subkeyId, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationSignatureSubpackets.Callback certificateSubpacketsCallback) throws PGPException { PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); return generateRevocation(secretKeyRingProtector, revokeeSubkey, certificateSubpacketsCallback); } private PGPSignature generateRevocation(@Nonnull SecretKeyRingProtector protector, @Nonnull PGPPublicKey revokeeSubKey, @Nullable RevocationSignatureSubpackets.Callback callback) throws PGPException { PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); SignatureType signatureType = revokeeSubKey.isMasterKey() ? SignatureType.KEY_REVOCATION : SignatureType.SUBKEY_REVOCATION; RevocationSignatureBuilder signatureBuilder = new RevocationSignatureBuilder(signatureType, primaryKey, protector); signatureBuilder.applyCallback(callback); PGPSignature revocation = signatureBuilder.build(revokeeSubKey); return revocation; } private static RevocationSignatureSubpackets.Callback callbackFromRevocationAttributes( @Nullable RevocationAttributes attributes) { return new RevocationSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { if (attributes != null) { hashedSubpackets.setRevocationReason(attributes); } } }; } @Override public SecretKeyRingEditorInterface revokeUserId( @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes revocationAttributes) throws PGPException { if (revocationAttributes != null) { RevocationAttributes.Reason reason = revocationAttributes.getReason(); if (reason != RevocationAttributes.Reason.NO_REASON && reason != RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { throw new IllegalArgumentException("Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID"); } } RevocationSignatureSubpackets.Callback callback = new RevocationSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { if (revocationAttributes != null) { hashedSubpackets.setRevocationReason(false, revocationAttributes); } } }; return revokeUserId(userId, secretKeyRingProtector, callback); } @Override public SecretKeyRingEditorInterface revokeUserId( @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationSignatureSubpackets.Callback subpacketCallback) throws PGPException { String sanitized = sanitizeUserId(userId); return revokeUserIds( SelectUserId.exactMatch(sanitized), secretKeyRingProtector, subpacketCallback); } @Override public SecretKeyRingEditorInterface revokeUserIds( @Nonnull SelectUserId userIdSelector, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes revocationAttributes) throws PGPException { return revokeUserIds( userIdSelector, secretKeyRingProtector, new RevocationSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { hashedSubpackets.setRevocationReason(revocationAttributes); } }); } @Override public SecretKeyRingEditorInterface revokeUserIds( @Nonnull SelectUserId userIdSelector, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) throws PGPException { List selected = userIdSelector.selectUserIds(secretKeyRing); if (selected.isEmpty()) { throw new NoSuchElementException("No matching user-ids found on the key."); } for (String userId : selected) { doRevokeUserId(userId, secretKeyRingProtector, subpacketsCallback); } return this; } private SecretKeyRingEditorInterface doRevokeUserId( @Nonnull String userId, @Nonnull SecretKeyRingProtector protector, @Nullable RevocationSignatureSubpackets.Callback callback) throws PGPException { PGPSecretKey primarySecretKey = secretKeyRing.getSecretKey(); RevocationSignatureBuilder signatureBuilder = new RevocationSignatureBuilder( SignatureType.CERTIFICATION_REVOCATION, primarySecretKey, protector); if (referenceTime != null) { signatureBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); } signatureBuilder.applyCallback(callback); PGPSignature revocationSignature = signatureBuilder.build(userId); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, userId, revocationSignature); return this; } @Override public SecretKeyRingEditorInterface setExpirationDate( @Nullable Date expiration, @Nonnull SecretKeyRingProtector secretKeyRingProtector) throws PGPException { PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); if (!primaryKey.isMasterKey()) { throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key."); } // reissue direct key sig PGPSignature prevDirectKeySig = getPreviousDirectKeySignature(); if (prevDirectKeySig != null) { PGPSignature directKeySig = reissueDirectKeySignature(expiration, secretKeyRingProtector, prevDirectKeySig); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryKey.getPublicKey(), directKeySig); } // reissue primary user-id sig String primaryUserId = PGPainless.inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId(); if (primaryUserId != null) { PGPSignature prevUserIdSig = getPreviousUserIdSignatures(primaryUserId); PGPSignature userIdSig = reissuePrimaryUserIdSig(expiration, secretKeyRingProtector, primaryUserId, prevUserIdSig); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); } KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); for (String userId : info.getValidUserIds()) { if (userId.equals(primaryUserId)) { continue; } PGPSignature prevUserIdSig = info.getLatestUserIdCertification(userId); if (prevUserIdSig == null) { throw new AssertionError("A valid user-id shall never have no user-id signature."); } if (prevUserIdSig.getHashedSubPackets().isPrimaryUserID()) { assert (primaryUserId != null); PGPSignature userIdSig = reissueNonPrimaryUserId(secretKeyRingProtector, userId, prevUserIdSig); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); } } return this; } @Override public PGPPublicKeyRing createMinimalRevocationCertificate( @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nullable RevocationAttributes keyRevocationAttributes) throws PGPException { // Check reason if (keyRevocationAttributes != null && !RevocationAttributes.Reason.isKeyRevocation(keyRevocationAttributes.getReason())) { throw new IllegalArgumentException("Revocation reason MUST be applicable to a key revocation."); } PGPSignature revocation = createRevocation(secretKeyRingProtector, keyRevocationAttributes); PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); primaryKey = KeyRingUtils.getStrippedDownPublicKey(primaryKey); primaryKey = PGPPublicKey.addCertification(primaryKey, revocation); return new PGPPublicKeyRing(Collections.singletonList(primaryKey)); } private PGPSignature reissueNonPrimaryUserId( SecretKeyRingProtector secretKeyRingProtector, String userId, PGPSignature prevUserIdSig) throws PGPException { SelfSignatureBuilder builder = new SelfSignatureBuilder(secretKeyRing.getSecretKey(), secretKeyRingProtector, prevUserIdSig); if (referenceTime != null) { builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); } builder.applyCallback(new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { // unmark as primary hashedSubpackets.setPrimaryUserId(null); } }); return builder.build(secretKeyRing.getPublicKey(), userId); } private PGPSignature reissuePrimaryUserIdSig( @Nullable Date expiration, @Nonnull SecretKeyRingProtector secretKeyRingProtector, @Nonnull String primaryUserId, @Nonnull PGPSignature prevUserIdSig) throws PGPException { PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); PGPPublicKey publicKey = primaryKey.getPublicKey(); SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevUserIdSig); if (referenceTime != null) { builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); } builder.applyCallback(new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { if (expiration != null) { hashedSubpackets.setKeyExpirationTime(true, publicKey.getCreationTime(), expiration); } else { hashedSubpackets.setKeyExpirationTime(new KeyExpirationTime(true, 0)); } hashedSubpackets.setPrimaryUserId(); } }); return builder.build(publicKey, primaryUserId); } private PGPSignature reissueDirectKeySignature( Date expiration, SecretKeyRingProtector secretKeyRingProtector, PGPSignature prevDirectKeySig) throws PGPException { PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); PGPPublicKey publicKey = primaryKey.getPublicKey(); final Date keyCreationTime = publicKey.getCreationTime(); DirectKeySelfSignatureBuilder builder = new DirectKeySelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig); if (referenceTime != null) { builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); } builder.applyCallback(new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { if (expiration != null) { hashedSubpackets.setKeyExpirationTime(keyCreationTime, expiration); } else { hashedSubpackets.setKeyExpirationTime(null); } } }); return builder.build(publicKey); } private PGPSignature getPreviousDirectKeySignature() { KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); return info.getLatestDirectKeySelfSignature(); } private PGPSignature getPreviousUserIdSignatures(String userId) { KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); return info.getLatestUserIdCertification(userId); } @Override public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( @Nullable Passphrase oldPassphrase, @Nonnull KeyRingProtectionSettings oldProtectionSettings) { SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector( oldProtectionSettings, new SolitaryPassphraseProvider(oldPassphrase)); return new WithKeyRingEncryptionSettingsImpl(null, protector); } @Override public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( @Nonnull Long keyId, @Nullable Passphrase oldPassphrase, @Nonnull KeyRingProtectionSettings oldProtectionSettings) { Map passphraseMap = Collections.singletonMap(keyId, oldPassphrase); SecretKeyRingProtector protector = new CachingSecretKeyRingProtector( passphraseMap, oldProtectionSettings, null); return new WithKeyRingEncryptionSettingsImpl(keyId, protector); } @Override public PGPSecretKeyRing done() { return secretKeyRing; } private final class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings { private final Long keyId; // Protector to unlock the key with the old passphrase private final SecretKeyRingProtector oldProtector; /** * Builder for selecting protection settings. * * If the keyId is null, the whole keyRing will get the same new passphrase. * * @param keyId id of the subkey whose passphrase will be changed, or null. * @param oldProtector protector do unlock the key/ring. */ private WithKeyRingEncryptionSettingsImpl(Long keyId, SecretKeyRingProtector oldProtector) { this.keyId = keyId; this.oldProtector = oldProtector; } @Override public WithPassphrase withSecureDefaultSettings() { return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()); } @Override public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) { return new WithPassphraseImpl(keyId, oldProtector, settings); } } private final class WithPassphraseImpl implements WithPassphrase { private final SecretKeyRingProtector oldProtector; private final KeyRingProtectionSettings newProtectionSettings; private final Long keyId; private WithPassphraseImpl( Long keyId, SecretKeyRingProtector oldProtector, KeyRingProtectionSettings newProtectionSettings) { this.keyId = keyId; this.oldProtector = oldProtector; this.newProtectionSettings = newProtectionSettings; } @Override public SecretKeyRingEditorInterface toNewPassphrase(Passphrase passphrase) throws PGPException { SecretKeyRingProtector newProtector = new PasswordBasedSecretKeyRingProtector( newProtectionSettings, new SolitaryPassphraseProvider(passphrase)); PGPSecretKeyRing secretKeys = changePassphrase( keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); SecretKeyRingEditor.this.secretKeyRing = secretKeys; return SecretKeyRingEditor.this; } @Override public SecretKeyRingEditorInterface toNoPassphrase() throws PGPException { SecretKeyRingProtector newProtector = new UnprotectedKeysProtector(); PGPSecretKeyRing secretKeys = changePassphrase( keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); SecretKeyRingEditor.this.secretKeyRing = secretKeys; return SecretKeyRingEditor.this; } } }