diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java new file mode 100644 index 00000000..e7e772bd --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java @@ -0,0 +1,94 @@ +package org.pgpainless.key.collection; + +import org.bouncycastle.openpgp.*; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; + +import javax.annotation.Nonnull; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +/** + * This class describes a logic of handling a collection of different {@link PGPKeyRing}. The logic was inspired by + * {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection}. + */ +public class PGPKeyRingCollection { + private PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + private PGPPublicKeyRingCollection pgpPublicKeyRingCollection; + + public PGPKeyRingCollection(@Nonnull byte[] encoding, @Nonnull KeyFingerPrintCalculator fingerPrintCalculator, + boolean isSilent) throws IOException, PGPException { + this(new ByteArrayInputStream(encoding), fingerPrintCalculator, isSilent); + } + + /** + * Build a {@link PGPKeyRingCollection} from the passed in input stream. + * + * @param in input stream containing data + * @param isSilent flag indicating that unsupported objects will be ignored + * @throws IOException if a problem parsing the base stream occurs + * @throws PGPException if an object is encountered which isn't a {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing} + */ + public PGPKeyRingCollection(@Nonnull InputStream in, @Nonnull KeyFingerPrintCalculator fingerPrintCalculator, + boolean isSilent) throws IOException, PGPException { + PGPObjectFactory pgpFact = new PGPObjectFactory(in, fingerPrintCalculator); + Object obj; + + List secretKeyRings = new ArrayList<>(); + List publicKeyRings = new ArrayList<>(); + + while ((obj = pgpFact.nextObject()) != null) { + if (obj instanceof PGPSecretKeyRing) { + secretKeyRings.add((PGPSecretKeyRing) obj); + } else if (obj instanceof PGPPublicKeyRing) { + publicKeyRings.add((PGPPublicKeyRing) obj); + } else if (!isSilent) { + throw new PGPException(obj.getClass().getName() + " found where " + + PGPSecretKeyRing.class.getSimpleName() + " or " + + PGPPublicKeyRing.class.getSimpleName() + " expected"); + } + } + + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings); + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); + } + + public PGPKeyRingCollection(Collection collection, boolean isSilent) throws IOException, PGPException { + List secretKeyRings = new ArrayList<>(); + List publicKeyRings = new ArrayList<>(); + + for (PGPKeyRing pgpKeyRing : collection) { + if (pgpKeyRing instanceof PGPSecretKeyRing) { + secretKeyRings.add((PGPSecretKeyRing) pgpKeyRing); + } else if (pgpKeyRing instanceof PGPPublicKeyRing) { + publicKeyRings.add((PGPPublicKeyRing) pgpKeyRing); + } else if (!isSilent) { + throw new PGPException(pgpKeyRing.getClass().getName() + " found where " + + PGPSecretKeyRing.class.getSimpleName() + " or " + + PGPPublicKeyRing.class.getSimpleName() + " expected"); + } + } + + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings); + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); + } + + public PGPSecretKeyRingCollection getPGPSecretKeyRingCollection() { + return pgpSecretKeyRingCollection; + } + + public PGPPublicKeyRingCollection getPgpPublicKeyRingCollection() { + return pgpPublicKeyRingCollection; + } + + /** + * Return the number of rings in this collection. + * + * @return total size of {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection} + * in this collection + */ + public int size() { + return pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size(); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java index 66884cb2..0525eb5b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java @@ -29,6 +29,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.key.collection.PGPKeyRingCollection; public class KeyRingReader { @@ -84,6 +85,19 @@ public class KeyRingReader { return secretKeyRingCollection(asciiArmored.getBytes(UTF8)); } + public PGPKeyRingCollection keyRingCollection(@Nonnull InputStream inputStream, boolean isSilent) + throws IOException, PGPException { + return readKeyRingCollection(inputStream, isSilent); + } + + public PGPKeyRingCollection keyRingCollection(@Nonnull byte[] bytes, boolean isSilent) throws IOException, PGPException { + return keyRingCollection(new ByteArrayInputStream(bytes), isSilent); + } + + public PGPKeyRingCollection keyRingCollection(@Nonnull String asciiArmored, boolean isSilent) throws IOException, PGPException { + return keyRingCollection(asciiArmored.getBytes(UTF8), isSilent); + } + /* STATIC METHODS */ @@ -114,6 +128,14 @@ public class KeyRingReader { ImplementationFactory.getInstance().getKeyFingerprintCalculator()); } + public static PGPKeyRingCollection readKeyRingCollection(@Nonnull InputStream inputStream, boolean isSilent) + throws IOException, PGPException { + return new PGPKeyRingCollection( + getDecoderStream(inputStream), + ImplementationFactory.getInstance().getKeyFingerprintCalculator(), + isSilent); + } + private static void validateStreamsNotBothNull(InputStream publicIn, InputStream secretIn) { if (publicIn == null && secretIn == null) { throw new NullPointerException("publicIn and secretIn cannot be BOTH null."); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/parsing/KeyRingReaderTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/parsing/KeyRingReaderTest.java index 1a5c5877..3eeec7c1 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/parsing/KeyRingReaderTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/parsing/KeyRingReaderTest.java @@ -31,6 +31,7 @@ import org.bouncycastle.openpgp.*; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.key.collection.PGPKeyRingCollection; import org.pgpainless.key.util.KeyRingUtils; class KeyRingReaderTest { @@ -189,12 +190,42 @@ class KeyRingReaderTest { assertEquals(10, getPgpSecretKeyRingsFromResource("10_prv_keys_binary.key").size()); } + /** + * Many armored keys(private or pub) where each has own -----BEGIN PGP ... KEY BLOCK-----...-----END PGP ... KEY BLOCK----- + */ + @Test + void parseKeysMultiplyArmoredOwnHeader() throws IOException, PGPException, URISyntaxException { + assertEquals(10, getPGPKeyRingsFromResource("10_prv_and_pub_keys_armored_own_header.asc").size()); + } + + /** + * Many armored keys(private or pub) where each has own -----BEGIN PGP ... KEY BLOCK-----...-----END PGP ... KEY BLOCK----- + * Each of those blocks can have a different count of keys. + */ + @Test + void parseKeysMultiplyArmoredOwnWithSingleHeader() throws IOException, PGPException, URISyntaxException { + assertEquals(10, getPGPKeyRingsFromResource("10_prv_and_pub_keys_armored_own_with_single_header.asc").size()); + } + + /** + * Many binary keys(private or pub) + */ + @Test + void parseKeysMultiplyBinary() throws IOException, PGPException, URISyntaxException { + assertEquals(10, getPGPKeyRingsFromResource("10_prv_and_pub_keys_binary.key").size()); + } + private InputStream getFileInputStreamFromResource(String fileName) throws IOException, URISyntaxException { URL resource = getClass().getClassLoader().getResource(fileName); assert resource != null; return new FileInputStream(new File(resource.toURI())); } + private PGPKeyRingCollection getPGPKeyRingsFromResource(String fileName) + throws IOException, URISyntaxException, PGPException { + return PGPainless.readKeyRing().keyRingCollection(getFileInputStreamFromResource(fileName), true); + } + private PGPPublicKeyRingCollection getPgpPublicKeyRingsFromResource(String fileName) throws IOException, URISyntaxException, PGPException { return PGPainless.readKeyRing().publicKeyRingCollection(getFileInputStreamFromResource(fileName));