diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java index 32a21650..4ed2f756 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java @@ -55,6 +55,7 @@ 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.OpenPgpKeyAttributeUtil; import org.pgpainless.util.NotYetImplementedException; import org.pgpainless.util.Passphrase; @@ -274,8 +275,32 @@ public class KeyRingEditor implements KeyRingEditorInterface { } @Override - public KeyRingEditorInterface revokeSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector protector) { - throw new NotYetImplementedException(); + public KeyRingEditorInterface revokeSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector protector) + throws PGPException { + PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); + PGPPrivateKey privateKey = primaryKey.extractPrivateKey(protector.getDecryptor(primaryKey.getKeyID())); + + PGPPublicKey revokeeSubKey = secretKeyRing.getPublicKey(fingerprint.getKeyId()); + if (revokeeSubKey == null) { + throw new NoSuchElementException("No subkey with fingerprint " + fingerprint + " found."); + } + + PGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder( + primaryKey.getPublicKey().getAlgorithm(), + defaultDigestHashAlgorithm.getAlgorithmId()); + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(SignatureType.SUBKEY_REVOCATION.getCode(), privateKey); + + // Generate revocation + PGPSignature subKeyRevocation = signatureGenerator.generateCertification(primaryKey.getPublicKey(), revokeeSubKey); + revokeeSubKey = PGPPublicKey.addCertification(revokeeSubKey, subKeyRevocation); + + // Inject revoked public key into key ring + PGPPublicKeyRing publicKeyRing = KeyRingUtils.publicKeyRingFrom(secretKeyRing); + publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, revokeeSubKey); + secretKeyRing = PGPSecretKeyRing.replacePublicKeys(secretKeyRing, publicKeyRing); + + return this; } @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java index 3a9403b3..25a37391 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java @@ -102,7 +102,7 @@ public interface KeyRingEditorInterface { * @param fingerprint fingerprint of the subkey to be revoked * @return the builder */ - KeyRingEditorInterface revokeSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector secretKeyRingProtector); + KeyRingEditorInterface revokeSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector secretKeyRingProtector) throws PGPException; /** * Revoke the subkey binding signature of a subkey. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java new file mode 100644 index 00000000..c75fb95c --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; + +public class KeyRingUtils { + + public static PGPPublicKeyRing publicKeyRingFrom(PGPSecretKeyRing secretKeys) { + List publicKeyList = new ArrayList<>(); + Iterator publicKeyIterator = secretKeys.getPublicKeys(); + while (publicKeyIterator.hasNext()) { + publicKeyList.add(publicKeyIterator.next()); + } + PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList); + return publicKeyRing; + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeSubKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeSubKeyTest.java new file mode 100644 index 00000000..21aa490f --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeSubKeyTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key.modification; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class RevokeSubKeyTest { + + @Test + public void revokeSukeyTest() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + + Iterator keysIterator = secretKeys.iterator(); + PGPSecretKey primaryKey = keysIterator.next(); + PGPSecretKey subKey = keysIterator.next(); + + assertFalse(subKey.getPublicKey().hasRevocation()); + + SecretKeyRingProtector protector = PasswordBasedSecretKeyRingProtector + .forKey(secretKeys, Passphrase.fromPassword("password123")); + + secretKeys = PGPainless.modifyKeyRing(secretKeys) + .revokeSubKey(new OpenPgpV4Fingerprint(subKey), protector) + .done(); + keysIterator = secretKeys.iterator(); + primaryKey = keysIterator.next(); + subKey = keysIterator.next(); + + assertTrue(subKey.getPublicKey().hasRevocation()); + } +}