001// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.parsing;
006
007import java.io.ByteArrayInputStream;
008import java.io.IOException;
009import java.io.InputStream;
010import java.nio.charset.Charset;
011import java.util.ArrayList;
012import java.util.Iterator;
013import java.util.List;
014import javax.annotation.Nonnull;
015
016import org.bouncycastle.openpgp.PGPException;
017import org.bouncycastle.openpgp.PGPMarker;
018import org.bouncycastle.openpgp.PGPObjectFactory;
019import org.bouncycastle.openpgp.PGPPublicKeyRing;
020import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
021import org.bouncycastle.openpgp.PGPSecretKeyRing;
022import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
023import org.bouncycastle.util.io.Streams;
024import org.pgpainless.implementation.ImplementationFactory;
025import org.pgpainless.key.collection.PGPKeyRingCollection;
026import org.pgpainless.util.ArmorUtils;
027
028public class KeyRingReader {
029
030    public static final int MAX_ITERATIONS = 10000;
031
032    @SuppressWarnings("CharsetObjectCanBeUsed")
033    public static final Charset UTF8 = Charset.forName("UTF-8");
034
035    public PGPPublicKeyRing publicKeyRing(@Nonnull InputStream inputStream) throws IOException {
036        return readPublicKeyRing(inputStream);
037    }
038
039    public PGPPublicKeyRing publicKeyRing(@Nonnull byte[] bytes) throws IOException {
040        return publicKeyRing(new ByteArrayInputStream(bytes));
041    }
042
043    public PGPPublicKeyRing publicKeyRing(@Nonnull String asciiArmored) throws IOException {
044        return publicKeyRing(asciiArmored.getBytes(UTF8));
045    }
046
047    public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull InputStream inputStream)
048            throws IOException, PGPException {
049        return readPublicKeyRingCollection(inputStream);
050    }
051
052    public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull byte[] bytes) throws IOException, PGPException {
053        return publicKeyRingCollection(new ByteArrayInputStream(bytes));
054    }
055
056    public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull String asciiArmored) throws IOException, PGPException {
057        return publicKeyRingCollection(asciiArmored.getBytes(UTF8));
058    }
059
060    public PGPSecretKeyRing secretKeyRing(@Nonnull InputStream inputStream) throws IOException {
061        return readSecretKeyRing(inputStream);
062    }
063
064    public PGPSecretKeyRing secretKeyRing(@Nonnull byte[] bytes) throws IOException {
065        return secretKeyRing(new ByteArrayInputStream(bytes));
066    }
067
068    public PGPSecretKeyRing secretKeyRing(@Nonnull String asciiArmored) throws IOException {
069        return secretKeyRing(asciiArmored.getBytes(UTF8));
070    }
071
072    public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull InputStream inputStream)
073            throws IOException, PGPException {
074        return readSecretKeyRingCollection(inputStream);
075    }
076
077    public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull byte[] bytes) throws IOException, PGPException {
078        return secretKeyRingCollection(new ByteArrayInputStream(bytes));
079    }
080
081    public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull String asciiArmored) throws IOException, PGPException {
082        return secretKeyRingCollection(asciiArmored.getBytes(UTF8));
083    }
084
085    public PGPKeyRingCollection keyRingCollection(@Nonnull InputStream inputStream, boolean isSilent)
086            throws IOException, PGPException {
087        return readKeyRingCollection(inputStream, isSilent);
088    }
089
090    public PGPKeyRingCollection keyRingCollection(@Nonnull byte[] bytes, boolean isSilent) throws IOException, PGPException {
091        return keyRingCollection(new ByteArrayInputStream(bytes), isSilent);
092    }
093
094    public PGPKeyRingCollection keyRingCollection(@Nonnull String asciiArmored, boolean isSilent) throws IOException, PGPException {
095        return keyRingCollection(asciiArmored.getBytes(UTF8), isSilent);
096    }
097
098    public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream) throws IOException {
099        return readPublicKeyRing(inputStream, MAX_ITERATIONS);
100    }
101
102    /**
103     * Read a public key ring from the provided {@link InputStream}.
104     * If more than maxIterations PGP packets are encountered before a {@link PGPPublicKeyRing} is read,
105     * an {@link IOException} is thrown.
106     *
107     * @param inputStream input stream
108     * @param maxIterations max iterations before abort
109     * @return public key ring
110     */
111    public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream, int maxIterations) throws IOException {
112        PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
113                ArmorUtils.getDecoderStream(inputStream));
114        int i = 0;
115        Object next;
116        do {
117            next = objectFactory.nextObject();
118            if (next == null) {
119                return null;
120            }
121            if (next instanceof PGPMarker) {
122                continue;
123            }
124            if (next instanceof PGPPublicKeyRing) {
125                return (PGPPublicKeyRing) next;
126            }
127        } while (++i < maxIterations);
128
129        throw new IOException("Loop exceeded max iteration count.");
130    }
131
132    public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream)
133            throws IOException, PGPException {
134        return readPublicKeyRingCollection(inputStream, MAX_ITERATIONS);
135    }
136
137    /**
138     * Read a public key ring collection from the provided {@link InputStream}.
139     * If more than maxIterations PGP packets are encountered before the stream is exhausted,
140     * an {@link IOException} is thrown.
141     *
142     * @param inputStream input stream
143     * @param maxIterations max iterations before abort
144     * @return public key ring collection
145     */
146    public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream, int maxIterations)
147            throws IOException, PGPException {
148        PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
149                ArmorUtils.getDecoderStream(inputStream));
150
151        List<PGPPublicKeyRing> rings = new ArrayList<>();
152        int i = 0;
153        Object next;
154        do {
155            next = objectFactory.nextObject();
156            if (next == null) {
157                return new PGPPublicKeyRingCollection(rings);
158            }
159            if (next instanceof PGPMarker) {
160                continue;
161            }
162            if (next instanceof PGPPublicKeyRing) {
163                rings.add((PGPPublicKeyRing) next);
164            }
165            if (next instanceof PGPPublicKeyRingCollection) {
166                PGPPublicKeyRingCollection collection = (PGPPublicKeyRingCollection) next;
167                Iterator<PGPPublicKeyRing> iterator = collection.getKeyRings();
168                while (iterator.hasNext()) {
169                    rings.add(iterator.next());
170                }
171            }
172        } while (++i < maxIterations);
173
174        throw new IOException("Loop exceeded max iteration count.");
175    }
176
177    public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream) throws IOException {
178        return readSecretKeyRing(inputStream, MAX_ITERATIONS);
179    }
180
181    /**
182     * Read a secret key ring from the provided {@link InputStream}.
183     * If more than maxIterations PGP packets are encountered before a {@link PGPSecretKeyRing} is read,
184     * an {@link IOException} is thrown.
185     *
186     * @param inputStream input stream
187     * @param maxIterations max iterations before abort
188     * @return public key ring
189     */
190    public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream, int maxIterations) throws IOException {
191        InputStream decoderStream = ArmorUtils.getDecoderStream(inputStream);
192        PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream);
193        int i = 0;
194        Object next;
195        do {
196            next = objectFactory.nextObject();
197            if (next == null) {
198                return null;
199            }
200            if (next instanceof PGPMarker) {
201                continue;
202            }
203            if (next instanceof PGPSecretKeyRing) {
204                Streams.drain(decoderStream);
205                return (PGPSecretKeyRing) next;
206            }
207        } while (++i < maxIterations);
208
209        throw new IOException("Loop exceeded max iteration count.");
210    }
211
212    public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream)
213            throws IOException, PGPException {
214        return readSecretKeyRingCollection(inputStream, MAX_ITERATIONS);
215    }
216
217    /**
218     * Read a secret key ring collection from the provided {@link InputStream}.
219     * If more than maxIterations PGP packets are encountered before the stream is exhausted,
220     * an {@link IOException} is thrown.
221     *
222     * @param inputStream input stream
223     * @param maxIterations max iterations before abort
224     * @return secret key ring collection
225     */
226    public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream,
227                                                                         int maxIterations)
228            throws IOException, PGPException {
229        PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
230                ArmorUtils.getDecoderStream(inputStream));
231
232        List<PGPSecretKeyRing> rings = new ArrayList<>();
233        int i = 0;
234        Object next;
235        do {
236            next = objectFactory.nextObject();
237            if (next == null) {
238                return new PGPSecretKeyRingCollection(rings);
239            }
240            if (next instanceof PGPMarker) {
241                continue;
242            }
243            if (next instanceof PGPSecretKeyRing) {
244                rings.add((PGPSecretKeyRing) next);
245            }
246            if (next instanceof PGPSecretKeyRingCollection) {
247                PGPSecretKeyRingCollection collection = (PGPSecretKeyRingCollection) next;
248                Iterator<PGPSecretKeyRing> iterator = collection.getKeyRings();
249                while (iterator.hasNext()) {
250                    rings.add(iterator.next());
251                }
252            }
253        } while (++i < maxIterations);
254
255        throw new IOException("Loop exceeded max iteration count.");
256    }
257
258    public static PGPKeyRingCollection readKeyRingCollection(@Nonnull InputStream inputStream, boolean isSilent)
259            throws IOException, PGPException {
260        return new PGPKeyRingCollection(inputStream, isSilent);
261    }
262}