001// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.util;
006
007import java.io.IOException;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Iterator;
011import java.util.List;
012import java.util.NoSuchElementException;
013import javax.annotation.Nonnull;
014
015import org.bouncycastle.openpgp.PGPException;
016import org.bouncycastle.openpgp.PGPKeyRing;
017import org.bouncycastle.openpgp.PGPPrivateKey;
018import org.bouncycastle.openpgp.PGPPublicKey;
019import org.bouncycastle.openpgp.PGPPublicKeyRing;
020import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
021import org.bouncycastle.openpgp.PGPSecretKey;
022import org.bouncycastle.openpgp.PGPSecretKeyRing;
023import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
024import org.bouncycastle.openpgp.PGPSignature;
025import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
026import org.pgpainless.PGPainless;
027import org.pgpainless.exception.NotYetImplementedException;
028import org.pgpainless.key.protection.SecretKeyRingProtector;
029import org.pgpainless.key.protection.UnlockSecretKey;
030
031public final class KeyRingUtils {
032
033    private KeyRingUtils() {
034
035    }
036
037    /**
038     * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing}.
039     * If it has no primary secret key, throw a {@link NoSuchElementException}.
040     *
041     * @param secretKeys secret keys
042     * @return primary secret key
043     */
044    public static PGPSecretKey requirePrimarySecretKeyFrom(PGPSecretKeyRing secretKeys) {
045        PGPSecretKey primarySecretKey = getPrimarySecretKeyFrom(secretKeys);
046        if (primarySecretKey == null) {
047            throw new NoSuchElementException("Provided PGPSecretKeyRing has no primary secret key.");
048        }
049        return primarySecretKey;
050    }
051
052    /**
053     * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing} or null if it has none.
054     *
055     * @param secretKeys secret key ring
056     * @return primary secret key
057     */
058    public static PGPSecretKey getPrimarySecretKeyFrom(PGPSecretKeyRing secretKeys) {
059        PGPSecretKey secretKey = secretKeys.getSecretKey();
060        if (secretKey.isMasterKey()) {
061            return secretKey;
062        }
063        return null;
064    }
065
066    /**
067     * Return the primary {@link PGPPublicKey} from the provided key ring.
068     * Throws a {@link NoSuchElementException} if the key ring has no primary public key.
069     *
070     * @param keyRing key ring
071     * @return primary public key
072     */
073    public static PGPPublicKey requirePrimaryPublicKeyFrom(PGPKeyRing keyRing) {
074        PGPPublicKey primaryPublicKey = getPrimaryPublicKeyFrom(keyRing);
075        if (primaryPublicKey == null) {
076            throw new NoSuchElementException("Provided PGPKeyRing has no primary public key.");
077        }
078        return primaryPublicKey;
079    }
080
081    /**
082     * Return the primary {@link PGPPublicKey} from the provided key ring or null if it has none.
083     *
084     * @param keyRing key ring
085     * @return primary public key
086     */
087    public static PGPPublicKey getPrimaryPublicKeyFrom(PGPKeyRing keyRing) {
088        PGPPublicKey primaryPublicKey = keyRing.getPublicKey();
089        if (primaryPublicKey.isMasterKey()) {
090            return primaryPublicKey;
091        }
092        return null;
093    }
094
095    public static PGPPublicKey getPublicKeyFrom(PGPKeyRing keyRing, long subKeyId) {
096        return keyRing.getPublicKey(subKeyId);
097    }
098
099    public static PGPPublicKey requirePublicKeyFrom(PGPKeyRing keyRing, long subKeyId) {
100        PGPPublicKey publicKey = getPublicKeyFrom(keyRing, subKeyId);
101        if (publicKey == null) {
102            throw new NoSuchElementException("KeyRing does not contain public key with keyID " + Long.toHexString(subKeyId));
103        }
104        return publicKey;
105    }
106
107    public static PGPSecretKey requireSecretKeyFrom(PGPSecretKeyRing keyRing, long subKeyId) {
108        PGPSecretKey secretKey = keyRing.getSecretKey(subKeyId);
109        if (secretKey == null) {
110            throw new NoSuchElementException("KeyRing does not contain secret key with keyID " + Long.toHexString(subKeyId));
111        }
112        return secretKey;
113    }
114
115    /**
116     * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}.
117     *
118     * @param secretKeys secret key ring
119     * @return public key ring
120     */
121    public static PGPPublicKeyRing publicKeyRingFrom(PGPSecretKeyRing secretKeys) {
122        List<PGPPublicKey> publicKeyList = new ArrayList<>();
123        Iterator<PGPPublicKey> publicKeyIterator = secretKeys.getPublicKeys();
124        while (publicKeyIterator.hasNext()) {
125            publicKeyList.add(publicKeyIterator.next());
126        }
127        PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList);
128        return publicKeyRing;
129    }
130
131    /**
132     * Unlock a {@link PGPSecretKey} and return the resulting {@link PGPPrivateKey}.
133     *
134     * @param secretKey secret key
135     * @param protector protector to unlock the secret key
136     * @return private key
137     *
138     * @throws PGPException if something goes wrong (e.g. wrong passphrase)
139     */
140    public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) throws PGPException {
141        return UnlockSecretKey.unlockSecretKey(secretKey, protector);
142    }
143
144    /*
145        PGPXxxKeyRing -> PGPXxxKeyRingCollection
146         */
147    public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPPublicKeyRing... rings)
148            throws IOException, PGPException {
149        return new PGPPublicKeyRingCollection(Arrays.asList(rings));
150    }
151
152    public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPSecretKeyRing... rings)
153            throws IOException, PGPException {
154        return new PGPSecretKeyRingCollection(Arrays.asList(rings));
155    }
156
157    public static boolean keyRingContainsKeyWithId(@Nonnull PGPPublicKeyRing ring,
158                                                   long keyId) {
159        return ring.getPublicKey(keyId) != null;
160    }
161
162    public static <T extends PGPKeyRing> T injectCertification(T keyRing, PGPPublicKey certifiedKey, PGPSignature certification) {
163        PGPSecretKeyRing secretKeys = null;
164        PGPPublicKeyRing publicKeys;
165        if (keyRing instanceof PGPSecretKeyRing) {
166            secretKeys = (PGPSecretKeyRing) keyRing;
167            publicKeys = PGPainless.extractCertificate(secretKeys);
168        } else {
169            publicKeys = (PGPPublicKeyRing) keyRing;
170        }
171
172        certifiedKey = PGPPublicKey.addCertification(certifiedKey, certification);
173        List<PGPPublicKey> publicKeyList = new ArrayList<>();
174        Iterator<PGPPublicKey> publicKeyIterator = publicKeys.iterator();
175        boolean added = false;
176        while (publicKeyIterator.hasNext()) {
177            PGPPublicKey key = publicKeyIterator.next();
178            if (key.getKeyID() == certifiedKey.getKeyID()) {
179                added = true;
180                publicKeyList.add(certifiedKey);
181            } else {
182                publicKeyList.add(key);
183            }
184        }
185        if (!added) {
186            throw new NoSuchElementException("Cannot find public key with id " + Long.toHexString(certifiedKey.getKeyID()) + " in the provided key ring.");
187        }
188
189        publicKeys = new PGPPublicKeyRing(publicKeyList);
190        if (secretKeys == null) {
191            return (T) publicKeys;
192        } else {
193            secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
194            return (T) secretKeys;
195        }
196    }
197
198    public static <T extends PGPKeyRing> T injectCertification(T keyRing, String userId, PGPSignature certification) {
199        PGPSecretKeyRing secretKeys = null;
200        PGPPublicKeyRing publicKeys;
201        if (keyRing instanceof PGPSecretKeyRing) {
202            secretKeys = (PGPSecretKeyRing) keyRing;
203            publicKeys = PGPainless.extractCertificate(secretKeys);
204        } else {
205            publicKeys = (PGPPublicKeyRing) keyRing;
206        }
207
208        Iterator<PGPPublicKey> publicKeyIterator = publicKeys.iterator();
209        PGPPublicKey primaryKey = publicKeyIterator.next();
210        primaryKey = PGPPublicKey.addCertification(primaryKey, userId, certification);
211
212        List<PGPPublicKey> publicKeyList = new ArrayList<>();
213        publicKeyList.add(primaryKey);
214        while (publicKeyIterator.hasNext()) {
215            publicKeyList.add(publicKeyIterator.next());
216        }
217
218        publicKeys = new PGPPublicKeyRing(publicKeyList);
219        if (secretKeys == null) {
220            return (T) publicKeys;
221        } else {
222            secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
223            return (T) secretKeys;
224        }
225    }
226
227    public static <T extends PGPKeyRing> T injectCertification(T keyRing, PGPUserAttributeSubpacketVector userAttributes, PGPSignature certification) {
228        PGPSecretKeyRing secretKeys = null;
229        PGPPublicKeyRing publicKeys;
230        if (keyRing instanceof PGPSecretKeyRing) {
231            secretKeys = (PGPSecretKeyRing) keyRing;
232            publicKeys = PGPainless.extractCertificate(secretKeys);
233        } else {
234            publicKeys = (PGPPublicKeyRing) keyRing;
235        }
236
237        Iterator<PGPPublicKey> publicKeyIterator = publicKeys.iterator();
238        PGPPublicKey primaryKey = publicKeyIterator.next();
239        primaryKey = PGPPublicKey.addCertification(primaryKey, userAttributes, certification);
240
241        List<PGPPublicKey> publicKeyList = new ArrayList<>();
242        publicKeyList.add(primaryKey);
243        while (publicKeyIterator.hasNext()) {
244            publicKeyList.add(publicKeyIterator.next());
245        }
246
247        publicKeys = new PGPPublicKeyRing(publicKeyList);
248        if (secretKeys == null) {
249            return (T) publicKeys;
250        } else {
251            secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
252            return (T) secretKeys;
253        }
254    }
255
256    public static <T extends PGPKeyRing> T keysPlusPublicKey(T keyRing, PGPPublicKey publicKey) {
257        if (true)
258            // Is currently broken beyond repair
259            throw new NotYetImplementedException();
260
261        PGPSecretKeyRing secretKeys = null;
262        PGPPublicKeyRing publicKeys;
263        if (keyRing instanceof PGPSecretKeyRing) {
264            secretKeys = (PGPSecretKeyRing) keyRing;
265            publicKeys = PGPainless.extractCertificate(secretKeys);
266        } else {
267            publicKeys = (PGPPublicKeyRing) keyRing;
268        }
269
270        publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, publicKey);
271        if (secretKeys == null) {
272            return (T) publicKeys;
273        } else {
274            // TODO: Replace with PGPSecretKeyRing.insertOrReplacePublicKey() once available
275            //  Right now replacePublicKeys looses extra public keys.
276            //  See https://github.com/bcgit/bc-java/pull/1068 for a possible fix
277            secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
278            return (T) secretKeys;
279        }
280    }
281
282    public static PGPSecretKeyRing keysPlusSecretKey(PGPSecretKeyRing secretKeys, PGPSecretKey secretKey) {
283        return PGPSecretKeyRing.insertSecretKey(secretKeys, secretKey);
284    }
285
286    public static PGPSecretKey secretKeyPlusSignature(PGPSecretKey secretKey, PGPSignature signature) {
287        PGPPublicKey publicKey = secretKey.getPublicKey();
288        publicKey = PGPPublicKey.addCertification(publicKey, signature);
289        PGPSecretKey newSecretKey = PGPSecretKey.replacePublicKey(secretKey, publicKey);
290        return newSecretKey;
291    }
292
293}