mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-23 12:52:07 +01:00
commit
2ad944977d
7 changed files with 514 additions and 45 deletions
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.algorithm;
|
||||||
|
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ATTESTED_CERTIFICATIONS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.CREATION_TIME;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EMBEDDED_SIGNATURE;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPIRE_TIME;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPORTABLE;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.FEATURES;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.INTENDED_RECIPIENT_FINGERPRINT;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_FINGERPRINT;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_KEY_ID;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_EXPIRE_TIME;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_FLAGS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_SERVER_PREFS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.NOTATION_DATA;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PLACEHOLDER;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.POLICY_URL;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_COMP_ALGS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_HASH_ALGS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_KEY_SERV;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_SYM_ALGS;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.PRIMARY_USER_ID;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REG_EXP;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCABLE;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_REASON;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNATURE_TARGET;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNER_USER_ID;
|
||||||
|
import static org.bouncycastle.bcpg.SignatureSubpacketTags.TRUST_SIG;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public enum SignatureSubpacket {
|
||||||
|
signatureCreationTime(CREATION_TIME),
|
||||||
|
signatureExpirationTime(EXPIRE_TIME),
|
||||||
|
exportableCertification(EXPORTABLE),
|
||||||
|
trustSignature(TRUST_SIG),
|
||||||
|
regularExpression(REG_EXP),
|
||||||
|
revocable(REVOCABLE),
|
||||||
|
keyExpirationTime(KEY_EXPIRE_TIME),
|
||||||
|
placeholder(PLACEHOLDER),
|
||||||
|
preferredSymmetricAlgorithms(PREFERRED_SYM_ALGS),
|
||||||
|
revocationKey(REVOCATION_KEY),
|
||||||
|
issuerKeyId(ISSUER_KEY_ID),
|
||||||
|
notationData(NOTATION_DATA),
|
||||||
|
preferredHashAlgorithms(PREFERRED_HASH_ALGS),
|
||||||
|
preferredCompressionAlgorithms(PREFERRED_COMP_ALGS),
|
||||||
|
keyServerPreferences(KEY_SERVER_PREFS),
|
||||||
|
preferredKeyServers(PREFERRED_KEY_SERV),
|
||||||
|
primaryUserId(PRIMARY_USER_ID),
|
||||||
|
policyUrl(POLICY_URL),
|
||||||
|
keyFlags(KEY_FLAGS),
|
||||||
|
signerUserId(SIGNER_USER_ID),
|
||||||
|
revocationReason(REVOCATION_REASON),
|
||||||
|
features(FEATURES),
|
||||||
|
signatureTarget(SIGNATURE_TARGET),
|
||||||
|
embeddedSignature(EMBEDDED_SIGNATURE),
|
||||||
|
issuerFingerprint(ISSUER_FINGERPRINT),
|
||||||
|
preferredAEADAlgorithms(PREFERRED_AEAD_ALGORITHMS),
|
||||||
|
intendedRecipientFingerprint(INTENDED_RECIPIENT_FINGERPRINT),
|
||||||
|
attestedCertification(ATTESTED_CERTIFICATIONS)
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final Map<Integer, SignatureSubpacket> MAP = new ConcurrentHashMap<>();
|
||||||
|
static {
|
||||||
|
for (SignatureSubpacket p : values()) {
|
||||||
|
MAP.put(p.code, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
SignatureSubpacket(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureSubpacket fromCode(int code) {
|
||||||
|
SignatureSubpacket tag = MAP.get(code);
|
||||||
|
if (tag == null) {
|
||||||
|
throw new IllegalArgumentException("No SignatureSubpacket tag found with code " + code);
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SignatureSubpacket> fromCodes(int[] codes) {
|
||||||
|
List<SignatureSubpacket> tags = new ArrayList<>();
|
||||||
|
for (int code : codes) {
|
||||||
|
tags.add(fromCode(code));
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
}
|
|
@ -215,7 +215,11 @@ public class KeyRingInfo {
|
||||||
* @return expiration date
|
* @return expiration date
|
||||||
*/
|
*/
|
||||||
public Date getExpirationDate() {
|
public Date getExpirationDate() {
|
||||||
long validSeconds = getPublicKey().getValidSeconds();
|
return getExpirationDate(new OpenPgpV4Fingerprint(getPublicKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getExpirationDate(OpenPgpV4Fingerprint fingerprint) {
|
||||||
|
long validSeconds = keys.getPublicKey(fingerprint.getKeyId()).getValidSeconds();
|
||||||
if (validSeconds == 0) {
|
if (validSeconds == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.pgpainless.key.util.KeyRingUtils;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
import org.pgpainless.key.util.SignatureUtils;
|
import org.pgpainless.key.util.SignatureUtils;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.SignatureSubpacketGeneratorUtil;
|
||||||
|
|
||||||
public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
|
|
||||||
|
@ -287,57 +288,120 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKeyRingEditorInterface setExpirationDate(Date expiration,
|
||||||
|
SecretKeyRingProtector secretKeyRingProtector)
|
||||||
|
throws PGPException {
|
||||||
|
return setExpirationDate(new OpenPgpV4Fingerprint(secretKeyRing), expiration, secretKeyRingProtector);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKeyRingEditorInterface setExpirationDate(OpenPgpV4Fingerprint fingerprint,
|
public SecretKeyRingEditorInterface setExpirationDate(OpenPgpV4Fingerprint fingerprint,
|
||||||
Date expiration,
|
Date expiration,
|
||||||
SecretKeyRingProtector secretKeyRingProtector)
|
SecretKeyRingProtector secretKeyRingProtector)
|
||||||
throws PGPException {
|
throws PGPException {
|
||||||
Iterator<PGPSecretKey> secretKeyIterator = secretKeyRing.getSecretKeys();
|
|
||||||
|
|
||||||
if (!secretKeyIterator.hasNext()) {
|
|
||||||
throw new NoSuchElementException("No secret keys in the ring.");
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPSecretKey secretKey = secretKeyIterator.next();
|
|
||||||
PGPPublicKey publicKey = secretKey.getPublicKey();
|
|
||||||
|
|
||||||
if (!new OpenPgpV4Fingerprint(publicKey).equals(fingerprint)) {
|
|
||||||
throw new IllegalArgumentException("Currently it is possible to adjust expiration date for primary key only.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
||||||
PGPPrivateKey privateKey = unlockSecretKey(secretKey, secretKeyRingProtector);
|
|
||||||
|
|
||||||
PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
|
PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
|
||||||
PGPSignatureGenerator signatureGenerator = SignatureUtils.getSignatureGeneratorFor(primaryKey);
|
if (!primaryKey.isMasterKey()) {
|
||||||
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
|
throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key.");
|
||||||
|
|
||||||
long secondsToExpire = 0; // 0 means "no expiration"
|
|
||||||
if (expiration != null) {
|
|
||||||
secondsToExpire = (expiration.getTime() - primaryKey.getPublicKey().getCreationTime().getTime()) / 1000;
|
|
||||||
}
|
|
||||||
subpacketGenerator.setKeyExpirationTime(false, secondsToExpire);
|
|
||||||
|
|
||||||
PGPSignatureSubpacketVector subPackets = subpacketGenerator.generate();
|
|
||||||
signatureGenerator.setHashedSubpackets(subPackets);
|
|
||||||
|
|
||||||
signatureGenerator.init(PGPSignature.POSITIVE_CERTIFICATION, privateKey);
|
|
||||||
|
|
||||||
Iterator<String> users = publicKey.getUserIDs();
|
|
||||||
while (users.hasNext()) {
|
|
||||||
String user = users.next();
|
|
||||||
PGPSignature signature = signatureGenerator.generateCertification(user, primaryKey.getPublicKey());
|
|
||||||
publicKey = PGPPublicKey.addCertification(publicKey, user, signature);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
secretKey = PGPSecretKey.replacePublicKey(secretKey, publicKey);
|
boolean found = false;
|
||||||
secretKeyList.add(secretKey);
|
Iterator<PGPSecretKey> iterator = secretKeyRing.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
PGPSecretKey secretKey = iterator.next();
|
||||||
|
|
||||||
|
// Skip over unaffected subkeys
|
||||||
|
if (secretKey.getKeyID() != fingerprint.getKeyId()) {
|
||||||
|
secretKeyList.add(secretKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We found the target subkey
|
||||||
|
found = true;
|
||||||
|
secretKey = setExpirationDate(primaryKey, secretKey, expiration, secretKeyRingProtector);
|
||||||
|
secretKeyList.add(secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
throw new IllegalArgumentException("Key Ring does not contain secret key with fingerprint " + fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
|
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PGPSecretKey setExpirationDate(PGPSecretKey primaryKey,
|
||||||
|
PGPSecretKey subjectKey,
|
||||||
|
Date expiration,
|
||||||
|
SecretKeyRingProtector secretKeyRingProtector)
|
||||||
|
throws PGPException {
|
||||||
|
|
||||||
|
if (expiration != null && expiration.before(subjectKey.getPublicKey().getCreationTime())) {
|
||||||
|
throw new IllegalArgumentException("Expiration date cannot be before creation date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPrivateKey privateKey = KeyRingUtils.unlockSecretKey(primaryKey, secretKeyRingProtector);
|
||||||
|
PGPPublicKey subjectPubKey = subjectKey.getPublicKey();
|
||||||
|
|
||||||
|
PGPSignature oldSignature = getPreviousSignature(primaryKey, subjectPubKey);
|
||||||
|
|
||||||
|
PGPSignatureSubpacketVector oldSubpackets = oldSignature.getHashedSubPackets();
|
||||||
|
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(oldSubpackets);
|
||||||
|
SignatureSubpacketGeneratorUtil.setSignatureCreationTimeInSubpacketGenerator(new Date(), subpacketGenerator);
|
||||||
|
SignatureSubpacketGeneratorUtil.setExpirationDateInSubpacketGenerator(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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
PGPSignature oldSignature = null;
|
||||||
|
if (primaryKey.getKeyID() == subjectPubKey.getKeyID()) {
|
||||||
|
Iterator<PGPSignature> keySignatures = subjectPubKey.getSignaturesForKeyID(primaryKey.getKeyID());
|
||||||
|
while (keySignatures.hasNext()) {
|
||||||
|
PGPSignature next = keySignatures.next();
|
||||||
|
if (next.getSignatureType() == PGPSignature.POSITIVE_CERTIFICATION) {
|
||||||
|
oldSignature = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldSignature == null) {
|
||||||
|
throw new IllegalStateException("Key " + new OpenPgpV4Fingerprint(subjectPubKey) + " does not have a previous positive signature.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Iterator bindingSignatures = subjectPubKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||||
|
while (bindingSignatures.hasNext()) {
|
||||||
|
oldSignature = (PGPSignature) bindingSignatures.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldSignature == null) {
|
||||||
|
throw new IllegalStateException("Key " + new OpenPgpV4Fingerprint(subjectPubKey) + " does not have a previous subkey binding signature.");
|
||||||
|
}
|
||||||
|
return oldSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PGPSignature createRevocationCertificate(OpenPgpV4Fingerprint fingerprint,
|
public PGPSignature createRevocationCertificate(OpenPgpV4Fingerprint fingerprint,
|
||||||
SecretKeyRingProtector secretKeyRingProtector,
|
SecretKeyRingProtector secretKeyRingProtector,
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.modification.secretkeyring;
|
package org.pgpainless.key.modification.secretkeyring;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Date;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -198,6 +198,10 @@ public interface SecretKeyRingEditorInterface {
|
||||||
RevocationAttributes revocationAttributes)
|
RevocationAttributes revocationAttributes)
|
||||||
throws PGPException;
|
throws PGPException;
|
||||||
|
|
||||||
|
SecretKeyRingEditorInterface setExpirationDate(Date expiration,
|
||||||
|
SecretKeyRingProtector secretKeyRingProtector)
|
||||||
|
throws PGPException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set key expiration time.
|
* Set key expiration time.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.util;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.SignatureSubpacket;
|
||||||
|
import org.bouncycastle.bcpg.SignatureSubpacketTags;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that helps dealing with BCs SignatureSubpacketGenerator class.
|
||||||
|
*/
|
||||||
|
public class SignatureSubpacketGeneratorUtil {
|
||||||
|
|
||||||
|
public static void removeAllPacketsOfType(org.pgpainless.algorithm.SignatureSubpacket subpacketType,
|
||||||
|
PGPSignatureSubpacketGenerator subpacketGenerator) {
|
||||||
|
removeAllPacketsOfType(subpacketType.getCode(), subpacketGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeAllPacketsOfType(int type, PGPSignatureSubpacketGenerator subpacketGenerator) {
|
||||||
|
for (SignatureSubpacket subpacket : subpacketGenerator.getSubpackets(type)) {
|
||||||
|
subpacketGenerator.removePacket(subpacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all occurrences of a signature creation time subpackets in the subpacket generator
|
||||||
|
* with a single new instance representing the provided date.
|
||||||
|
*
|
||||||
|
* @param date signature creation time
|
||||||
|
* @param subpacketGenerator subpacket generator
|
||||||
|
*/
|
||||||
|
public static void setSignatureCreationTimeInSubpacketGenerator(Date date, PGPSignatureSubpacketGenerator subpacketGenerator) {
|
||||||
|
removeAllPacketsOfType(SignatureSubpacketTags.CREATION_TIME, subpacketGenerator);
|
||||||
|
subpacketGenerator.setSignatureCreationTime(false, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all occurrences of key expiration time subpackets in the subpacket generator
|
||||||
|
* with a single instance representing the new expiration time.
|
||||||
|
*
|
||||||
|
* @param expirationDate expiration time as date or null for no expiration
|
||||||
|
* @param creationDate date on which the key was created
|
||||||
|
* @param subpacketGenerator subpacket generator
|
||||||
|
*/
|
||||||
|
public static void setExpirationDateInSubpacketGenerator(Date expirationDate,
|
||||||
|
@Nonnull Date creationDate,
|
||||||
|
PGPSignatureSubpacketGenerator subpacketGenerator) {
|
||||||
|
removeAllPacketsOfType(SignatureSubpacketTags.KEY_EXPIRE_TIME, subpacketGenerator);
|
||||||
|
long secondsToExpire = getKeyLifetimeInSeconds(expirationDate, creationDate);
|
||||||
|
subpacketGenerator.setKeyExpirationTime(true, secondsToExpire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the duration in seconds until the key expires after creation.
|
||||||
|
*
|
||||||
|
* @param expirationDate new expiration date
|
||||||
|
* @param creationTime key creation time
|
||||||
|
* @return life time of the key in seconds
|
||||||
|
*/
|
||||||
|
private static long getKeyLifetimeInSeconds(Date expirationDate, @Nonnull Date creationTime) {
|
||||||
|
long secondsToExpire = 0; // 0 means "no expiration"
|
||||||
|
if (expirationDate != null) {
|
||||||
|
if (creationTime.after(expirationDate)) {
|
||||||
|
throw new IllegalArgumentException("Key MUST NOT expire before being created. (creation: " + creationTime + ", expiration: " + expirationDate + ")");
|
||||||
|
}
|
||||||
|
secondsToExpire = (expirationDate.getTime() - creationTime.getTime()) / 1000;
|
||||||
|
}
|
||||||
|
return secondsToExpire;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.sig.Features;
|
||||||
|
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
|
||||||
|
import org.bouncycastle.bcpg.sig.NotationData;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
public class SubpacketsInspector {
|
||||||
|
|
||||||
|
public static StringBuilder toString(PGPSignatureSubpacketVector vector) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
optAppendSignatureCreationTime(sb, vector);
|
||||||
|
optAppendSignatureExpirationTime(sb, vector);
|
||||||
|
optAppendFlags(sb, vector);
|
||||||
|
optAppendFeatures(sb, vector);
|
||||||
|
optAppendIssuerKeyID(sb, vector);
|
||||||
|
optAppendSignerUserID(sb, vector);
|
||||||
|
optAppendKeyExpirationTime(sb, vector);
|
||||||
|
optAppendIntendedRecipientFingerprint(sb, vector);
|
||||||
|
optAppendNotationDataOccurrences(sb, vector);
|
||||||
|
optAppendCriticalTags(sb, vector);
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendCriticalTags(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
int[] criticalTagCodes = v.getCriticalTags();
|
||||||
|
if (criticalTagCodes.length == 0) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("Critical Tags: ").append('[');
|
||||||
|
for (int i = 0; i < criticalTagCodes.length; i++) {
|
||||||
|
int tag = criticalTagCodes[i];
|
||||||
|
try {
|
||||||
|
sb.append(SignatureSubpacket.fromCode(tag)).append(i == criticalTagCodes.length - 1 ? "" : ", ");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.append(']').append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendNotationDataOccurrences(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
NotationData[] notationData = v.getNotationDataOccurrences();
|
||||||
|
if (notationData.length == 0) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
sb.append("Notation Data: [").append('\n');
|
||||||
|
for (int i = 0; i < notationData.length; i++) {
|
||||||
|
NotationData n = notationData[i];
|
||||||
|
sb.append('\'').append(n.getNotationName())
|
||||||
|
.append("' = '").append(n.getNotationValue())
|
||||||
|
.append(i == notationData.length - 1 ? "'" : "', ");
|
||||||
|
}
|
||||||
|
return sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendSignatureCreationTime(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
return sb.append("Sig created: ").append(v.getSignatureCreationTime()).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendSignatureExpirationTime(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
long time = v.getSignatureExpirationTime();
|
||||||
|
sb.append("Sig expires: ");
|
||||||
|
if (time == 0) {
|
||||||
|
sb.append("never");
|
||||||
|
} else {
|
||||||
|
Date creationTime = v.getSignatureCreationTime();
|
||||||
|
if (creationTime != null) {
|
||||||
|
long seconds = creationTime.getTime() / 1000;
|
||||||
|
Date expirationDate = new Date((seconds + time) * 1000);
|
||||||
|
sb.append(expirationDate).append(" (").append(time).append(')');
|
||||||
|
} else {
|
||||||
|
sb.append(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendFlags(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
List<KeyFlag> flagList = KeyFlag.fromBitmask(v.getKeyFlags());
|
||||||
|
sb.append("Flags: ").append(Arrays.toString(flagList.toArray())).append('\n');
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendFeatures(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
Features features = v.getFeatures();
|
||||||
|
if (features == null) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
sb.append("Features: ");
|
||||||
|
sb.append('[');
|
||||||
|
if (features.supportsModificationDetection()) {
|
||||||
|
sb.append("Modification Detection");
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
return sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendIssuerKeyID(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
long keyId = v.getIssuerKeyID();
|
||||||
|
if (keyId == 0) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
return sb.append("Issuer KeyID: ").append(Long.toHexString(keyId)).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendSignerUserID(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
String userID = v.getSignerUserID();
|
||||||
|
if (userID == null) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
return sb.append("Signer UserID: ").append(userID).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendKeyExpirationTime(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
long expirationTime = v.getKeyExpirationTime();
|
||||||
|
sb.append("Key Expiration Time: ");
|
||||||
|
if (expirationTime == 0) {
|
||||||
|
sb.append("never");
|
||||||
|
} else {
|
||||||
|
sb.append(expirationTime).append(" seconds after creation");
|
||||||
|
}
|
||||||
|
return sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder optAppendIntendedRecipientFingerprint(StringBuilder sb, PGPSignatureSubpacketVector v) {
|
||||||
|
IntendedRecipientFingerprint fingerprint = v.getIntendedRecipientFingerprint();
|
||||||
|
if (fingerprint == null) {
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
return sb.append("Intended Recipient Fingerprint: ")
|
||||||
|
.append(new OpenPgpV4Fingerprint(fingerprint.getFingerprint()))
|
||||||
|
.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 Paul Schaub.
|
* Copyright 2021 Paul Schaub.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -33,28 +33,64 @@ import java.util.Date;
|
||||||
|
|
||||||
public class ChangeExpirationTest {
|
public class ChangeExpirationTest {
|
||||||
|
|
||||||
@Test
|
private final OpenPgpV4Fingerprint subKeyFingerprint = new OpenPgpV4Fingerprint("F73FDE6439ABE210B1AF4EDD273EF7A0C749807B");
|
||||||
public void setExpirationDateAndThenUnsetIt() throws PGPException, IOException, InterruptedException {
|
|
||||||
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpirationDateAndThenUnsetIt_OnPrimaryKey() throws PGPException, IOException, InterruptedException {
|
||||||
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
OpenPgpV4Fingerprint fingerprint = sInfo.getFingerprint();
|
|
||||||
|
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getExpirationDate());
|
||||||
|
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
|
||||||
Date date = new Date(1606493432000L);
|
Date date = new Date(1606493432000L);
|
||||||
secretKeys = PGPainless.modifyKeyRing(secretKeys).setExpirationDate(fingerprint, date, new UnprotectedKeysProtector()).done();
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
|
.setExpirationDate(date, new UnprotectedKeysProtector()).done();
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNotNull(sInfo.getExpirationDate());
|
assertNotNull(sInfo.getExpirationDate());
|
||||||
assertEquals(date.getTime(), sInfo.getExpirationDate().getTime());
|
assertEquals(date.getTime(), sInfo.getExpirationDate().getTime());
|
||||||
|
// subkey unchanged
|
||||||
|
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
|
||||||
// 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
|
||||||
// "invisible"
|
// "invisible"
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1100);
|
||||||
|
|
||||||
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
|
.setExpirationDate(null, new UnprotectedKeysProtector()).done();
|
||||||
|
|
||||||
secretKeys = PGPainless.modifyKeyRing(secretKeys).setExpirationDate(fingerprint, null, new UnprotectedKeysProtector()).done();
|
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getExpirationDate());
|
||||||
|
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpirationDateAndThenUnsetIt_OnSubkey() throws PGPException, IOException, InterruptedException {
|
||||||
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
|
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
|
||||||
|
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
assertNull(sInfo.getExpirationDate());
|
||||||
|
|
||||||
|
Date date = new Date(1606493432000L);
|
||||||
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
|
.setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done();
|
||||||
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
assertNotNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
assertEquals(date.getTime(), sInfo.getExpirationDate(subKeyFingerprint).getTime());
|
||||||
|
assertNull(sInfo.getExpirationDate());
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// "invisible"
|
||||||
|
Thread.sleep(1100);
|
||||||
|
|
||||||
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
|
.setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done();
|
||||||
|
|
||||||
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
||||||
|
assertNull(sInfo.getExpirationDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue