From c942238b4003c8e046bdb26093bd0ad88556b301 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 7 Sep 2021 18:19:34 +0200 Subject: [PATCH] Add tests for CachingSecretKeyRingProtector --- .../CachingSecretKeyRingProtector.java | 12 ++ .../CachingSecretKeyRingProtectorTest.java | 141 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java 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 009831b6..f1678230 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 @@ -41,6 +41,18 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se private final SecretKeyRingProtector protector; private final SecretKeyPassphraseProvider provider; + public CachingSecretKeyRingProtector() { + this(null); + } + + public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) { + this( + new HashMap<>(), + KeyRingProtectionSettings.secureDefaultSettings(), + missingPassphraseCallback + ); + } + public CachingSecretKeyRingProtector(@Nonnull Map passphrases, @Nonnull KeyRingProtectionSettings protectionSettings, @Nullable SecretKeyPassphraseProvider missingPassphraseCallback) { diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java new file mode 100644 index 00000000..598a4c0e --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java @@ -0,0 +1,141 @@ +/* + * 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 java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Random; +import javax.annotation.Nullable; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; +import org.pgpainless.util.Passphrase; + +public class CachingSecretKeyRingProtectorTest { + + // Dummy passphrase callback that returns the doubled key-id as passphrase + private final SecretKeyPassphraseProvider dummyCallback = new SecretKeyPassphraseProvider() { + @Nullable + @Override + public Passphrase getPassphraseFor(Long keyId) { + long doubled = keyId * 2; + return Passphrase.fromPassword(Long.toString(doubled)); + } + }; + + private CachingSecretKeyRingProtector protector; + + @BeforeEach + public void resetProtectors() { + protector = new CachingSecretKeyRingProtector(); + } + + @Test + public void noCallbackReturnsNullForUnknownKeyId() throws PGPException { + assertNull(protector.getDecryptor(123L)); + assertNull(protector.getEncryptor(123L)); + } + + @Test + public void testAddPassphrase() throws PGPException { + Passphrase passphrase = Passphrase.fromPassword("HelloWorld"); + protector.addPassphrase(123L, passphrase); + assertEquals(passphrase, protector.getPassphraseFor(123L)); + assertNotNull(protector.getEncryptor(123L)); + assertNotNull(protector.getDecryptor(123L)); + + assertNull(protector.getPassphraseFor(999L)); + } + + @Test + public void testForgetPassphrase() { + Passphrase passphrase = Passphrase.fromPassword("amnesiac"); + protector.addPassphrase(123L, passphrase); + assertEquals(passphrase, protector.getPassphraseFor(123L)); + protector.forgetPassphrase(123L); + assertNull(protector.getPassphraseFor(123L)); + } + + @Test + public void testAddPassphraseForKeyRing() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing keys = PGPainless.generateKeyRing() + .modernKeyRing("test@test.test", "Passphrase123"); + Passphrase passphrase = Passphrase.fromPassword("Passphrase123"); + + protector.addPassphrase(keys, passphrase); + Iterator it = keys.getSecretKeys(); + while (it.hasNext()) { + PGPSecretKey key = it.next(); + assertEquals(passphrase, protector.getPassphraseFor(key)); + assertNotNull(protector.getEncryptor(key.getKeyID())); + assertNotNull(protector.getDecryptor(key.getKeyID())); + } + + long nonMatching = findNonMatchingKeyId(keys); + assertNull(protector.getPassphraseFor(nonMatching)); + + protector.forgetPassphrase(keys); + it = keys.getSecretKeys(); + while (it.hasNext()) { + PGPSecretKey key = it.next(); + assertNull(protector.getPassphraseFor(key)); + assertNull(protector.getEncryptor(key.getKeyID())); + assertNull(protector.getDecryptor(key.getKeyID())); + } + } + + private static long findNonMatchingKeyId(PGPKeyRing keyRing) { + Random random = new Random(); + long nonMatchingKeyId = 123L; + outerloop: while (true) { + Iterator pubKeys = keyRing.getPublicKeys(); + while (pubKeys.hasNext()) { + if (pubKeys.next().getKeyID() == nonMatchingKeyId) { + nonMatchingKeyId = random.nextLong(); + continue outerloop; + } + } + return nonMatchingKeyId; + } + } + + @Test + public void testProtectorWithCallback() { + CachingSecretKeyRingProtector withCallback = new CachingSecretKeyRingProtector(dummyCallback); + + for (int i = -5; i <= 5; i++) { + long x = i * 5; + long doubled = x * 2; + + Passphrase passphrase = withCallback.getPassphraseFor(x); + assertNotNull(passphrase); + assertEquals(doubled, Long.valueOf(new String(passphrase.getChars()))); + } + } + +}