mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-01-03 16:57:57 +01:00
parent
2a7c6af022
commit
53d6260210
5 changed files with 118 additions and 1 deletions
|
@ -10,6 +10,7 @@ import org.bouncycastle.bcpg.sig.KeyExpirationTime;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
@ -154,7 +155,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
PGPSignature signature = primaryUserId == null ?
|
PGPSignature signature = primaryUserId == null ?
|
||||||
info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId);
|
info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId);
|
||||||
final Date previousKeyExpiration = signature == null ? null :
|
final Date previousKeyExpiration = signature == null ? null :
|
||||||
SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey);
|
SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey);
|
||||||
|
|
||||||
// Add new primary user-id signature
|
// Add new primary user-id signature
|
||||||
addUserId(
|
addUserId(
|
||||||
|
@ -607,6 +608,23 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
return this;
|
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(
|
private PGPSignature reissueNonPrimaryUserId(
|
||||||
SecretKeyRingProtector secretKeyRingProtector,
|
SecretKeyRingProtector secretKeyRingProtector,
|
||||||
String userId,
|
String userId,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
import org.bouncycastle.openpgp.PGPKeyPair;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
@ -460,6 +461,22 @@ public interface SecretKeyRingEditorInterface {
|
||||||
@Nonnull SecretKeyRingProtector secretKeyRingProtector)
|
@Nonnull SecretKeyRingProtector secretKeyRingProtector)
|
||||||
throws PGPException;
|
throws PGPException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a minimal, self-authorizing revocation certificate, containing only the primary key
|
||||||
|
* and a revocation signature.
|
||||||
|
* This type of revocation certificates was introduced in OpenPGP v6.
|
||||||
|
* This method has no side effects on the original key and will leave it intact.
|
||||||
|
*
|
||||||
|
* @param secretKeyRingProtector protector to unlock the primary key.
|
||||||
|
* @param keyRevocationAttributes reason for the revocation (key revocation)
|
||||||
|
* @return minimal revocation certificate
|
||||||
|
*
|
||||||
|
* @throws PGPException in case we cannot generate a revocation signature
|
||||||
|
*/
|
||||||
|
PGPPublicKeyRing createMinimalRevocationCertificate(@Nonnull SecretKeyRingProtector secretKeyRingProtector,
|
||||||
|
@Nullable RevocationAttributes keyRevocationAttributes)
|
||||||
|
throws PGPException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a detached revocation certificate, which can be used to revoke the whole key.
|
* Create a detached revocation certificate, which can be used to revoke the whole key.
|
||||||
* The original key will not be modified by this method.
|
* The original key will not be modified by this method.
|
||||||
|
|
|
@ -485,4 +485,13 @@ public final class KeyRingUtils {
|
||||||
// Parse the key back into an object
|
// Parse the key back into an object
|
||||||
return new PGPSecretKeyRing(encoded.toByteArray(), ImplementationFactory.getInstance().getKeyFingerprintCalculator());
|
return new PGPSecretKeyRing(encoded.toByteArray(), ImplementationFactory.getInstance().getKeyFingerprintCalculator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip all user-ids, user-attributes and signatures from the given public key.
|
||||||
|
* @param bloatedKey public key
|
||||||
|
* @return stripped public key
|
||||||
|
*/
|
||||||
|
public static PGPPublicKey getStrippedDownPublicKey(PGPPublicKey bloatedKey) throws PGPException {
|
||||||
|
return new PGPPublicKey(bloatedKey.getPublicKeyPacket(), ImplementationFactory.getInstance().getKeyFingerprintCalculator());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,25 @@ public final class RevocationAttributes {
|
||||||
return isHardRevocation(reason.reasonCode);
|
return isHardRevocation(reason.reasonCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given {@link Reason} denotes a key revocation.
|
||||||
|
* @param reason reason
|
||||||
|
* @return is key revocation
|
||||||
|
*/
|
||||||
|
public static boolean isKeyRevocation(@Nonnull Reason reason) {
|
||||||
|
return isKeyRevocation(reason.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given reason code denotes a key revocation.
|
||||||
|
* @param code reason code
|
||||||
|
* @return is key revocation
|
||||||
|
*/
|
||||||
|
public static boolean isKeyRevocation(byte code) {
|
||||||
|
Reason reason = MAP.get(code);
|
||||||
|
return reason != USER_ID_NO_LONGER_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
private final byte reasonCode;
|
private final byte reasonCode;
|
||||||
|
|
||||||
Reason(byte reasonCode) {
|
Reason(byte reasonCode) {
|
||||||
|
|
|
@ -4,13 +4,20 @@
|
||||||
|
|
||||||
package org.pgpainless.key.modification;
|
package org.pgpainless.key.modification;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -19,6 +26,7 @@ import org.pgpainless.key.TestKeys;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
|
||||||
public class RevocationCertificateTest {
|
public class RevocationCertificateTest {
|
||||||
|
|
||||||
|
@ -43,4 +51,50 @@ public class RevocationCertificateTest {
|
||||||
|
|
||||||
assertFalse(PGPainless.inspectKeyRing(revokedKey).isKeyValidlyBound(secretKeys.getPublicKey().getKeyID()));
|
assertFalse(PGPainless.inspectKeyRing(revokedKey).isKeyValidlyBound(secretKeys.getPublicKey().getKeyID()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMinimalRevocationCertificateTest() throws PGPException, IOException {
|
||||||
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
|
|
||||||
|
PGPPublicKeyRing minimalRevocationCert = PGPainless.modifyKeyRing(secretKeys).createMinimalRevocationCertificate(
|
||||||
|
SecretKeyRingProtector.unprotectedKeys(),
|
||||||
|
RevocationAttributes.createKeyRevocation().withReason(RevocationAttributes.Reason.KEY_RETIRED).withoutDescription());
|
||||||
|
|
||||||
|
assertEquals(1, minimalRevocationCert.size());
|
||||||
|
PGPPublicKey key = minimalRevocationCert.getPublicKey();
|
||||||
|
assertEquals(secretKeys.getPublicKey().getKeyID(), key.getKeyID());
|
||||||
|
assertEquals(1, CollectionUtils.iteratorToList(key.getSignatures()).size());
|
||||||
|
assertFalse(key.getUserIDs().hasNext());
|
||||||
|
assertFalse(key.getUserAttributes().hasNext());
|
||||||
|
assertNull(key.getTrustData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMinimalRevocationCertificateForFreshKeyTest()
|
||||||
|
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("Alice <alice@example.org>");
|
||||||
|
|
||||||
|
PGPPublicKeyRing minimalRevocationCert = PGPainless.modifyKeyRing(secretKeys).createMinimalRevocationCertificate(
|
||||||
|
SecretKeyRingProtector.unprotectedKeys(),
|
||||||
|
RevocationAttributes.createKeyRevocation().withReason(RevocationAttributes.Reason.KEY_RETIRED).withoutDescription());
|
||||||
|
|
||||||
|
assertEquals(1, minimalRevocationCert.size());
|
||||||
|
PGPPublicKey key = minimalRevocationCert.getPublicKey();
|
||||||
|
assertEquals(secretKeys.getPublicKey().getKeyID(), key.getKeyID());
|
||||||
|
assertEquals(1, CollectionUtils.iteratorToList(key.getSignatures()).size());
|
||||||
|
assertFalse(key.getUserIDs().hasNext());
|
||||||
|
assertFalse(key.getUserAttributes().hasNext());
|
||||||
|
assertNull(key.getTrustData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMinimalRevocationCertificate_wrongReason() throws PGPException, IOException {
|
||||||
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> PGPainless.modifyKeyRing(secretKeys).createMinimalRevocationCertificate(
|
||||||
|
SecretKeyRingProtector.unprotectedKeys(),
|
||||||
|
RevocationAttributes.createCertificateRevocation()
|
||||||
|
.withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID)
|
||||||
|
.withoutDescription()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue