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}