001/*
002 * Copyright 2018 Paul Schaub.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.pgpainless.util;
017
018import javax.annotation.Nonnull;
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Arrays;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.Set;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.bouncycastle.openpgp.PGPException;
031import org.bouncycastle.openpgp.PGPKeyRing;
032import org.bouncycastle.openpgp.PGPPublicKey;
033import org.bouncycastle.openpgp.PGPPublicKeyRing;
034import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
035import org.bouncycastle.openpgp.PGPSecretKey;
036import org.bouncycastle.openpgp.PGPSecretKeyRing;
037import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
038import org.bouncycastle.openpgp.PGPSignature;
039import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
040import org.bouncycastle.openpgp.PGPUtil;
041import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
042import org.bouncycastle.util.io.Streams;
043import org.pgpainless.algorithm.KeyFlag;
044import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
045import org.pgpainless.key.selection.key.impl.And;
046import org.pgpainless.key.selection.key.impl.NoRevocation;
047import org.pgpainless.key.selection.key.impl.SignedByMasterKey;
048
049public class BCUtil {
050
051    private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName());
052
053    /*
054    PGPXxxKeyRing -> PGPXxxKeyRingCollection
055     */
056    public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPPublicKeyRing... rings)
057            throws IOException, PGPException {
058        return new PGPPublicKeyRingCollection(Arrays.asList(rings));
059    }
060
061    public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPSecretKeyRing... rings)
062            throws IOException, PGPException {
063        return new PGPSecretKeyRingCollection(Arrays.asList(rings));
064    }
065
066    public static PGPPublicKeyRing publicKeyRingFromSecretKeyRing(@Nonnull PGPSecretKeyRing secretKeys)
067            throws PGPException, IOException {
068        PGPSecretKeyRing fixedSecretKeys = KeyRingSubKeyFix.repairSubkeyPackets(secretKeys, null, null);
069
070        ByteArrayOutputStream buffer = new ByteArrayOutputStream(512);
071        for (PGPSecretKey secretKey : fixedSecretKeys) {
072            PGPPublicKey publicKey = secretKey.getPublicKey();
073            if (publicKey != null) {
074                publicKey.encode(buffer, false);
075            }
076        }
077
078        return new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator());
079    }
080
081    /*
082    PGPXxxKeyRingCollection -> PGPXxxKeyRing
083     */
084
085    public static PGPSecretKeyRing getKeyRingFromCollection(@Nonnull PGPSecretKeyRingCollection collection,
086                                                            @Nonnull Long id)
087            throws PGPException {
088        PGPSecretKeyRing uncleanedRing = collection.getSecretKeyRing(id);
089
090        // Determine ids of signed keys
091        Set<Long> signedKeyIds = new HashSet<>();
092        signedKeyIds.add(id); // Add the signing key itself
093        Iterator<PGPPublicKey> signedPubKeys = uncleanedRing.getKeysWithSignaturesBy(id);
094        while (signedPubKeys.hasNext()) {
095            signedKeyIds.add(signedPubKeys.next().getKeyID());
096        }
097
098        PGPSecretKeyRing cleanedRing = uncleanedRing;
099        Iterator<PGPSecretKey> secretKeys = uncleanedRing.getSecretKeys();
100        while (secretKeys.hasNext()) {
101            PGPSecretKey secretKey = secretKeys.next();
102            if (!signedKeyIds.contains(secretKey.getKeyID())) {
103                cleanedRing = PGPSecretKeyRing.removeSecretKey(cleanedRing, secretKey);
104            }
105        }
106        return cleanedRing;
107    }
108
109    public static PGPPublicKeyRing getKeyRingFromCollection(@Nonnull PGPPublicKeyRingCollection collection,
110                                                            @Nonnull Long id)
111            throws PGPException {
112        PGPPublicKey key = collection.getPublicKey(id);
113        return removeUnassociatedKeysFromKeyRing(collection.getPublicKeyRing(id), key);
114    }
115
116    public static InputStream getPgpDecoderInputStream(@Nonnull byte[] bytes)
117            throws IOException {
118        return getPgpDecoderInputStream(new ByteArrayInputStream(bytes));
119    }
120
121    public static InputStream getPgpDecoderInputStream(@Nonnull InputStream inputStream)
122            throws IOException {
123        return PGPUtil.getDecoderStream(inputStream);
124    }
125
126    public static byte[] getDecodedBytes(@Nonnull byte[] bytes)
127            throws IOException {
128        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
129        Streams.pipeAll(getPgpDecoderInputStream(bytes), buffer);
130        return buffer.toByteArray();
131    }
132
133    public static byte[] getDecodedBytes(@Nonnull InputStream inputStream)
134            throws IOException {
135        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
136        Streams.pipeAll(inputStream, buffer);
137        return getDecodedBytes(buffer.toByteArray());
138    }
139
140    /**
141     * Remove all keys from the key ring, are either not having a subkey signature from the master key
142     * (identified by {@code masterKeyId}), or are revoked ("normal" key revocation, as well as subkey revocation).
143     *
144     * @param ring key ring
145     * @param masterKey master key
146     * @return "cleaned" key ring
147     */
148    public static PGPPublicKeyRing removeUnassociatedKeysFromKeyRing(@Nonnull PGPPublicKeyRing ring,
149                                                                     @Nonnull PGPPublicKey masterKey) {
150        if (!masterKey.isMasterKey()) {
151            throw new IllegalArgumentException("Given key is not a master key.");
152        }
153        // Only select keys which are signed by the master key and not revoked.
154        PublicKeySelectionStrategy<PGPPublicKey> selector = new And.PubKeySelectionStrategy<>(
155                new SignedByMasterKey.PubkeySelectionStrategy(),
156                new NoRevocation.PubKeySelectionStrategy<>());
157
158        PGPPublicKeyRing cleaned = ring;
159
160        Iterator<PGPPublicKey> publicKeys = ring.getPublicKeys();
161        while (publicKeys.hasNext()) {
162            PGPPublicKey publicKey = publicKeys.next();
163            if (!selector.accept(masterKey, publicKey)) {
164                cleaned = PGPPublicKeyRing.removePublicKey(cleaned, publicKey);
165            }
166        }
167
168        return cleaned;
169    }
170
171    /**
172     * Remove all keys from the key ring, are either not having a subkey signature from the master key
173     * (identified by {@code masterKeyId}), or are revoked ("normal" key revocation, as well as subkey revocation).
174     *
175     * @param ring key ring
176     * @param masterKey master key
177     * @return "cleaned" key ring
178     */
179    public static PGPSecretKeyRing removeUnassociatedKeysFromKeyRing(@Nonnull PGPSecretKeyRing ring,
180                                                                     @Nonnull PGPPublicKey masterKey) {
181        if (!masterKey.isMasterKey()) {
182            throw new IllegalArgumentException("Given key is not a master key.");
183        }
184        // Only select keys which are signed by the master key and not revoked.
185        PublicKeySelectionStrategy<PGPPublicKey> selector = new And.PubKeySelectionStrategy<>(
186                new SignedByMasterKey.PubkeySelectionStrategy(),
187                new NoRevocation.PubKeySelectionStrategy<>());
188
189        PGPSecretKeyRing cleaned = ring;
190
191        Iterator<PGPSecretKey> secretKeys = ring.getSecretKeys();
192        while (secretKeys.hasNext()) {
193            PGPSecretKey secretKey = secretKeys.next();
194            if (!selector.accept(masterKey, secretKey.getPublicKey())) {
195                cleaned = PGPSecretKeyRing.removeSecretKey(cleaned, secretKey);
196            }
197        }
198
199        return cleaned;
200    }
201
202    /**
203     * Return the {@link PGPPublicKey} which is the master key of the key ring.
204     *
205     * @param ring key ring
206     * @return master key
207     */
208    public static PGPPublicKey getMasterKeyFrom(@Nonnull PGPPublicKeyRing ring) {
209        Iterator<PGPPublicKey> it = ring.getPublicKeys();
210        while (it.hasNext()) {
211            PGPPublicKey k = it.next();
212            if (k.isMasterKey()) {
213                // There can only be one master key, so we can immediately return
214                return k;
215            }
216        }
217        return null;
218    }
219
220    public static PGPPublicKey getMasterKeyFrom(@Nonnull PGPKeyRing ring) {
221        Iterator it = ring.getPublicKeys();
222        while (it.hasNext()) {
223            PGPPublicKey k = (PGPPublicKey) it.next();
224            if (k.isMasterKey()) {
225                // There can only be one master key, so we can immediately return
226                return k;
227            }
228        }
229        return null;
230    }
231
232    public static Set<Long> signingKeyIds(@Nonnull PGPSecretKeyRing ring) {
233        Set<Long> ids = new HashSet<>();
234        Iterator<PGPPublicKey> it = ring.getPublicKeys();
235        while (it.hasNext()) {
236            PGPPublicKey k = it.next();
237
238            boolean signingKey = false;
239
240            Iterator sit = k.getSignatures();
241            while (sit.hasNext()) {
242                Object n = sit.next();
243                if (!(n instanceof PGPSignature)) {
244                    continue;
245                }
246
247                PGPSignature s = (PGPSignature) n;
248                if (!s.hasSubpackets()) {
249                    continue;
250                }
251
252                try {
253                    s.verifyCertification(ring.getPublicKey(s.getKeyID()));
254                } catch (PGPException e) {
255                    LOGGER.log(Level.WARNING, "Could not verify signature on " + Long.toHexString(k.getKeyID()) + " made by " + Long.toHexString(s.getKeyID()));
256                    continue;
257                }
258
259                PGPSignatureSubpacketVector hashed = s.getHashedSubPackets();
260                if (KeyFlag.fromInteger(hashed.getKeyFlags()).contains(KeyFlag.SIGN_DATA)) {
261                    signingKey = true;
262                    break;
263                }
264            }
265
266            if (signingKey) {
267                ids.add(k.getKeyID());
268            }
269        }
270        return ids;
271    }
272
273    public static boolean keyRingContainsKeyWithId(@Nonnull PGPPublicKeyRing ring,
274                                                   long keyId) {
275        return ring.getPublicKey(keyId) != null;
276    }
277
278    public static boolean keyRingContainsKeyWithId(@Nonnull PGPSecretKeyRing ring,
279                                                   long keyId) {
280        return ring.getSecretKey(keyId) != null;
281    }
282}