diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java index 6f5ea044..774dcad0 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java @@ -59,6 +59,10 @@ public abstract class ImplementationFactory { public abstract PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException; + public PGPDigestCalculator getV4FingerprintCalculator() throws PGPException { + return getPGPDigestCalculator(HashAlgorithm.SHA1); + } + public PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException { return getPGPDigestCalculator(algorithm.getAlgorithmId()); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java index 85b95b92..205b7b4e 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java @@ -41,6 +41,7 @@ import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.provider.ProviderFactory; import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil; @@ -52,13 +53,13 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { private final Charset UTF8 = Charset.forName("UTF-8"); private PGPSignatureGenerator signatureGenerator; - private PGPDigestCalculator digestCalculator; + private PGPDigestCalculator keyFingerprintCalculator; private PBESecretKeyEncryptor secretKeyEncryptor; private KeySpec primaryKeySpec; private final List subkeySpecs = new ArrayList<>(); private final Set userIds = new LinkedHashSet<>(); - private Passphrase passphrase = null; + private Passphrase passphrase = Passphrase.emptyPassphrase(); private Date expirationDate = null; @Override @@ -126,13 +127,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { if (userIds.isEmpty()) { throw new IllegalStateException("At least one user-id is required."); } - digestCalculator = buildDigestCalculator(); + keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator(); secretKeyEncryptor = buildSecretKeyEncryptor(); PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor(); - if (passphrase != null) { - passphrase.clear(); - } + passphrase.clear(); // Generate Primary Key PGPKeyPair certKey = generateKeyPair(primaryKeySpec); @@ -171,8 +170,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { // "reassemble" secret key ring with modified primary key PGPSecretKey primarySecKey = new PGPSecretKey( - privateKey, - primaryPubKey, digestCalculator, true, secretKeyEncryptor); + privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor); List secretKeyList = new ArrayList<>(); secretKeyList.add(primarySecKey); while (secretKeys.hasNext()) { @@ -190,7 +188,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { String primaryUserId = userIds.iterator().next(); return new PGPKeyRingGenerator( SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey, - primaryUserId, digestCalculator, + primaryUserId, keyFingerprintCalculator, hashedSubPackets, null, signer, secretKeyEncryptor); } @@ -236,22 +234,20 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { private PBESecretKeyEncryptor buildSecretKeyEncryptor() { SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy() .getDefaultSymmetricKeyAlgorithm(); - PBESecretKeyEncryptor encryptor = passphrase == null || passphrase.isEmpty() ? - null : // unencrypted key pair, otherwise AES-256 encrypted + if (!passphrase.isValid()) { + throw new IllegalStateException("Passphrase was cleared."); + } + return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted ImplementationFactory.getInstance().getPBESecretKeyEncryptor( - keyEncryptionAlgorithm, digestCalculator, passphrase); - return encryptor; + keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase); } private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException { - PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ? - null : + if (!passphrase.isValid()) { + throw new IllegalStateException("Passphrase was cleared."); + } + return passphrase.isEmpty() ? null : ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); - return decryptor; - } - - private PGPDigestCalculator buildDigestCalculator() throws PGPException { - return ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1); } public static PGPKeyPair generateKeyPair(KeySpec spec) @@ -269,4 +265,30 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance().getPGPKeyPair(type.getAlgorithm(), keyPair, new Date()); return pgpKeyPair; } + + public static PGPSecretKey generatePGPSecretKey(KeySpec keySpec, @Nonnull Passphrase passphrase, boolean isPrimary) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance() + .getV4FingerprintCalculator(); + PGPKeyPair keyPair = generateKeyPair(keySpec); + SecretKeyRingProtector protector; + + synchronized (passphrase.lock) { + if (!passphrase.isValid()) { + throw new IllegalStateException("Passphrase has been cleared."); + } + if (!passphrase.isEmpty()) { + protector = SecretKeyRingProtector.unlockSingleKeyWith(passphrase, keyPair.getKeyID()); + } else { + protector = SecretKeyRingProtector.unprotectedKeys(); + } + + return new PGPSecretKey( + keyPair.getPrivateKey(), + keyPair.getPublicKey(), + keyFingerprintCalculator, + isPrimary, + protector.getEncryptor(keyPair.getKeyID())); + } + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 299721f7..cc20a156 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -41,7 +41,6 @@ import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.PublicKeyAlgorithm; import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.generation.KeyRingBuilder; @@ -182,28 +181,27 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { KeyFlag keyFlag, KeyFlag... additionalKeyFlags) throws PGPException, IOException { KeyFlag[] flags = concat(keyFlag, additionalKeyFlags); - SignatureSubpacketsUtil.assureKeyCanCarryFlags(PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm())); - - boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) || - CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER); - if (!isSigningKey) { - return addSubKey(subkey.getPublicKey(), - bindingSignatureCallback, - primaryKeyProtector, - keyFlag, - additionalKeyFlags); - } + PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm()); + SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm); PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - SubkeyBindingSignatureBuilder bindingSigBuilder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector); + SubkeyBindingSignatureBuilder bindingSigBuilder = + new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector); + bindingSigBuilder.applyCallback(bindingSignatureCallback); bindingSigBuilder.getHashedSubpackets().setKeyFlags(flags); - PrimaryKeyBindingSignatureBuilder backSigBuilder = new PrimaryKeyBindingSignatureBuilder(subkey, subkeyProtector); - backSigBuilder.applyCallback(backSignatureCallback); - PGPSignature backSig = backSigBuilder.build(primaryKey.getPublicKey()); + boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) || + CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER); + if (isSigningKey) { + // Add embedded back-signature made by subkey over primary key + PrimaryKeyBindingSignatureBuilder backSigBuilder = + new PrimaryKeyBindingSignatureBuilder(subkey, subkeyProtector); + backSigBuilder.applyCallback(backSignatureCallback); + PGPSignature backSig = backSigBuilder.build(primaryKey.getPublicKey()); + bindingSigBuilder.getHashedSubpackets().addEmbeddedSignature(backSig); + } - bindingSigBuilder.getHashedSubpackets().addEmbeddedSignature(backSig); PGPSignature bindingSig = bindingSigBuilder.build(subkey.getPublicKey()); subkey = KeyRingUtils.secretKeyPlusSignature(subkey, bindingSig); secretKeyRing = KeyRingUtils.secretKeysPlusSecretKey(secretKeyRing, subkey); @@ -211,54 +209,10 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { return this; } - @Override - public SecretKeyRingEditorInterface addSubKey(PGPPublicKey subkey, - SelfSignatureSubpackets.Callback bindingSignatureCallback, - SecretKeyRingProtector primaryKeyProtector, - KeyFlag keyFlag, - KeyFlag... additionalKeyFlags) throws PGPException { - KeyFlag[] flags = concat(keyFlag, additionalKeyFlags); - boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) || - CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER); - if (isSigningKey) { - throw new IllegalArgumentException("Cannot bind a signing capable subkey without access to the secret subkey.\n" + - "Please use addSubKey(PGPSecretKey secretSubKey, [...]) instead."); - } - - PGPSignature bindingSignature = createSubkeyBindingSignature(subkey, bindingSignatureCallback, primaryKeyProtector, flags); - subkey = PGPPublicKey.addCertification(subkey, bindingSignature); - - secretKeyRing = KeyRingUtils.secretKeysPlusPublicKey(secretKeyRing, subkey); - - return this; - } - - private PGPSignature createSubkeyBindingSignature(PGPPublicKey subkey, - SelfSignatureSubpackets.Callback bindingSignatureCallback, - SecretKeyRingProtector primaryKeyProtector, - KeyFlag... keyFlags) throws PGPException { - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - SubkeyBindingSignatureBuilder builder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector); - builder.applyCallback(bindingSignatureCallback); - builder.getHashedSubpackets().setKeyFlags(keyFlags); - - PGPSignature signature = builder.build(subkey); - return signature; - } - private PGPSecretKey generateSubKey(@Nonnull KeySpec keySpec, @Nonnull Passphrase subKeyPassphrase) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - PGPDigestCalculator checksumCalculator = ImplementationFactory.getInstance() - .getPGPDigestCalculator(defaultDigestHashAlgorithm); - - PBESecretKeyEncryptor subKeyEncryptor = subKeyPassphrase.isEmpty() ? null : - ImplementationFactory.getInstance().getPBESecretKeyEncryptor(SymmetricKeyAlgorithm.AES_256, subKeyPassphrase); - - PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); - PGPSecretKey secretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), - checksumCalculator, false, subKeyEncryptor); - return secretKey; + return KeyRingBuilder.generatePGPSecretKey(keySpec, subKeyPassphrase, false); } @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java index a1699eaa..541c0db8 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java @@ -12,7 +12,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; @@ -59,7 +58,7 @@ public interface SecretKeyRingEditorInterface { * @return the builder */ SecretKeyRingEditorInterface addSubKey(@Nonnull KeySpec keySpec, - @Nonnull Passphrase subKeyPassphrase, + @Nullable Passphrase subKeyPassphrase, SecretKeyRingProtector secretKeyRingProtector) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException; @@ -78,12 +77,6 @@ public interface SecretKeyRingEditorInterface { KeyFlag keyFlag, KeyFlag... additionalKeyFlags) throws PGPException, IOException; - SecretKeyRingEditorInterface addSubKey(PGPPublicKey subkey, - SelfSignatureSubpackets.Callback bindingSignatureCallback, - SecretKeyRingProtector primaryKeyProtector, - KeyFlag keyFlag, - KeyFlag... additionalKeyFlags) throws PGPException; - /** * Revoke the key ring. * The revocation will be a hard revocation, rendering the whole key invalid for any past or future signatures. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java index d0cdd59e..4a58b5f9 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java @@ -56,7 +56,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param keyId id of the key * @param passphrase passphrase */ - public void addPassphrase(@Nonnull Long keyId, @Nullable Passphrase passphrase) { + public void addPassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) { this.cache.put(keyId, passphrase); } @@ -66,7 +66,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param keyRing key ring * @param passphrase passphrase */ - public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nullable Passphrase passphrase) { + public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nonnull Passphrase passphrase) { Iterator keys = keyRing.getPublicKeys(); while (keys.hasNext()) { PGPPublicKey publicKey = keys.next(); @@ -80,11 +80,11 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param key key * @param passphrase passphrase */ - public void addPassphrase(@Nonnull PGPPublicKey key, @Nullable Passphrase passphrase) { + public void addPassphrase(@Nonnull PGPPublicKey key, @Nonnull Passphrase passphrase) { addPassphrase(key.getKeyID(), passphrase); } - public void addPassphrase(@Nonnull OpenPgpFingerprint fingerprint, @Nullable Passphrase passphrase) { + public void addPassphrase(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull Passphrase passphrase) { addPassphrase(fingerprint.getKeyId(), passphrase); } @@ -95,9 +95,10 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param keyId id of the key */ public void forgetPassphrase(@Nonnull Long keyId) { - Passphrase passphrase = cache.get(keyId); - passphrase.clear(); - cache.remove(keyId); + Passphrase passphrase = cache.remove(keyId); + if (passphrase != null) { + passphrase.clear(); + } } /** @@ -140,12 +141,13 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se @Override public boolean hasPassphrase(Long keyId) { - return cache.containsKey(keyId); + Passphrase passphrase = cache.get(keyId); + return passphrase != null && passphrase.isValid(); } @Override public boolean hasPassphraseFor(Long keyId) { - return cache.containsKey(keyId); + return hasPassphrase(keyId); } @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java index 2f97863f..d198c187 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java @@ -64,12 +64,16 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect } public static PasswordBasedSecretKeyRingProtector forKey(PGPSecretKey key, Passphrase passphrase) { + return forKeyId(key.getPublicKey().getKeyID(), passphrase); + } + + public static PasswordBasedSecretKeyRingProtector forKeyId(long singleKeyId, Passphrase passphrase) { KeyRingProtectionSettings protectionSettings = KeyRingProtectionSettings.secureDefaultSettings(); SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { - @Override @Nullable + @Override public Passphrase getPassphraseFor(Long keyId) { - if (key.getKeyID() == keyId) { + if (keyId == singleKeyId) { return passphrase; } return null; @@ -77,7 +81,7 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect @Override public boolean hasPassphrase(Long keyId) { - return keyId == key.getKeyID(); + return keyId == singleKeyId; } }; return new PasswordBasedSecretKeyRingProtector(protectionSettings, passphraseProvider); diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java index 73fb64fc..b1cb7fdf 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java @@ -98,6 +98,10 @@ public interface SecretKeyRingProtector { return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase); } + static SecretKeyRingProtector unlockSingleKeyWith(Passphrase passphrase, long keyId) { + return PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase); + } + /** * Protector for unprotected keys. * This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls, diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java b/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java index 1d33ea5b..bd2e203f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java @@ -10,7 +10,7 @@ import java.util.Arrays; public class Passphrase { - private final Object lock = new Object(); + public final Object lock = new Object(); private final char[] chars; private boolean valid = true; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubkeyWithModifiedBindingSignatureSubpackets.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubkeyWithModifiedBindingSignatureSubpackets.java new file mode 100644 index 00000000..4306298d --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubkeyWithModifiedBindingSignatureSubpackets.java @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// 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.assertTrue; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.List; + +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.junit.JUtils; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.EncryptionPurpose; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.generation.KeyRingBuilder; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.generation.type.xdh.XDHSpec; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; +import org.pgpainless.util.Passphrase; + +public class AddSubkeyWithModifiedBindingSignatureSubpackets { + + @Test + public void bindEncryptionSubkeyAndModifyBindingSignatureHashedSubpackets() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() + .modernKeyRing("Alice ", null); + KeyRingInfo before = PGPainless.inspectKeyRing(secretKeys); + + PGPSecretKey secretSubkey = KeyRingBuilder.generatePGPSecretKey( + KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS).build(), + Passphrase.emptyPassphrase(), false); + + secretKeys = PGPainless.modifyKeyRing(secretKeys) + .addSubKey(secretSubkey, new SelfSignatureSubpackets.Callback() { + @Override + public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { + hashedSubpackets.setKeyExpirationTime(true, 1000); + hashedSubpackets.addNotationData(false, "test@test.test", "test"); + } + }, null, SecretKeyRingProtector.unprotectedKeys(), protector, KeyFlag.ENCRYPT_COMMS) + .done(); + + KeyRingInfo after = PGPainless.inspectKeyRing(secretKeys); + + List encryptionKeys = after.getEncryptionSubkeys(EncryptionPurpose.COMMUNICATIONS); + encryptionKeys.removeAll(before.getEncryptionSubkeys(EncryptionPurpose.COMMUNICATIONS)); + assertFalse(encryptionKeys.isEmpty()); + assertEquals(1, encryptionKeys.size()); + + PGPPublicKey newKey = encryptionKeys.get(0); + JUtils.assertEquals(new Date().getTime() + 1000 * 1000, after.getSubkeyExpirationDate(new OpenPgpV4Fingerprint(newKey)).getTime(), 2000); + assertTrue(newKey.getSignatures().hasNext()); + PGPSignature binding = newKey.getSignatures().next(); + List notations = SignatureSubpacketsUtil.getHashedNotationData(binding); + assertEquals(1, notations.size()); + assertEquals("test@test.test", notations.get(0).getNotationName()); + } +}