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 javax.annotation.Nullable; 020import java.lang.reflect.Field; 021import java.util.ArrayList; 022import java.util.Iterator; 023import java.util.List; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.bouncycastle.bcpg.HashAlgorithmTags; 028import org.bouncycastle.bcpg.PublicKeyPacket; 029import org.bouncycastle.bcpg.PublicSubkeyPacket; 030import org.bouncycastle.openpgp.PGPException; 031import org.bouncycastle.openpgp.PGPPrivateKey; 032import org.bouncycastle.openpgp.PGPPublicKey; 033import org.bouncycastle.openpgp.PGPSecretKey; 034import org.bouncycastle.openpgp.PGPSecretKeyRing; 035import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; 036import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; 037import org.bouncycastle.openpgp.operator.PGPDigestCalculator; 038import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; 039 040public class KeyRingSubKeyFix { 041 042 private static final Logger LOGGER = Logger.getLogger(KeyRingSubKeyFix.class.getName()); 043 044 /** 045 * This method makes sure, that sub keys do consist of sub key packets. 046 * Bouncycastle versions up to and including 1.60 created {@link PGPSecretKeyRing}s which sub keys consisted of 047 * normal public key packets, which would result in lost keys when converting PGPSecretKeyRings to PGPPublicKeyRings. 048 * 049 * This method throws a {@link RuntimeException} of a {@link NoSuchFieldException} or {@link IllegalAccessException}. 050 * 051 * @see <a href="https://github.com/bcgit/bc-java/issues/381">Bouncycastle Java bug report #381</a> 052 * 053 * @param secretKeys possibly faulty PGPSecretKeyRing 054 * @param decryptor decryptor in case the keys are encrypted (can be null) 055 * @param encryptor encryptor to re-encrypt the keys in case they are encrypted (can be null) 056 * 057 * @return fixed PGPSecretKeyRing 058 * 059 * @throws PGPException in case we cannot dismantle or reassemble the key. 060 */ 061 public static PGPSecretKeyRing repairSubkeyPackets(@Nonnull PGPSecretKeyRing secretKeys, 062 @Nullable PBESecretKeyDecryptor decryptor, 063 @Nullable PBESecretKeyEncryptor encryptor) 064 throws PGPException { 065 066 PGPDigestCalculator calculator = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1); 067 068 List<PGPSecretKey> _secretKeys = new ArrayList<>(); 069 Iterator<PGPSecretKey> secretKeyIterator = secretKeys.iterator(); 070 try { 071 072 while (secretKeyIterator.hasNext()) { 073 PGPSecretKey secSubKey = secretKeyIterator.next(); 074 075 if (secSubKey.isMasterKey()) { 076 LOGGER.log(Level.INFO, Long.toHexString(secSubKey.getKeyID()) + " is master key. Skip."); 077 _secretKeys.add(secSubKey); 078 continue; 079 } 080 081 PGPPublicKey pubSubKey = secSubKey.getPublicKey(); 082 083 // check for public key packet type 084 085 Field publicPk = pubSubKey.getClass().getDeclaredField("publicPk"); 086 publicPk.setAccessible(true); 087 PublicKeyPacket keyPacket = (PublicKeyPacket) publicPk.get(pubSubKey); 088 089 if (keyPacket instanceof PublicSubkeyPacket) { 090 // Sub key is already sub key 091 _secretKeys.add(secSubKey); 092 continue; 093 } 094 095 // Sub key is normal key -> fix 096 LOGGER.log(Level.INFO, "Subkey " + Long.toHexString(secSubKey.getKeyID()) + " does not have a subkey key packet. Convert it..."); 097 keyPacket = new PublicSubkeyPacket(pubSubKey.getAlgorithm(), pubSubKey.getCreationTime(), keyPacket.getKey()); 098 publicPk.set(pubSubKey, keyPacket); 099 100 PGPPrivateKey privateKey = secSubKey.extractPrivateKey(decryptor); 101 102 PGPSecretKey secretKey = new PGPSecretKey(privateKey, pubSubKey, calculator, false, encryptor); 103 _secretKeys.add(secretKey); 104 } 105 106 return new PGPSecretKeyRing(_secretKeys); 107 } catch (NoSuchFieldException | IllegalAccessException e) { 108 throw new RuntimeException("Cannot apply fix due to an error while using reflections.", e); 109 } 110 } 111}