From 3aa9e2915ab94db4368edf211bb0cecb2d2ef1cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Dec 2021 13:28:16 +0100 Subject: [PATCH] Re-certify expired user-ids when changing key expiration date --- .../org/pgpainless/key/info/KeyRingInfo.java | 33 ++++++++++++++++++- .../secretkeyring/SecretKeyRingEditor.java | 7 ++-- ...gePrimaryUserIdAndExpirationDatesTest.java | 11 +++---- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index a0cf76c3..19e73220 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -349,6 +349,38 @@ public class KeyRingInfo { return valid; } + /** + * Return a list of all user-ids that were valid at some point, but might be expired by now. + * + * @return bound user-ids + */ + public List getBoundButPossiblyExpiredUserIds() { + List probablyExpired = new ArrayList<>(); + List userIds = getUserIds(); + + for (String userId : userIds) { + PGPSignature certification = signatures.userIdCertifications.get(userId); + PGPSignature revocation = signatures.userIdRevocations.get(userId); + + // Not revoked -> valid + if (revocation == null) { + probablyExpired.add(userId); + continue; + } + + // Hard revocation -> invalid + if (SignatureUtils.isHardRevocation(revocation)) { + continue; + } + + // Soft revocation -> valid if certification is newer than revocation (revalidation) + if (certification.getCreationTime().after(revocation.getCreationTime())) { + probablyExpired.add(userId); + } + } + return probablyExpired; + } + /** * Return true if the provided user-id is valid. * @@ -371,7 +403,6 @@ public class KeyRingInfo { PGPSignature certification = signatures.userIdCertifications.get(userId); PGPSignature revocation = signatures.userIdRevocations.get(userId); - // If user-id is expired, certification will be null. if (certification == null) { return false; } 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 8b703600..51ca0a3a 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 @@ -146,15 +146,12 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { // Determine previous key expiration date PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); - /* KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); String primaryUserId = info.getPrimaryUserId(); PGPSignature signature = primaryUserId == null ? info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId); final Date previousKeyExpiration = signature == null ? null : SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey); - */ - final Date previousKeyExpiration = null; // Add new primary user-id signature addUserId( @@ -173,8 +170,8 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { protector); // unmark previous primary user-ids to be non-primary - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); - for (String otherUserId : info.getValidUserIds()) { + info = PGPainless.inspectKeyRing(secretKeyRing); + for (String otherUserId : info.getBoundButPossiblyExpiredUserIds()) { if (userId.toString().equals(otherUserId)) { continue; } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangePrimaryUserIdAndExpirationDatesTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangePrimaryUserIdAndExpirationDatesTest.java index 51c60383..26618b7a 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangePrimaryUserIdAndExpirationDatesTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangePrimaryUserIdAndExpirationDatesTest.java @@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.util.Date; @@ -151,7 +150,7 @@ public class ChangePrimaryUserIdAndExpirationDatesTest { @Test public void generateA_expire_primaryB_expire_isPrimaryB() - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException, IOException { + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("A", null); SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); @@ -165,7 +164,7 @@ public class ChangePrimaryUserIdAndExpirationDatesTest { KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); assertIsPrimaryUserId("A", info); - assertIsNotValid("A", info); + assertIsNotValid("A", info); // A is expired secretKeys = PGPainless.modifyKeyRing(secretKeys) .addPrimaryUserId("B", protector) @@ -174,8 +173,8 @@ public class ChangePrimaryUserIdAndExpirationDatesTest { info = PGPainless.inspectKeyRing(secretKeys); assertIsPrimaryUserId("B", info); - assertIsValid("B", info); - assertIsNotValid("A", info); // A is still expired + assertIsNotValid("B", info); // A and B are still expired + assertIsNotValid("A", info); Thread.sleep(1000); @@ -187,7 +186,7 @@ public class ChangePrimaryUserIdAndExpirationDatesTest { info = PGPainless.inspectKeyRing(secretKeys); assertIsValid("B", info); - assertIsNotValid("A", info); // A was expired when the expiration date was changed, so it was not re-certified + assertIsValid("A", info); // A got re-validated when changing exp date assertIsPrimaryUserId("B", info); secretKeys = PGPainless.modifyKeyRing(secretKeys)