Rework key modification API.

Fixes #225
This commit is contained in:
Paul Schaub 2021-12-20 12:46:31 +01:00
parent deb23fb7e3
commit 710f961984
13 changed files with 510 additions and 204 deletions

View File

@ -22,6 +22,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.bouncycastle.bcpg.sig.PrimaryUserID; import org.bouncycastle.bcpg.sig.PrimaryUserID;
import org.bouncycastle.bcpg.sig.RevocationReason;
import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
@ -38,9 +39,10 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.KeyValidationError; import org.pgpainless.exception.KeyValidationError;
import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.util.RevocationAttributes;
import org.pgpainless.policy.Policy; import org.pgpainless.policy.Policy;
import org.pgpainless.signature.consumer.SignaturePicker;
import org.pgpainless.signature.SignatureUtils; import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.signature.consumer.SignaturePicker;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
/** /**
@ -52,6 +54,8 @@ public class KeyRingInfo {
private final PGPKeyRing keys; private final PGPKeyRing keys;
private Signatures signatures; private Signatures signatures;
private final Date evaluationDate;
private final String primaryUserId;
/** /**
* Evaluate the key ring at creation time of the given signature. * Evaluate the key ring at creation time of the given signature.
@ -82,6 +86,8 @@ public class KeyRingInfo {
public KeyRingInfo(PGPKeyRing keys, Date validationDate) { public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
this.keys = keys; this.keys = keys;
this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy()); this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy());
this.evaluationDate = validationDate;
this.primaryUserId = findPrimaryUserId();
} }
/** /**
@ -251,45 +257,67 @@ public class KeyRingInfo {
return OpenPgpFingerprint.of(getPublicKey()); return OpenPgpFingerprint.of(getPublicKey());
} }
public @Nullable String getPrimaryUserId() {
return primaryUserId;
}
/** /**
* Return the primary user-id of the key ring. * Return the primary user-id of the key ring.
* *
* Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet,
* this method returns the first valid user-id, otherwise null. * this method returns the latest added user-id, otherwise null.
* *
* @return primary user-id or null * @return primary user-id or null
*/ */
public @Nullable String getPrimaryUserId() { private String findPrimaryUserId() {
String nonPrimaryUserId = null;
String primaryUserId = null; String primaryUserId = null;
Date modificationDate = null; Date modificationDate = null;
List<String> validUserIds = getValidUserIds(); List<String> userIds = getUserIds();
if (validUserIds.isEmpty()) { if (userIds.isEmpty()) {
return null; return null;
} }
for (String userId : validUserIds) { if (userIds.size() == 1) {
return userIds.get(0);
}
PGPSignature signature = signatures.userIdCertifications.get(userId); for (String userId : userIds) {
if (signature == null) { PGPSignature certification = signatures.userIdCertifications.get(userId);
if (certification == null) {
continue; continue;
} }
Date creationTime = certification.getCreationTime();
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature); if (certification.getHashedSubPackets().isPrimaryUserID()) {
if (subpacket != null && subpacket.isPrimaryUserID()) { if (nonPrimaryUserId != null) {
// if there are multiple primary userIDs, return most recently signed nonPrimaryUserId = null;
if (modificationDate == null || !signature.getCreationTime().before(modificationDate)) { modificationDate = null;
}
if (modificationDate == null || creationTime.after(modificationDate)) {
primaryUserId = userId; primaryUserId = userId;
modificationDate = signature.getCreationTime(); modificationDate = creationTime;
}
} else {
if (primaryUserId != null) {
continue;
}
if (modificationDate == null || creationTime.after(modificationDate)) {
nonPrimaryUserId = userId;
modificationDate = creationTime;
} }
} }
} }
// Workaround for keys with only one user-id but no primary user-id packet.
if (primaryUserId == null) { if (primaryUserId != null) {
return validUserIds.get(0); return primaryUserId;
} }
return primaryUserId; return nonPrimaryUserId;
} }
/** /**
@ -314,7 +342,7 @@ public class KeyRingInfo {
List<String> valid = new ArrayList<>(); List<String> valid = new ArrayList<>();
List<String> userIds = getUserIds(); List<String> userIds = getUserIds();
for (String userId : userIds) { for (String userId : userIds) {
if (isUserIdValid(userId)) { if (isUserIdBound(userId)) {
valid.add(userId); valid.add(userId);
} }
} }
@ -328,6 +356,18 @@ public class KeyRingInfo {
* @return true if user-id is valid * @return true if user-id is valid
*/ */
public boolean isUserIdValid(String userId) { public boolean isUserIdValid(String userId) {
if (!userId.equals(primaryUserId)) {
if (!isUserIdBound(primaryUserId)) {
// primary user-id not valid
return false;
}
}
return isUserIdBound(userId);
}
private boolean isUserIdBound(String userId) {
PGPSignature certification = signatures.userIdCertifications.get(userId); PGPSignature certification = signatures.userIdCertifications.get(userId);
PGPSignature revocation = signatures.userIdRevocations.get(userId); PGPSignature revocation = signatures.userIdRevocations.get(userId);
@ -338,6 +378,12 @@ public class KeyRingInfo {
if (SignatureUtils.isSignatureExpired(certification)) { if (SignatureUtils.isSignatureExpired(certification)) {
return false; return false;
} }
if (certification.getHashedSubPackets().isPrimaryUserID()) {
Date keyExpiration = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(certification, keys.getPublicKey());
if (keyExpiration != null && evaluationDate.after(keyExpiration)) {
return false;
}
}
// Not revoked -> valid // Not revoked -> valid
if (revocation == null) { if (revocation == null) {
return true; return true;
@ -588,7 +634,7 @@ public class KeyRingInfo {
} }
} }
PGPSignature primaryUserIdCertification = getLatestUserIdCertification(getPrimaryUserId()); PGPSignature primaryUserIdCertification = getLatestUserIdCertification(getPossiblyExpiredUserId());
if (primaryUserIdCertification != null) { if (primaryUserIdCertification != null) {
return SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(primaryUserIdCertification, getPublicKey()); return SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(primaryUserIdCertification, getPublicKey());
} }
@ -596,6 +642,38 @@ public class KeyRingInfo {
throw new NoSuchElementException("No suitable signatures found on the key."); throw new NoSuchElementException("No suitable signatures found on the key.");
} }
public String getPossiblyExpiredUserId() {
String validPrimaryUserId = getPrimaryUserId();
if (validPrimaryUserId != null) {
return validPrimaryUserId;
}
Date latestCreationTime = null;
String primaryUserId = null;
boolean foundPrimary = false;
for (String userId : getUserIds()) {
PGPSignature signature = getLatestUserIdCertification(userId);
if (signature == null) {
continue;
}
boolean isPrimary = signature.getHashedSubPackets().isPrimaryUserID();
if (foundPrimary && !isPrimary) {
continue;
}
Date creationTime = signature.getCreationTime();
if (latestCreationTime == null || creationTime.after(latestCreationTime) || isPrimary && !foundPrimary) {
latestCreationTime = creationTime;
primaryUserId = userId;
}
foundPrimary |= isPrimary;
}
return primaryUserId;
}
/** /**
* Return the expiration date of the subkey with the provided fingerprint. * Return the expiration date of the subkey with the provided fingerprint.
* *
@ -668,6 +746,15 @@ public class KeyRingInfo {
return primaryExpiration; return primaryExpiration;
} }
public boolean isHardRevoked(String userId) {
PGPSignature revocation = signatures.userIdRevocations.get(userId);
if (revocation == null) {
return false;
}
RevocationReason revocationReason = revocation.getHashedSubPackets().getRevocationReason();
return revocationReason == null || RevocationAttributes.Reason.isHardRevocation(revocationReason.getRevocationReason());
}
/** /**
* Return true if the key ring is a {@link PGPSecretKeyRing}. * Return true if the key ring is a {@link PGPSecretKeyRing}.
* If it is a {@link PGPPublicKeyRing} return false and if it is neither, throw an {@link AssertionError}. * If it is a {@link PGPPublicKeyRing} return false and if it is neither, throw an {@link AssertionError}.

View File

@ -22,17 +22,14 @@ import javax.annotation.Nullable;
import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SecretKeyPacket;
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.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
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;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
@ -47,7 +44,6 @@ import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeyRingBuilder;
import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.info.KeyRingInfo;
@ -55,18 +51,16 @@ import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.KeyRingProtectionSettings;
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.protection.fixes.S2KUsageFix; import org.pgpainless.key.protection.fixes.S2KUsageFix;
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
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.signature.SignatureUtils; import org.pgpainless.signature.builder.DirectKeySignatureBuilder;
import org.pgpainless.signature.builder.RevocationSignatureBuilder; import org.pgpainless.signature.builder.RevocationSignatureBuilder;
import org.pgpainless.signature.builder.SelfSignatureBuilder; import org.pgpainless.signature.builder.SelfSignatureBuilder;
import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets;
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
import org.pgpainless.signature.subpackets.SignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpackets;
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
@ -107,6 +101,9 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
// retain key flags from previous signature // retain key flags from previous signature
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing);
if (info.isHardRevoked(userId.toString())) {
throw new IllegalArgumentException("User-ID " + userId + " is hard revoked and cannot be re-certified.");
}
List<KeyFlag> keyFlags = info.getKeyFlagsOf(info.getKeyId()); List<KeyFlag> keyFlags = info.getKeyFlagsOf(info.getKeyId());
Set<HashAlgorithm> hashAlgorithmPreferences; Set<HashAlgorithm> hashAlgorithmPreferences;
@ -146,15 +143,54 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
public SecretKeyRingEditorInterface addPrimaryUserId( public SecretKeyRingEditorInterface addPrimaryUserId(
@Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector protector) @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector protector)
throws PGPException { throws PGPException {
return addUserId(
// 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(
userId, userId,
new SelfSignatureSubpackets.Callback() { new SelfSignatureSubpackets.Callback() {
@Override @Override
public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) {
hashedSubpackets.setPrimaryUserId(); hashedSubpackets.setPrimaryUserId();
if (previousKeyExpiration != null) {
hashedSubpackets.setKeyExpirationTime(primaryKey, previousKeyExpiration);
} else {
hashedSubpackets.setKeyExpirationTime(null);
}
} }
}, },
protector); protector);
// unmark previous primary user-ids to be non-primary
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing);
for (String otherUserId : info.getValidUserIds()) {
if (userId.toString().equals(otherUserId)) {
continue;
}
// We need to unmark this user-id as primary
if (info.getLatestUserIdCertification(otherUserId).getHashedSubPackets().isPrimaryUserID()) {
addUserId(otherUserId, new SelfSignatureSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) {
hashedSubpackets.setPrimaryUserId(null);
hashedSubpackets.setKeyExpirationTime(null); // non-primary
}
}, protector);
}
}
return this;
} }
// TODO: Move to utility class? // TODO: Move to utility class?
@ -468,118 +504,119 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
@Nullable Date expiration, @Nullable Date expiration,
@Nonnull SecretKeyRingProtector secretKeyRingProtector) @Nonnull SecretKeyRingProtector secretKeyRingProtector)
throws PGPException { throws PGPException {
return setExpirationDate(OpenPgpFingerprint.of(secretKeyRing), expiration, secretKeyRingProtector);
}
@Override
public SecretKeyRingEditorInterface setExpirationDate(
@Nonnull OpenPgpFingerprint fingerprint,
@Nullable Date expiration,
@Nonnull SecretKeyRingProtector secretKeyRingProtector)
throws PGPException {
List<PGPSecretKey> secretKeyList = new ArrayList<>();
PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
if (!primaryKey.isMasterKey()) { if (!primaryKey.isMasterKey()) {
throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key."); throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key.");
} }
boolean found = false; // reissue direct key sig
for (PGPSecretKey secretKey : secretKeyRing) { PGPSignature prevDirectKeySig = getPreviousDirectKeySignature();
// Skip over unaffected subkeys if (prevDirectKeySig != null) {
if (secretKey.getKeyID() != fingerprint.getKeyId()) { PGPSignature directKeySig = reissueDirectKeySignature(expiration, secretKeyRingProtector, prevDirectKeySig);
secretKeyList.add(secretKey); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryKey.getPublicKey(), directKeySig);
}
// reissue primary user-id sig
String primaryUserId = PGPainless.inspectKeyRing(secretKeyRing).getPossiblyExpiredUserId();
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);
for (String userId : info.getValidUserIds()) {
if (userId.equals(primaryUserId)) {
continue; continue;
} }
// We found the target subkey
found = true;
secretKey = setExpirationDate(primaryKey, secretKey, expiration, secretKeyRingProtector);
secretKeyList.add(secretKey);
}
if (!found) { PGPSignature prevUserIdSig = info.getLatestUserIdCertification(userId);
throw new IllegalArgumentException("Key Ring does not contain secret key with fingerprint " + fingerprint); if (prevUserIdSig == null) {
} throw new AssertionError("A valid user-id shall never have no user-id signature.");
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList); if (prevUserIdSig.getHashedSubPackets().isPrimaryUserID()) {
PGPSignature userIdSig = reissueNonPrimaryUserId(secretKeyRingProtector, userId, prevUserIdSig);
secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig);
}
}
return this; return this;
} }
private PGPSecretKey setExpirationDate(PGPSecretKey primaryKey, private PGPSignature reissueNonPrimaryUserId(
PGPSecretKey subjectKey, SecretKeyRingProtector secretKeyRingProtector,
Date expiration, String userId,
SecretKeyRingProtector secretKeyRingProtector) PGPSignature prevUserIdSig)
throws PGPException { throws PGPException {
SelfSignatureBuilder builder = new SelfSignatureBuilder(secretKeyRing.getSecretKey(), secretKeyRingProtector, prevUserIdSig);
if (expiration != null && expiration.before(subjectKey.getPublicKey().getCreationTime())) { builder.applyCallback(new SelfSignatureSubpackets.Callback() {
throw new IllegalArgumentException("Expiration date cannot be before creation date."); @Override
} public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) {
// unmark as primary
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(primaryKey, secretKeyRingProtector); hashedSubpackets.setPrimaryUserId(null);
PGPPublicKey subjectPubKey = subjectKey.getPublicKey();
PGPSignature oldSignature = getPreviousSignature(primaryKey, subjectPubKey);
PGPSignatureSubpacketVector oldSubpackets = oldSignature.getHashedSubPackets();
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(oldSubpackets);
SignatureSubpacketGeneratorUtil.setSignatureCreationTimeInSubpacketGenerator(new Date(), subpacketGenerator);
SignatureSubpacketGeneratorUtil.setKeyExpirationDateInSubpacketGenerator(
expiration, subjectPubKey.getCreationTime(), subpacketGenerator);
PGPSignatureGenerator signatureGenerator = SignatureUtils.getSignatureGeneratorFor(primaryKey);
signatureGenerator.setHashedSubpackets(subpacketGenerator.generate());
if (primaryKey.getKeyID() == subjectKey.getKeyID()) {
signatureGenerator.init(PGPSignature.POSITIVE_CERTIFICATION, privateKey);
for (Iterator<String> it = subjectKey.getUserIDs(); it.hasNext(); ) {
String userId = it.next();
PGPSignature signature = signatureGenerator.generateCertification(userId, subjectPubKey);
subjectPubKey = PGPPublicKey.addCertification(subjectPubKey, userId, signature);
} }
} else { });
signatureGenerator.init(PGPSignature.SUBKEY_BINDING, privateKey); return builder.build(secretKeyRing.getPublicKey(), userId);
PGPSignature signature = signatureGenerator.generateCertification(
primaryKey.getPublicKey(), subjectPubKey);
subjectPubKey = PGPPublicKey.addCertification(subjectPubKey, signature);
}
subjectKey = PGPSecretKey.replacePublicKey(subjectKey, subjectPubKey);
return subjectKey;
} }
private PGPSignature getPreviousSignature(PGPSecretKey primaryKey, PGPPublicKey subjectPubKey) { private PGPSignature reissuePrimaryUserIdSig(
PGPSignature oldSignature = null; @Nullable Date expiration,
if (primaryKey.getKeyID() == subjectPubKey.getKeyID()) { @Nonnull SecretKeyRingProtector secretKeyRingProtector,
Iterator<PGPSignature> keySignatures = subjectPubKey.getSignaturesForKeyID(primaryKey.getKeyID()); @Nonnull String primaryUserId,
while (keySignatures.hasNext()) { @Nonnull PGPSignature prevUserIdSig)
PGPSignature next = keySignatures.next(); throws PGPException {
SignatureType type = SignatureType.valueOf(next.getSignatureType()); PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
if (type == SignatureType.POSITIVE_CERTIFICATION || PGPPublicKey publicKey = primaryKey.getPublicKey();
type == SignatureType.CASUAL_CERTIFICATION ||
type == SignatureType.GENERIC_CERTIFICATION) { SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevUserIdSig);
oldSignature = next; 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();
DirectKeySignatureBuilder builder = new DirectKeySignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig);
builder.applyCallback(new SelfSignatureSubpackets.Callback() {
@Override
public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) {
if (expiration != null) {
hashedSubpackets.setKeyExpirationTime(keyCreationTime, expiration);
} else {
hashedSubpackets.setKeyExpirationTime(null);
} }
} }
if (oldSignature == null) { });
throw new IllegalStateException("Key " + OpenPgpFingerprint.of(subjectPubKey) +
" does not have a previous positive/casual/generic certification signature.");
}
} else {
Iterator<PGPSignature> bindingSignatures = subjectPubKey.getSignaturesOfType(
SignatureType.SUBKEY_BINDING.getCode());
while (bindingSignatures.hasNext()) {
oldSignature = bindingSignatures.next();
}
}
if (oldSignature == null) { return builder.build(publicKey);
throw new IllegalStateException("Key " + OpenPgpFingerprint.of(subjectPubKey) + }
" does not have a previous subkey binding signature.");
} private PGPSignature getPreviousDirectKeySignature() {
return oldSignature; KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing);
return info.getLatestDirectKeySelfSignature();
}
private PGPSignature getPreviousUserIdSignatures(String userId) {
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing);
return info.getLatestUserIdCertification(userId);
} }
@Override @Override

View File

@ -366,20 +366,6 @@ public interface SecretKeyRingEditorInterface {
@Nonnull SecretKeyRingProtector secretKeyRingProtector) @Nonnull SecretKeyRingProtector secretKeyRingProtector)
throws PGPException; throws PGPException;
/**
* Set key expiration time.
*
* @param fingerprint key that will have its expiration date adjusted
* @param expiration target expiration time or @{code null} for no expiration
* @param secretKeyRingProtector protector to unlock the priary key
* @return the builder
*/
SecretKeyRingEditorInterface setExpirationDate(
@Nonnull OpenPgpFingerprint fingerprint,
@Nullable Date expiration,
@Nonnull SecretKeyRingProtector secretKeyRingProtector)
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.
* *

View File

@ -21,6 +21,21 @@ public class SubkeyBindingSignatureBuilder extends AbstractSignatureBuilder<Subk
super(SignatureType.SUBKEY_BINDING, signingKey, protector); super(SignatureType.SUBKEY_BINDING, signingKey, protector);
} }
public SubkeyBindingSignatureBuilder(
PGPSecretKey signingKey,
SecretKeyRingProtector protector,
PGPSignature oldSubkeyBinding)
throws PGPException {
super(signingKey, protector, requireValidSignatureType(oldSubkeyBinding));
}
private static PGPSignature requireValidSignatureType(PGPSignature signature) {
if (signature.getSignatureType() == SignatureType.SUBKEY_BINDING.getCode()) {
return signature;
}
throw new IllegalArgumentException("Invalid signature type.");
}
@Override @Override
protected boolean isValidSignatureType(SignatureType type) { protected boolean isValidSignatureType(SignatureType type) {
return type == SignatureType.SUBKEY_BINDING; return type == SignatureType.SUBKEY_BINDING;

View File

@ -237,10 +237,6 @@ public final class SignaturePicker {
SignatureValidator.signatureIsCertification().verify(signature); SignatureValidator.signatureIsCertification().verify(signature);
SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
// if the currently latest signature is not yet expired, check if the next candidate is not yet expired
if (latestUserIdCert != null && !SignatureUtils.isSignatureExpired(latestUserIdCert, validationDate)) {
SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
}
SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature); SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature);
} catch (SignatureValidationException e) { } catch (SignatureValidationException e) {
// User-id certification is not valid // User-id certification is not valid

View File

@ -195,13 +195,14 @@ public final class SignatureSubpacketsUtil {
* @return key expiration time as date * @return key expiration time as date
*/ */
public static Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) { public static Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) {
if (signature.getKeyID() != signingKey.getKeyID()) {
throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")");
}
KeyExpirationTime subpacket = getKeyExpirationTime(signature); KeyExpirationTime subpacket = getKeyExpirationTime(signature);
if (subpacket == null) { if (subpacket == null) {
return null; return null;
} }
if (signature.getKeyID() != signingKey.getKeyID()) {
throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")");
}
return SignatureUtils.datePlusSeconds(signingKey.getCreationTime(), subpacket.getTime()); return SignatureUtils.datePlusSeconds(signingKey.getCreationTime(), subpacket.getTime());
} }

View File

@ -6,7 +6,6 @@ package org.pgpainless.example;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -26,7 +25,6 @@ import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.EncryptionPurpose; import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.exception.WrongPassphraseException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.ecc.EllipticCurve; import org.pgpainless.key.generation.type.ecc.EllipticCurve;
@ -223,33 +221,6 @@ public class ModifyKeys {
DateUtil.formatUTCDate(info.getExpirationDateForUse(KeyFlag.SIGN_DATA))); DateUtil.formatUTCDate(info.getExpirationDateForUse(KeyFlag.SIGN_DATA)));
} }
/**
* This example demonstrates how to set an expiration date for single subkeys.
*
* @throws PGPException
*/
@Test
public void setSubkeyExpirationDate() throws PGPException {
Date expirationDate = DateUtil.parseUTCDate("2032-01-13 22:30:01 UTC");
SecretKeyRingProtector protector = SecretKeyRingProtector
.unlockEachKeyWith(Passphrase.fromPassword(originalPassphrase), secretKey);
secretKey = PGPainless.modifyKeyRing(secretKey)
.setExpirationDate(
OpenPgpFingerprint.of(secretKey.getPublicKey(encryptionSubkeyId)),
expirationDate,
protector
)
.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
assertNull(info.getPrimaryKeyExpirationDate());
assertNull(info.getExpirationDateForUse(KeyFlag.SIGN_DATA));
assertEquals(DateUtil.formatUTCDate(expirationDate),
DateUtil.formatUTCDate(info.getExpirationDateForUse(KeyFlag.ENCRYPT_COMMS)));
}
/** /**
* This example demonstrates how to revoke a user-id on a key. * This example demonstrates how to revoke a user-id on a key.
* *

View File

@ -237,19 +237,15 @@ public class KeyRingInfoTest {
calendar.setTime(now); calendar.setTime(now);
calendar.add(Calendar.DATE, 10); calendar.add(Calendar.DATE, 10);
Date encryptionKeyExpiration = calendar.getTime(); // in 10 days
PGPSecretKey encryptionKey = keys.next(); PGPSecretKey encryptionKey = keys.next();
calendar.setTime(now); calendar.setTime(now);
calendar.add(Calendar.DATE, 3); calendar.add(Calendar.DATE, 3);
Date signingKeyExpiration = calendar.getTime(); // in 3 days
PGPSecretKey signingKey = keys.next(); PGPSecretKey signingKey = keys.next();
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
secretKeys = PGPainless.modifyKeyRing(secretKeys) secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new OpenPgpV4Fingerprint(primaryKey), primaryKeyExpiration, protector) .setExpirationDate(primaryKeyExpiration, protector)
.setExpirationDate(new OpenPgpV4Fingerprint(encryptionKey), encryptionKeyExpiration, protector)
.setExpirationDate(new OpenPgpV4Fingerprint(signingKey), signingKeyExpiration, protector)
.done(); .done();
KeyRingInfo info = new KeyRingInfo(secretKeys); KeyRingInfo info = new KeyRingInfo(secretKeys);
@ -267,7 +263,6 @@ public class KeyRingInfoTest {
assertEquals(primaryKey.getKeyID(), certKeys.get(0).getKeyID()); assertEquals(primaryKey.getKeyID(), certKeys.get(0).getKeyID());
assertEquals(primaryKeyExpiration.getTime(), info.getPrimaryKeyExpirationDate().getTime(), 5); assertEquals(primaryKeyExpiration.getTime(), info.getPrimaryKeyExpirationDate().getTime(), 5);
assertEquals(signingKeyExpiration.getTime(), info.getExpirationDateForUse(KeyFlag.SIGN_DATA).getTime(), 5);
// Encryption key expires after primary key, so we return primary key expiration instead. // Encryption key expires after primary key, so we return primary key expiration instead.
assertEquals(primaryKeyExpiration.getTime(), info.getExpirationDateForUse(KeyFlag.ENCRYPT_STORAGE).getTime(), 5); assertEquals(primaryKeyExpiration.getTime(), info.getExpirationDateForUse(KeyFlag.ENCRYPT_STORAGE).getTime(), 5);

View File

@ -9,12 +9,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.JUtils;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.TestKeys; import org.pgpainless.key.TestKeys;
@ -68,16 +70,17 @@ public class ChangeExpirationTest {
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys); KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate()); assertNull(sInfo.getPrimaryKeyExpirationDate());
Date date = DateUtil.parseUTCDate("2020-11-27 16:10:32 UTC"); Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DATE, 5);
Date expiration = calendar.getTime();
secretKeys = PGPainless.modifyKeyRing(secretKeys) secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done(); .setExpirationDate(expiration, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys); sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNotNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); assertNotNull(sInfo.getPrimaryKeyExpirationDate());
assertEquals(date.getTime(), sInfo.getSubkeyExpirationDate(subKeyFingerprint).getTime()); JUtils.assertDateEquals(expiration, sInfo.getPrimaryKeyExpirationDate());
assertNull(sInfo.getPrimaryKeyExpirationDate());
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second) // We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
// accuracy. Creating two signatures within a short amount of time will make the second one // accuracy. Creating two signatures within a short amount of time will make the second one
@ -85,10 +88,9 @@ public class ChangeExpirationTest {
Thread.sleep(1100); Thread.sleep(1100);
secretKeys = PGPainless.modifyKeyRing(secretKeys) secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done(); .setExpirationDate(null, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys); sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate()); assertNull(sInfo.getPrimaryKeyExpirationDate());
} }
} }

View File

@ -0,0 +1,224 @@
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>, 2021 Flowcrypt a.s.
//
// SPDX-License-Identifier: Apache-2.0
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.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;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
public class ChangePrimaryUserIdAndExpirationDatesTest {
@Test
public void generateA_primaryB_revokeA_cantSecondaryA()
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("A", null);
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertFalse(info.isHardRevoked("A"));
assertFalse(info.isHardRevoked("B"));
assertIsPrimaryUserId("A", info);
assertIsNotValid("B", info);
assertIsNotPrimaryUserId("B", info);
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.addPrimaryUserId("B", protector)
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("B", info);
assertIsNotPrimaryUserId("A", info);
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.revokeUserId("A", protector) // hard revoke A
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isHardRevoked("A"));
assertFalse(info.isHardRevoked("B"));
assertIsPrimaryUserId("B", info);
assertIsNotValid("A", info);
Thread.sleep(1000);
PGPSecretKeyRing finalSecretKeys = secretKeys;
assertThrows(IllegalArgumentException.class, () ->
PGPainless.modifyKeyRing(finalSecretKeys).addUserId("A", protector));
}
@Test
public void generateA_primaryExpire_isExpired()
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("A", null);
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("A", info);
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(), protector) // expire the whole key
.done();
Thread.sleep(1000);
info = PGPainless.inspectKeyRing(secretKeys);
assertFalse(info.isUserIdValid("A")); // is expired by now
}
@Test
public void generateA_primaryB_primaryExpire_bIsStillPrimary()
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("A", null);
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("A", info);
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.addPrimaryUserId("B", protector)
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("B", info);
assertIsNotPrimaryUserId("A", info);
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(new Date().getTime() + 1000), protector) // expire the whole key in 1 sec
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertIsValid("A", info);
assertIsValid("B", info);
assertIsPrimaryUserId("B", info);
assertIsNotPrimaryUserId("A", info);
Thread.sleep(2000);
info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("B", info); // B is still primary, even though
assertFalse(info.isUserIdValid("A")); // key is expired by now
assertFalse(info.isUserIdValid("B"));
}
@Test
public void generateA_expire_certify() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("A", null);
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(new Date().getTime() + 1000), protector)
.done();
Thread.sleep(2000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(new Date().getTime() + 2000), protector)
.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertIsValid("A", info);
assertIsPrimaryUserId("A", info);
}
@Test
public void generateA_expire_primaryB_expire_isPrimaryB()
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException, IOException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("A", null);
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(), protector)
.done();
Thread.sleep(2000);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("A", info);
assertIsNotValid("A", info);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.addPrimaryUserId("B", protector)
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertIsPrimaryUserId("B", info);
assertIsValid("B", info);
assertIsNotValid("A", info); // A is still expired
Thread.sleep(1000);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(new Date(new Date().getTime() + 10000), protector)
.done();
Thread.sleep(1000);
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
assertIsPrimaryUserId("B", info);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.addUserId("A", protector) // re-certify A as non-primary user-id
.done();
info = PGPainless.inspectKeyRing(secretKeys);
assertIsValid("B", info);
assertIsValid("A", info);
assertIsPrimaryUserId("B", info);
}
private static void assertIsPrimaryUserId(String userId, KeyRingInfo info) {
assertEquals(userId, info.getPrimaryUserId());
}
private static void assertIsNotPrimaryUserId(String userId, KeyRingInfo info) {
PGPSignature signature = info.getLatestUserIdCertification(userId);
if (signature == null) {
return;
}
assertFalse(signature.getHashedSubPackets().isPrimaryUserID());
}
private static void assertIsValid(String userId, KeyRingInfo info) {
assertTrue(info.isUserIdValid(userId));
}
private static void assertIsNotValid(String userId, KeyRingInfo info) {
assertFalse(info.isUserIdValid(userId));
}
}

View File

@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
@ -19,7 +20,6 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.util.TestAllImplementations; import org.pgpainless.util.TestAllImplementations;
@ -32,18 +32,22 @@ public class OldSignatureSubpacketsArePreservedOnNewSig {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.simpleEcKeyRing("Alice <alice@wonderland.lit>"); .simpleEcKeyRing("Alice <alice@wonderland.lit>");
OpenPgpV4Fingerprint subkeyFingerprint = new OpenPgpV4Fingerprint(PGPainless.inspectKeyRing(secretKeys).getPublicKeys().get(1)); PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getLatestUserIdCertification("Alice <alice@wonderland.lit>");
PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId());
PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets(); PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets();
assertEquals(0, oldPackets.getKeyExpirationTime()); assertEquals(0, oldPackets.getKeyExpirationTime());
Thread.sleep(1000); Thread.sleep(1000);
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.DATE, 5);
Date expiration = calendar.getTime(); // in 5 days
secretKeys = PGPainless.modifyKeyRing(secretKeys) secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(subkeyFingerprint, new Date(), new UnprotectedKeysProtector()) .setExpirationDate(expiration, new UnprotectedKeysProtector())
.done(); .done();
PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId()); PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getLatestUserIdCertification("Alice <alice@wonderland.lit>");
PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets(); PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets();
assertNotEquals(0, newPackets.getKeyExpirationTime()); assertNotEquals(0, newPackets.getKeyExpirationTime());

View File

@ -5,18 +5,14 @@
package org.pgpainless.key.modification; package org.pgpainless.key.modification;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.JUtils; import org.junit.JUtils;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface; import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
@ -106,24 +102,15 @@ public class RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey {
throws IOException, PGPException { throws IOException, PGPException {
Date expirationDate = DateUtil.parseUTCDate(DateUtil.formatUTCDate(new Date())); Date expirationDate = DateUtil.parseUTCDate(DateUtil.formatUTCDate(new Date()));
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
List<OpenPgpV4Fingerprint> fingerprintList = new ArrayList<>();
for (PGPSecretKey secretKey : secretKeys) {
fingerprintList.add(new OpenPgpV4Fingerprint(secretKey));
}
SecretKeyRingProtector protector = new UnprotectedKeysProtector(); SecretKeyRingProtector protector = new UnprotectedKeysProtector();
SecretKeyRingEditorInterface modify = PGPainless.modifyKeyRing(secretKeys) SecretKeyRingEditorInterface modify = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(expirationDate, protector); .setExpirationDate(expirationDate, protector);
for (int i = 1; i < fingerprintList.size(); i++) {
modify.setExpirationDate(fingerprintList.get(i), expirationDate, protector);
}
secretKeys = modify.done(); secretKeys = modify.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
JUtils.assertDateEquals(expirationDate, info.getPrimaryKeyExpirationDate()); JUtils.assertDateEquals(expirationDate, info.getPrimaryKeyExpirationDate());
for (OpenPgpV4Fingerprint fingerprint : fingerprintList) {
JUtils.assertDateEquals(expirationDate, info.getSubkeyExpirationDate(fingerprint));
}
} }
} }

View File

@ -60,7 +60,8 @@ public class SignatureSubpacketsUtilTest {
.setExpirationDate(expiration, SecretKeyRingProtector.unprotectedKeys()) .setExpirationDate(expiration, SecretKeyRingProtector.unprotectedKeys())
.done(); .done();
PGPSignature expirationSig = SignaturePicker.pickCurrentUserIdCertificationSignature(secretKeys, "Expire", Policy.getInstance(), new Date()); PGPSignature expirationSig = SignaturePicker.pickCurrentUserIdCertificationSignature(
secretKeys, "Expire", Policy.getInstance(), new Date());
PGPPublicKey notTheRightKey = PGPainless.inspectKeyRing(secretKeys).getSigningSubkeys().get(0); PGPPublicKey notTheRightKey = PGPainless.inspectKeyRing(secretKeys).getSigningSubkeys().get(0);
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->