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}