001// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.protection;
006
007import java.util.HashMap;
008import java.util.Map;
009import java.util.concurrent.ConcurrentHashMap;
010import javax.annotation.Nonnull;
011import javax.annotation.Nullable;
012
013import org.bouncycastle.openpgp.PGPException;
014import org.bouncycastle.openpgp.PGPSecretKey;
015import org.bouncycastle.openpgp.PGPSecretKeyRing;
016import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
017import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
018import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
019import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
020import org.pgpainless.util.Passphrase;
021
022/**
023 * Task of the {@link SecretKeyRingProtector} is to map encryptor/decryptor objects to key-ids.
024 * {@link PBESecretKeyEncryptor PBESecretKeyEncryptors}/{@link PBESecretKeyDecryptor PBESecretKeyDecryptors} are used
025 * to encrypt/decrypt secret keys using a passphrase.
026 *
027 * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of
028 * implementations ready for use.
029 */
030public interface SecretKeyRingProtector {
031
032    boolean hasPassphraseFor(Long keyId);
033
034    /**
035     * Return a decryptor for the key of id {@code keyId}.
036     * This method returns null if the key is unprotected.
037     *
038     * @param keyId id of the key
039     * @return decryptor for the key
040     */
041    @Nullable PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException;
042
043    /**
044     * Return an encryptor for the key of id {@code keyId}.
045     * This method returns null if the key is unprotected.
046     *
047     * @param keyId id of the key
048     * @return encryptor for the key
049     * @throws PGPException if the encryptor cannot be created for some reason
050     */
051    @Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException;
052
053    /**
054     * Return a protector for secret keys.
055     * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases
056     * at runtime.
057     *
058     * See {@link CachingSecretKeyRingProtector} for how to memorize/forget additional passphrases during runtime.
059     *
060     * @param missingPassphraseCallback callback that is used to provide missing passphrases.
061     * @return caching secret key protector
062     */
063    static CachingSecretKeyRingProtector defaultSecretKeyRingProtector(SecretKeyPassphraseProvider missingPassphraseCallback) {
064        return new CachingSecretKeyRingProtector(
065                new HashMap<>(),
066                KeyRingProtectionSettings.secureDefaultSettings(),
067                missingPassphraseCallback);
068    }
069
070    /**
071     * Use the provided passphrase to lock/unlock all keys in the provided key ring.
072     *
073     * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object.
074     * For other keys that are not present in the ring, it will return null.
075     *
076     * @param passphrase passphrase
077     * @param keys key ring
078     * @return protector
079     * @deprecated use {@link #unlockEachKeyWith(Passphrase, PGPSecretKeyRing)} instead.
080     */
081    @Deprecated
082    static SecretKeyRingProtector unlockAllKeysWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) {
083        return unlockEachKeyWith(passphrase, keys);
084    }
085
086    /**
087     * Use the provided passphrase to lock/unlock all keys in the provided key ring.
088     *
089     * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object.
090     * For other keys that are not present in the ring, it will return null.
091     *
092     * @param passphrase passphrase
093     * @param keys key ring
094     * @return protector
095     */
096    static SecretKeyRingProtector unlockEachKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) {
097        Map<Long, Passphrase> map = new ConcurrentHashMap<>();
098        for (PGPSecretKey secretKey : keys) {
099            map.put(secretKey.getKeyID(), passphrase);
100        }
101        return fromPassphraseMap(map);
102    }
103
104    /**
105     * Use the provided passphrase to unlock any key.
106     *
107     * @param passphrase passphrase
108     * @return protector
109     */
110    static SecretKeyRingProtector unlockAnyKeyWith(@Nonnull Passphrase passphrase) {
111        return new BaseSecretKeyRingProtector(new SolitaryPassphraseProvider(passphrase));
112    }
113
114    /**
115     * Use the provided passphrase to lock/unlock only the provided (sub-)key.
116     * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if
117     * {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} is getting called with the key-id of the provided key.
118     *
119     * Otherwise, this protector will always return null.
120     *
121     * @param passphrase passphrase
122     * @param key key to lock/unlock
123     * @return protector
124     */
125    static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKey key) {
126        return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase);
127    }
128
129    static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, long keyId) {
130        return PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase);
131    }
132
133    /**
134     * Protector for unprotected keys.
135     * This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls,
136     * no matter what the key-id is.
137     *
138     * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will
139     * leave keys unprotected, should it be used to "protect" a key
140     * (e.g. in {@link org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor#changePassphraseFromOldPassphrase(Passphrase)}).
141     *
142     * @return protector
143     */
144    static SecretKeyRingProtector unprotectedKeys() {
145        return new UnprotectedKeysProtector();
146    }
147
148    /**
149     * Use the provided map of key-ids and passphrases to unlock keys.
150     *
151     * @param passphraseMap map of key ids and their respective passphrases
152     * @return protector
153     */
154    static SecretKeyRingProtector fromPassphraseMap(@Nonnull Map<Long, Passphrase> passphraseMap) {
155        return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
156    }
157}