From bec2fb5ce14a2f9fe41bbaa49462613c61305b3c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 22 Jan 2021 20:03:20 +0100 Subject: [PATCH] Increase test coverage by writing bunch of JUnit tests --- .../CallbackBasedKeyringProtector.java | 2 +- .../PassphraseMapKeyRingProtector.java | 3 + .../protection/SecretKeyRingProtector.java | 7 +- ...asterKey.java => KeyBelongsToKeyRing.java} | 8 +- .../main/java/org/pgpainless/util/BCUtil.java | 6 +- .../util/NotYetImplementedException.java | 20 --- .../java/org/pgpainless/util/Passphrase.java | 23 +++ .../pgpainless/util/SubpacketsInspector.java | 159 ------------------ .../pgpainless/key/info/KeyRingInfoTest.java | 13 +- .../key/info/UserIdRevocationTest.java | 1 - .../MapBasedPassphraseProviderTest.java | 55 ++++++ .../SecretKeyRingProtectorTest.java | 135 +++++++++++++++ .../key/AndOrSelectionStrategyTest.java | 62 +++++++ .../key/KeyBelongsToKeyRingTest.java | 54 ++++++ .../keyring/KeyRingsFromCollectionTest.java | 99 +++++++++++ 15 files changed, 457 insertions(+), 190 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/{SignedByMasterKey.java => KeyBelongsToKeyRing.java} (87%) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/NotYetImplementedException.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/SubpacketsInspector.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/protection/MapBasedPassphraseProviderTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/selection/key/AndOrSelectionStrategyTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/selection/key/KeyBelongsToKeyRingTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/selection/keyring/KeyRingsFromCollectionTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java index 75677bbe..6da9856d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CallbackBasedKeyringProtector.java @@ -40,7 +40,7 @@ public class CallbackBasedKeyringProtector implements SecretKeyRingProtector2 { @Override public PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException { Passphrase passphrase = lookupPassphraseInCache(key); - if (passphrase != null) { + if (passphrase == null) { passphrase = callback.getPassphraseFor(key); passphraseCache.put(key.getKeyID(), passphrase); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java index ad348a7e..81606632 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java @@ -72,6 +72,9 @@ public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, Se public Passphrase getPassphraseFor(Long keyId) { Passphrase passphrase = cache.get(keyId); if (passphrase == null || !passphrase.isValid()) { + if (provider == null) { + return null; + } passphrase = provider.getPassphraseFor(keyId); if (passphrase != null) { cache.put(keyId, passphrase); 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 f75bff25..006bfbcb 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 @@ -16,6 +16,7 @@ package org.pgpainless.key.protection; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; @@ -52,7 +53,11 @@ public interface SecretKeyRingProtector { @Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException; static SecretKeyRingProtector unlockAllKeysWith(Passphrase passphrase, PGPSecretKeyRing keys) { - return PasswordBasedSecretKeyRingProtector.forKey(keys, passphrase); + Map map = new ConcurrentHashMap<>(); + for (PGPSecretKey secretKey : keys) { + map.put(secretKey.getKeyID(), passphrase); + } + return fromPassphraseMap(map); } static SecretKeyRingProtector unlockSingleKeyWith(Passphrase passphrase, PGPSecretKey key) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/SignedByMasterKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/KeyBelongsToKeyRing.java similarity index 87% rename from pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/SignedByMasterKey.java rename to pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/KeyBelongsToKeyRing.java index c0f524a8..24ccf530 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/SignedByMasterKey.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/selection/key/impl/KeyBelongsToKeyRing.java @@ -24,12 +24,12 @@ import java.util.logging.Logger; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.selection.key.PublicKeySelectionStrategy; -public class SignedByMasterKey { +public class KeyBelongsToKeyRing { - private static final Logger LOGGER = Logger.getLogger(SignedByMasterKey.class.getName()); + private static final Logger LOGGER = Logger.getLogger(KeyBelongsToKeyRing.class.getName()); public static class PubkeySelectionStrategy extends PublicKeySelectionStrategy { @@ -51,7 +51,7 @@ public class SignedByMasterKey { PGPSignature signature = signatures.next(); if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) { try { - signature.init(new BcPGPContentVerifierBuilderProvider(), masterKey); + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), masterKey); return signature.verifyCertification(masterKey, key); } catch (PGPException e) { LOGGER.log(Level.WARNING, "Could not verify subkey signature of key " + diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java index 6ffe5b41..0ea23b38 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java @@ -42,7 +42,7 @@ import org.bouncycastle.util.io.Streams; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.key.selection.key.PublicKeySelectionStrategy; import org.pgpainless.key.selection.key.impl.NoRevocation; -import org.pgpainless.key.selection.key.impl.SignedByMasterKey; +import org.pgpainless.key.selection.key.impl.KeyBelongsToKeyRing; import org.pgpainless.key.selection.key.util.And; public class BCUtil { @@ -136,7 +136,7 @@ public class BCUtil { } // Only select keys which are signed by the master key and not revoked. PublicKeySelectionStrategy selector = new And.PubKeySelectionStrategy( - new SignedByMasterKey.PubkeySelectionStrategy(masterKey), + new KeyBelongsToKeyRing.PubkeySelectionStrategy(masterKey), new NoRevocation.PubKeySelectionStrategy()); PGPPublicKeyRing cleaned = ring; @@ -167,7 +167,7 @@ public class BCUtil { } // Only select keys which are signed by the master key and not revoked. PublicKeySelectionStrategy selector = new And.PubKeySelectionStrategy( - new SignedByMasterKey.PubkeySelectionStrategy(masterKey), + new KeyBelongsToKeyRing.PubkeySelectionStrategy(masterKey), new NoRevocation.PubKeySelectionStrategy()); PGPSecretKeyRing cleaned = ring; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/NotYetImplementedException.java b/pgpainless-core/src/main/java/org/pgpainless/util/NotYetImplementedException.java deleted file mode 100644 index ffdcc754..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/NotYetImplementedException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 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; - -public class NotYetImplementedException extends AssertionError { - -} 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 a46745df..14a52478 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java @@ -110,4 +110,27 @@ public class Passphrase { public static Passphrase emptyPassphrase() { return new Passphrase(null); } + + @Override + public int hashCode() { + if (getChars() == null) { + return 0; + } + return new String(getChars()).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof Passphrase)) { + return false; + } + Passphrase other = (Passphrase) obj; + return Arrays.equals(getChars(), other.getChars()); + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/SubpacketsInspector.java b/pgpainless-core/src/main/java/org/pgpainless/util/SubpacketsInspector.java deleted file mode 100644 index f19a1fca..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/SubpacketsInspector.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 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'); - } - -} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java index 8103962c..0c43ff22 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java @@ -105,9 +105,20 @@ public class KeyRingInfoTest { PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - assertEquals(secretKeys.getSecretKey(), info.getSecretKey()); + assertEquals(KeyRingUtils.requirePrimarySecretKeyFrom(secretKeys), info.getSecretKey()); info = PGPainless.inspectKeyRing(publicKeys); assertNull(info.getSecretKey()); } + + @Test + public void testGetPublicKey() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertEquals(KeyRingUtils.requirePrimaryPublicKeyFrom(secretKeys), info.getPublicKey()); + + assertEquals(KeyRingUtils.requirePrimarySecretKeyFrom(secretKeys), + KeyRingUtils.requireSecretKeyFrom(secretKeys, secretKeys.getPublicKey().getKeyID())); + } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java index c9987c4d..f758955b 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java @@ -113,7 +113,6 @@ public class UserIdRevocationTest { .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withDescription("I lost my mail password")) .done(); - PGPSignature s = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfSignatureOnKey(); PGPSignature signature = SignatureUtils.getLatestSelfSignatureForUserId(secretKeys.getPublicKey(), "secondary@key.id"); RevocationReason reason = (RevocationReason) signature.getHashedSubPackets() .getSubpacket(SignatureSubpacketTags.REVOCATION_REASON); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/MapBasedPassphraseProviderTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/MapBasedPassphraseProviderTest.java new file mode 100644 index 00000000..2960a1c6 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/MapBasedPassphraseProviderTest.java @@ -0,0 +1,55 @@ +/* + * 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.key.protection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.protection.passphrase_provider.MapBasedPassphraseProvider; +import org.pgpainless.util.Passphrase; + +public class MapBasedPassphraseProviderTest { + + @Test + public void testMapBasedProvider() throws IOException, PGPException { + Map passphraseMap = new ConcurrentHashMap<>(); + passphraseMap.put(1L, Passphrase.fromPassword("tiger")); + passphraseMap.put(123123123L, Passphrase.fromPassword("snake")); + passphraseMap.put(69696969L, Passphrase.emptyPassphrase()); + MapBasedPassphraseProvider provider = new MapBasedPassphraseProvider(passphraseMap); + + assertEquals(Passphrase.fromPassword("tiger"), provider.getPassphraseFor(1L)); + assertEquals(Passphrase.fromPassword("snake"), provider.getPassphraseFor(123123123L)); + assertEquals(Passphrase.emptyPassphrase(), provider.getPassphraseFor(69696969L)); + assertNull(provider.getPassphraseFor(555L)); + + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + passphraseMap = new ConcurrentHashMap<>(); + passphraseMap.put(secretKeys.getSecretKey().getKeyID(), TestKeys.CRYPTIE_PASSPHRASE); + provider = new MapBasedPassphraseProvider(passphraseMap); + + assertEquals(TestKeys.CRYPTIE_PASSPHRASE, provider.getPassphraseFor(secretKeys.getSecretKey())); + assertNull(provider.getPassphraseFor(TestKeys.getEmilSecretKeyRing().getSecretKey())); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java new file mode 100644 index 00000000..3e3332e3 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java @@ -0,0 +1,135 @@ +/* + * 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.key.protection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.Nullable; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; +import org.pgpainless.util.Passphrase; + +public class SecretKeyRingProtectorTest { + + @Test + public void testUnlockAllKeysWithSamePassword() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAllKeysWith(TestKeys.CRYPTIE_PASSPHRASE, secretKeys); + for (PGPSecretKey secretKey : secretKeys) { + PBESecretKeyDecryptor decryptor = protector.getDecryptor(secretKey.getKeyID()); + assertNotNull(decryptor); + secretKey.extractPrivateKey(decryptor); + } + PGPSecretKeyRing unrelatedKeys = PGPainless.generateKeyRing().simpleEcKeyRing("unrelated", + "SecurePassword"); + for (PGPSecretKey unrelatedKey : unrelatedKeys) { + PBESecretKeyDecryptor decryptor = protector.getDecryptor(unrelatedKey.getKeyID()); + assertNull(decryptor); + assertThrows(PGPException.class, () -> unrelatedKey.extractPrivateKey(protector.getDecryptor(unrelatedKey.getKeyID()))); + } + } + + @Test + public void testUnprotectedKeys() throws PGPException { + Random random = new Random(); + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + for (int i = 0; i < 10; i++) { + Long keyId = random.nextLong(); + assertNull(protector.getEncryptor(keyId)); + assertNull(protector.getDecryptor(keyId)); + } + } + + @Test + public void testUnlockSingleKeyWithPassphrase() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + Iterator iterator = secretKeys.iterator(); + PGPSecretKey secretKey = iterator.next(); + PGPSecretKey subKey = iterator.next(); + + SecretKeyRingProtector protector = SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, secretKey); + assertNotNull(protector.getDecryptor(secretKey.getKeyID())); + assertNotNull(protector.getEncryptor(secretKey.getKeyID())); + assertNull(protector.getEncryptor(subKey.getKeyID())); + assertNull(protector.getDecryptor(subKey.getKeyID())); + } + + @Test + public void testFromPassphraseMap() { + Map passphraseMap = new ConcurrentHashMap<>(); + passphraseMap.put(1L, Passphrase.emptyPassphrase()); + PassphraseMapKeyRingProtector protector = (PassphraseMapKeyRingProtector) SecretKeyRingProtector.fromPassphraseMap(passphraseMap); + + assertNotNull(protector.getPassphraseFor(1L)); + assertNull(protector.getPassphraseFor(5L)); + + protector.addPassphrase(5L, Passphrase.fromPassword("pa55w0rd")); + protector.forgetPassphrase(1L); + + assertNull(protector.getPassphraseFor(1L)); + assertNotNull(protector.getPassphraseFor(5L)); + } + + @Test + public void testMissingPassphraseCallback() { + Map passphraseMap = new ConcurrentHashMap<>(); + passphraseMap.put(1L, Passphrase.emptyPassphrase()); + PassphraseMapKeyRingProtector protector = new PassphraseMapKeyRingProtector(passphraseMap, + KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() { + @Nullable + @Override + public Passphrase getPassphraseFor(Long keyId) { + return Passphrase.fromPassword("missingP455w0rd"); + } + }); + + assertEquals(Passphrase.emptyPassphrase(), protector.getPassphraseFor(1L)); + assertEquals(Passphrase.fromPassword("missingP455w0rd"), protector.getPassphraseFor(3L)); + } + + @Test + public void testCallbackBasedKeyRingProtector() throws IOException, PGPException { + SecretKeyRingProtector2 protector = new CallbackBasedKeyringProtector(new CallbackBasedKeyringProtector.Callback() { + @Override + public Passphrase getPassphraseFor(PGPSecretKey secretKey) { + return TestKeys.CRYPTIE_PASSPHRASE; + } + }); + + PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); + for (PGPSecretKey secretKey : secretKeys) { + secretKey.extractPrivateKey(protector.getDecryptor(secretKey)); + } + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/AndOrSelectionStrategyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/AndOrSelectionStrategyTest.java new file mode 100644 index 00000000..cf55c6e7 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/AndOrSelectionStrategyTest.java @@ -0,0 +1,62 @@ +/* + * 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.key.selection.key; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.selection.key.impl.EncryptionKeySelectionStrategy; +import org.pgpainless.key.selection.key.impl.HasAnyKeyFlagSelectionStrategy; +import org.pgpainless.key.selection.key.util.Or; + +public class AndOrSelectionStrategyTest { + + @Test + public void testOr() throws IOException, PGPException { + PGPSecretKeyRing ring = TestKeys.getEmilSecretKeyRing(); + Iterator secretKeys = ring.getSecretKeys(); + Or.SecKeySelectionStrategy secStrategy = new Or.SecKeySelectionStrategy( + new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_COMMS), + new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_STORAGE) + ); + PGPSecretKey certSecKey = secretKeys.next(); + PGPSecretKey cryptSecKey = secretKeys.next(); + + assertFalse(secStrategy.accept(certSecKey)); + assertTrue(secStrategy.accept(cryptSecKey)); + + Iterator publicKeys = ring.getPublicKeys(); + Or.PubKeySelectionStrategy pubStrategy = new Or.PubKeySelectionStrategy( + new EncryptionKeySelectionStrategy(KeyFlag.ENCRYPT_COMMS), + new EncryptionKeySelectionStrategy(KeyFlag.ENCRYPT_STORAGE) + ); + PGPPublicKey certPubKey = publicKeys.next(); + PGPPublicKey cryptPubKey = publicKeys.next(); + + assertFalse(pubStrategy.accept(certPubKey)); + assertTrue(pubStrategy.accept(cryptPubKey)); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/KeyBelongsToKeyRingTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/KeyBelongsToKeyRingTest.java new file mode 100644 index 00000000..40db5a65 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/selection/key/KeyBelongsToKeyRingTest.java @@ -0,0 +1,54 @@ +/* + * 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.key.selection.key; + +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.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.selection.key.impl.KeyBelongsToKeyRing; + +public class KeyBelongsToKeyRingTest { + + @Test + public void testStrategyOnlyAcceptsKeysThatBelongToKeyRing() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { + PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("test@test.test"); + Iterator iterator = secretKeys.getPublicKeys(); + PGPPublicKey primaryKey = iterator.next(); + PGPPublicKey subKey = iterator.next(); + + KeyBelongsToKeyRing.PubkeySelectionStrategy strategy = new KeyBelongsToKeyRing.PubkeySelectionStrategy(primaryKey); + assertTrue(strategy.accept(primaryKey)); + assertTrue(strategy.accept(subKey)); + + PGPSecretKeyRing unrelatedKeys = TestKeys.getEmilSecretKeyRing(); + Iterator unrelated = unrelatedKeys.getPublicKeys(); + while (unrelated.hasNext()) { + PGPPublicKey unrelatedKey = unrelated.next(); + assertFalse(strategy.accept(unrelatedKey)); + } + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/selection/keyring/KeyRingsFromCollectionTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/selection/keyring/KeyRingsFromCollectionTest.java new file mode 100644 index 00000000..6a915459 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/selection/keyring/KeyRingsFromCollectionTest.java @@ -0,0 +1,99 @@ +/* + * 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.key.selection.keyring; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.junit.jupiter.api.Test; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.selection.keyring.impl.ExactUserId; +import org.pgpainless.util.MultiMap; + +public class KeyRingsFromCollectionTest { + + @Test + public void selectSecretKeyRingFromSecretKeyRingCollectionTest() throws IOException, PGPException { + PGPSecretKeyRing emil = TestKeys.getEmilSecretKeyRing(); + PGPSecretKeyRing juliet = TestKeys.getJulietSecretKeyRing(); + PGPSecretKeyRingCollection collection = new PGPSecretKeyRingCollection(Arrays.asList(emil, juliet)); + + SecretKeyRingSelectionStrategy strategy = new ExactUserId.SecRingSelectionStrategy(); + Set secretKeyRings = strategy.selectKeyRingsFromCollection(TestKeys.JULIET_UID, collection); + assertEquals(1, secretKeyRings.size()); + assertEquals(juliet.getPublicKey().getKeyID(), secretKeyRings.iterator().next().getPublicKey().getKeyID()); + } + + @Test + public void selectSecretKeyRingMapFromSecretKeyRingCollectionMapTest() throws IOException, PGPException { + PGPSecretKeyRing emil = TestKeys.getEmilSecretKeyRing(); + PGPSecretKeyRing juliet = TestKeys.getJulietSecretKeyRing(); + MultiMap map = new MultiMap<>(); + PGPSecretKeyRingCollection julietCollection = new PGPSecretKeyRingCollection(Arrays.asList(emil, juliet)); + map.put(TestKeys.JULIET_UID, julietCollection); + PGPSecretKeyRingCollection emilCollection = new PGPSecretKeyRingCollection(Collections.singletonList(emil)); + map.put(TestKeys.EMIL_UID, emilCollection); + assertEquals(2, julietCollection.size()); + map.put("invalidId", emilCollection); + + SecretKeyRingSelectionStrategy strategy = new ExactUserId.SecRingSelectionStrategy(); + MultiMap selected = strategy.selectKeyRingsFromCollections(map); + assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); + assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); + assertNull(selected.get("invalidId")); + } + + @Test + public void selectPublicKeyRingFromPublicKeyRingCollectionTest() throws IOException, PGPException { + PGPPublicKeyRing emil = TestKeys.getEmilPublicKeyRing(); + PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing(); + PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(Arrays.asList(emil, juliet)); + + PublicKeyRingSelectionStrategy strategy = new ExactUserId.PubRingSelectionStrategy(); + Set publicKeyRings = strategy.selectKeyRingsFromCollection(TestKeys.JULIET_UID, collection); + assertEquals(1, publicKeyRings.size()); + assertEquals(juliet.getPublicKey().getKeyID(), publicKeyRings.iterator().next().getPublicKey().getKeyID()); + } + + @Test + public void selectPublicKeyRingMapFromPublicKeyRingCollectionMapTest() throws IOException, PGPException { + PGPPublicKeyRing emil = TestKeys.getEmilPublicKeyRing(); + PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing(); + MultiMap map = new MultiMap<>(); + PGPPublicKeyRingCollection julietCollection = new PGPPublicKeyRingCollection(Arrays.asList(emil, juliet)); + map.put(TestKeys.JULIET_UID, julietCollection); + PGPPublicKeyRingCollection emilCollection = new PGPPublicKeyRingCollection(Collections.singletonList(emil)); + map.put(TestKeys.EMIL_UID, emilCollection); + assertEquals(2, julietCollection.size()); + map.put("invalidId", emilCollection); + + PublicKeyRingSelectionStrategy strategy = new ExactUserId.PubRingSelectionStrategy(); + MultiMap selected = strategy.selectKeyRingsFromCollections(map); + assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); + assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); + assertNull(selected.get("invalidId")); + } +}