diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 88d67440..25550dbf 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -72,6 +72,7 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.util.CollectionUtils; import org.pgpainless.util.Passphrase; +import org.pgpainless.util.selection.userid.SelectUserId; public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { @@ -391,12 +392,25 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { return doRevokeUserId(userId, secretKeyRingProtector, subpacketCallback); } + @Override + public SecretKeyRingEditorInterface revokeUserIds(SelectUserId userIdSelector, 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(String userId, SecretKeyRingProtector protector, @Nullable RevocationSignatureSubpackets.Callback callback) throws PGPException { PGPSecretKey primarySecretKey = secretKeyRing.getSecretKey(); - PGPPublicKey primaryPublicKey = primarySecretKey.getPublicKey(); RevocationSignatureBuilder signatureBuilder = new RevocationSignatureBuilder( SignatureType.CERTIFICATION_REVOCATION, primarySecretKey, diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java index 67974ade..0e662a50 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java @@ -25,6 +25,7 @@ import org.pgpainless.key.util.UserId; import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; import org.pgpainless.util.Passphrase; +import org.pgpainless.util.selection.userid.SelectUserId; public interface SecretKeyRingEditorInterface { @@ -207,6 +208,11 @@ public interface SecretKeyRingEditorInterface { @Nullable RevocationSignatureSubpackets.Callback subpacketCallback) throws PGPException; + SecretKeyRingEditorInterface revokeUserIds(SelectUserId userIdSelector, + SecretKeyRingProtector secretKeyRingProtector, + @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) + throws PGPException; + /** * Set the expiration date for the primary key of the key ring. * If the key is supposed to never expire, then an expiration date of null is expected. diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeUserIdsTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeUserIdsTest.java new file mode 100644 index 00000000..f5d070d7 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeUserIdsTest.java @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.modification; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.selection.userid.SelectUserId; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RevokeUserIdsTest { + + @Test + public void revokeWithSelectUserId() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() + .modernKeyRing("Alice ", null); + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + + secretKeys = PGPainless.modifyKeyRing(secretKeys) + .addUserId("Allice ", protector) + .addUserId("Alice ", protector) + .done(); + + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isUserIdValid("Alice ")); + assertTrue(info.isUserIdValid("Allice ")); + assertTrue(info.isUserIdValid("Alice ")); + + secretKeys = PGPainless.modifyKeyRing(secretKeys) + .revokeUserIds(SelectUserId.containsEmailAddress("alice@example.org"), protector, null) + .done(); + + info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isUserIdValid("Alice ")); + assertFalse(info.isUserIdValid("Allice ")); + assertFalse(info.isUserIdValid("Alice ")); + } + + @Test + public void emptySelectionYieldsNoSuchElementException() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() + .modernKeyRing("Alice ", null); + + assertThrows(NoSuchElementException.class, () -> + PGPainless.modifyKeyRing(secretKeys).revokeUserIds( + SelectUserId.containsEmailAddress("alice@example.org"), + SecretKeyRingProtector.unprotectedKeys(), + null)); + } +}