001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.protection.fixes;
006
007import org.bouncycastle.bcpg.SecretKeyPacket;
008import org.bouncycastle.openpgp.PGPException;
009import org.bouncycastle.openpgp.PGPPrivateKey;
010import org.bouncycastle.openpgp.PGPSecretKey;
011import org.bouncycastle.openpgp.PGPSecretKeyRing;
012import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
013import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
014import org.pgpainless.algorithm.HashAlgorithm;
015import org.pgpainless.exception.WrongPassphraseException;
016import org.pgpainless.implementation.ImplementationFactory;
017import org.pgpainless.key.protection.SecretKeyRingProtector;
018import org.pgpainless.key.protection.UnlockSecretKey;
019
020/**
021 * Repair class to fix keys which use S2K usage of value {@link SecretKeyPacket#USAGE_CHECKSUM}.
022 * The method {@link #replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing, SecretKeyRingProtector)} ensures
023 * that such keys are encrypted using S2K usage {@link SecretKeyPacket#USAGE_SHA1} instead.
024 *
025 * @see <a href="https://github.com/pgpainless/pgpainless/issues/176">Related PGPainless Bug Report</a>
026 * @see <a href="https://github.com/pgpainless/pgpainless/issues/178">Related PGPainless Feature Request</a>
027 * @see <a href="https://github.com/bcgit/bc-java/issues/1020">Related upstream BC bug report</a>
028 */
029public final class S2KUsageFix {
030
031    private S2KUsageFix() {
032
033    }
034
035    /**
036     * Repair method for keys which use S2K usage <pre>USAGE_CHECKSUM</pre> which is deemed insecure.
037     * This method fixes the private keys by changing them to <pre>USAGE_SHA1</pre> instead.
038     *
039     * @param keys keys
040     * @param protector protector to unlock and re-lock affected private keys
041     * @return fixed key ring
042     * @throws PGPException in case of a PGP error.
043     */
044    public static PGPSecretKeyRing replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing keys, SecretKeyRingProtector protector) throws PGPException {
045        return replaceUsageChecksumWithUsageSha1(keys, protector, false);
046    }
047
048    /**
049     * Repair method for keys which use S2K usage <pre>USAGE_CHECKSUM</pre> which is deemed insecure.
050     * This method fixes the private keys by changing them to <pre>USAGE_SHA1</pre> instead.
051     *
052     * @param keys keys
053     * @param protector protector to unlock and re-lock affected private keys
054     * @param skipKeysWithMissingPassphrase if set to true, missing subkey passphrases will cause the subkey to stay unaffected.
055     * @return fixed key ring
056     * @throws PGPException in case of a PGP error.
057     */
058    public static PGPSecretKeyRing replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing keys,
059                                                                     SecretKeyRingProtector protector,
060                                                                     boolean skipKeysWithMissingPassphrase) throws PGPException {
061        PGPDigestCalculator digestCalculator = ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1);
062        for (PGPSecretKey key : keys) {
063            // CHECKSUM is not recommended
064            if (key.getS2KUsage() != SecretKeyPacket.USAGE_CHECKSUM) {
065                continue;
066            }
067
068            long keyId = key.getKeyID();
069            PBESecretKeyEncryptor encryptor = protector.getEncryptor(keyId);
070            if (encryptor == null) {
071                if (skipKeysWithMissingPassphrase) {
072                    continue;
073                }
074                throw new WrongPassphraseException("Missing passphrase for key with ID " + Long.toHexString(keyId));
075            }
076
077            PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(key, protector);
078            // This constructor makes use of USAGE_SHA1 by default
079            PGPSecretKey fixedKey = new PGPSecretKey(
080                    privateKey,
081                    key.getPublicKey(),
082                    digestCalculator,
083                    key.isMasterKey(),
084                    protector.getEncryptor(keyId)
085            );
086
087            // replace the original key with the fixed one
088            keys = PGPSecretKeyRing.insertSecretKey(keys, fixedKey);
089        }
090        return keys;
091    }
092}