001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.encryption_signing; 006 007import java.util.Collections; 008import java.util.Date; 009import java.util.HashMap; 010import java.util.List; 011import java.util.Map; 012import java.util.Set; 013import javax.annotation.Nullable; 014 015import org.bouncycastle.openpgp.PGPException; 016import org.bouncycastle.openpgp.PGPPrivateKey; 017import org.bouncycastle.openpgp.PGPPublicKey; 018import org.bouncycastle.openpgp.PGPSecretKey; 019import org.bouncycastle.openpgp.PGPSecretKeyRing; 020import org.bouncycastle.openpgp.PGPSignatureGenerator; 021import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; 022import org.pgpainless.PGPainless; 023import org.pgpainless.algorithm.DocumentSignatureType; 024import org.pgpainless.algorithm.HashAlgorithm; 025import org.pgpainless.algorithm.PublicKeyAlgorithm; 026import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; 027import org.pgpainless.exception.KeyCannotSignException; 028import org.pgpainless.exception.KeyValidationError; 029import org.pgpainless.implementation.ImplementationFactory; 030import org.pgpainless.key.OpenPgpFingerprint; 031import org.pgpainless.key.SubkeyIdentifier; 032import org.pgpainless.key.info.KeyRingInfo; 033import org.pgpainless.key.protection.SecretKeyRingProtector; 034import org.pgpainless.key.protection.UnlockSecretKey; 035import org.pgpainless.policy.Policy; 036import org.pgpainless.signature.subpackets.BaseSignatureSubpackets; 037import org.pgpainless.signature.subpackets.SignatureSubpackets; 038import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; 039 040public final class SigningOptions { 041 042 /** 043 * A method of signing. 044 */ 045 public static final class SigningMethod { 046 private final PGPSignatureGenerator signatureGenerator; 047 private final boolean detached; 048 private final HashAlgorithm hashAlgorithm; 049 050 private SigningMethod(PGPSignatureGenerator signatureGenerator, boolean detached, HashAlgorithm hashAlgorithm) { 051 this.signatureGenerator = signatureGenerator; 052 this.detached = detached; 053 this.hashAlgorithm = hashAlgorithm; 054 } 055 056 /** 057 * Inline-signature method. 058 * The resulting signature will be written into the message itself, together with a one-pass-signature packet. 059 * 060 * @param signatureGenerator signature generator 061 * @return inline signing method 062 */ 063 public static SigningMethod inlineSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) { 064 return new SigningMethod(signatureGenerator, false, hashAlgorithm); 065 } 066 067 /** 068 * Detached signing method. 069 * The resulting signature will not be added to the message, and instead can be distributed separately 070 * to the signed message. 071 * 072 * @param signatureGenerator signature generator 073 * @return detached signing method 074 */ 075 public static SigningMethod detachedSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) { 076 return new SigningMethod(signatureGenerator, true, hashAlgorithm); 077 } 078 079 public boolean isDetached() { 080 return detached; 081 } 082 083 public PGPSignatureGenerator getSignatureGenerator() { 084 return signatureGenerator; 085 } 086 087 public HashAlgorithm getHashAlgorithm() { 088 return hashAlgorithm; 089 } 090 } 091 092 private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>(); 093 private HashAlgorithm hashAlgorithmOverride; 094 095 public static SigningOptions get() { 096 return new SigningOptions(); 097 } 098 099 /** 100 * Add inline signatures with all secret key rings in the provided secret key ring collection. 101 * 102 * @param secrectKeyDecryptor decryptor to unlock the signing secret keys 103 * @param signingKeys collection of signing keys 104 * @param signatureType type of signature (binary, canonical text) 105 * @return this 106 * @throws KeyValidationError if something is wrong with any of the keys 107 * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created 108 */ 109 public SigningOptions addInlineSignatures(SecretKeyRingProtector secrectKeyDecryptor, 110 Iterable<PGPSecretKeyRing> signingKeys, 111 DocumentSignatureType signatureType) 112 throws KeyValidationError, PGPException { 113 for (PGPSecretKeyRing signingKey : signingKeys) { 114 addInlineSignature(secrectKeyDecryptor, signingKey, signatureType); 115 } 116 return this; 117 } 118 119 /** 120 * Add an inline-signature. 121 * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use 122 * of one-pass-signature packets. 123 * 124 * @param secretKeyDecryptor decryptor to unlock the signing secret key 125 * @param secretKey signing key 126 * @param signatureType type of signature (binary, canonical text) 127 * @throws KeyValidationError if something is wrong with the key 128 * @throws PGPException if the key cannot be unlocked or the signing method cannot be created 129 * @return this 130 */ 131 public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, 132 PGPSecretKeyRing secretKey, 133 DocumentSignatureType signatureType) 134 throws KeyValidationError, PGPException { 135 return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType); 136 } 137 138 /** 139 * Add an inline-signature. 140 * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use 141 * of one-pass-signature packets. 142 * 143 * This method uses the passed in user-id to select user-specific hash algorithms. 144 * 145 * @param secretKeyDecryptor decryptor to unlock the signing secret key 146 * @param secretKey signing key 147 * @param userId user-id of the signer 148 * @param signatureType signature type (binary, canonical text) 149 * @return this 150 * @throws KeyValidationError if the key is invalid 151 * @throws PGPException if the key cannot be unlocked or the signing method cannot be created 152 */ 153 public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, 154 PGPSecretKeyRing secretKey, 155 String userId, 156 DocumentSignatureType signatureType) 157 throws KeyValidationError, PGPException { 158 return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); 159 } 160 161 /** 162 * Add an inline-signature. 163 * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use 164 * of one-pass-signature packets. 165 * 166 * This method uses the passed in user-id to select user-specific hash algorithms. 167 * 168 * @param secretKeyDecryptor decryptor to unlock the signing secret key 169 * @param secretKey signing key 170 * @param userId user-id of the signer 171 * @param signatureType signature type (binary, canonical text) 172 * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature 173 * @return this 174 * @throws KeyValidationError if the key is invalid 175 * @throws PGPException if the key cannot be unlocked or the signing method cannot be created 176 */ 177 public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, 178 PGPSecretKeyRing secretKey, 179 String userId, 180 DocumentSignatureType signatureType, 181 @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) 182 throws KeyValidationError, PGPException { 183 KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date()); 184 if (userId != null && !keyRingInfo.isUserIdValid(userId)) { 185 throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId)); 186 } 187 188 List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys(); 189 if (signingPubKeys.isEmpty()) { 190 throw new KeyCannotSignException("Key " + OpenPgpFingerprint.of(secretKey) + " has no valid signing key."); 191 } 192 193 for (PGPPublicKey signingPubKey : signingPubKeys) { 194 PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); 195 PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); 196 Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) 197 : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); 198 HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); 199 addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false); 200 } 201 202 return this; 203 } 204 205 /** 206 * Add detached signatures with all key rings from the provided secret key ring collection. 207 * 208 * @param secretKeyDecryptor decryptor to unlock the secret signing keys 209 * @param signingKeys collection of signing key rings 210 * @param signatureType type of the signature (binary, canonical text) 211 * @return this 212 * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created 213 */ 214 public SigningOptions addDetachedSignatures(SecretKeyRingProtector secretKeyDecryptor, 215 Iterable<PGPSecretKeyRing> signingKeys, 216 DocumentSignatureType signatureType) 217 throws PGPException { 218 for (PGPSecretKeyRing signingKey : signingKeys) { 219 addDetachedSignature(secretKeyDecryptor, signingKey, signatureType); 220 } 221 return this; 222 } 223 224 /** 225 * Create a detached signature. 226 * Detached signatures are not being added into the PGP message itself. 227 * Instead, they can be distributed separately to the message. 228 * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). 229 * 230 * @param secretKeyDecryptor decryptor to unlock the secret signing key 231 * @param secretKey signing key 232 * @param signatureType type of data that is signed (binary, canonical text) 233 * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created 234 * @return this 235 */ 236 public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, 237 PGPSecretKeyRing secretKey, 238 DocumentSignatureType signatureType) 239 throws PGPException { 240 return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType); 241 } 242 243 /** 244 * Create a detached signature. 245 * Detached signatures are not being added into the PGP message itself. 246 * Instead, they can be distributed separately to the message. 247 * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). 248 * 249 * This method uses the passed in user-id to select user-specific hash algorithms. 250 * 251 * @param secretKeyDecryptor decryptor to unlock the secret signing key 252 * @param secretKey signing key 253 * @param userId user-id 254 * @param signatureType type of data that is signed (binary, canonical text) 255 * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created 256 * @return this 257 */ 258 public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, 259 PGPSecretKeyRing secretKey, 260 String userId, 261 DocumentSignatureType signatureType) 262 throws PGPException { 263 return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); 264 } 265 266 /** 267 * Create a detached signature. 268 * Detached signatures are not being added into the PGP message itself. 269 * Instead, they can be distributed separately to the message. 270 * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). 271 * 272 * This method uses the passed in user-id to select user-specific hash algorithms. 273 * 274 * @param secretKeyDecryptor decryptor to unlock the secret signing key 275 * @param secretKey signing key 276 * @param userId user-id 277 * @param signatureType type of data that is signed (binary, canonical text) 278 * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature 279 * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created 280 * @return this 281 */ 282 public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, 283 PGPSecretKeyRing secretKey, 284 String userId, 285 DocumentSignatureType signatureType, 286 @Nullable BaseSignatureSubpackets.Callback subpacketCallback) 287 throws PGPException { 288 KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date()); 289 if (userId != null && !keyRingInfo.isUserIdValid(userId)) { 290 throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId)); 291 } 292 293 List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys(); 294 if (signingPubKeys.isEmpty()) { 295 throw new KeyCannotSignException("Key has no valid signing key."); 296 } 297 298 for (PGPPublicKey signingPubKey : signingPubKeys) { 299 PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); 300 if (signingSecKey == null) { 301 throw new PGPException("Missing secret key for signing key " + Long.toHexString(signingPubKey.getKeyID())); 302 } 303 PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey( 304 secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID())); 305 Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) 306 : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); 307 HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); 308 addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true); 309 } 310 311 return this; 312 } 313 314 private void addSigningMethod(PGPSecretKeyRing secretKey, 315 PGPPrivateKey signingSubkey, 316 @Nullable BaseSignatureSubpackets.Callback subpacketCallback, 317 HashAlgorithm hashAlgorithm, 318 DocumentSignatureType signatureType, 319 boolean detached) 320 throws PGPException { 321 SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID()); 322 PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID()); 323 PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(signingSecretKey.getPublicKey().getAlgorithm()); 324 int bitStrength = secretKey.getPublicKey().getBitStrength(); 325 if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { 326 throw new IllegalArgumentException("Public key algorithm policy violation: " + 327 publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); 328 } 329 330 PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType); 331 332 // Subpackets 333 SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey()); 334 SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets(); 335 if (subpacketCallback != null) { 336 subpacketCallback.modifyHashedSubpackets(hashedSubpackets); 337 subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets); 338 } 339 generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)); 340 generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)); 341 342 SigningMethod signingMethod = detached ? 343 SigningMethod.detachedSignature(generator, hashAlgorithm) : 344 SigningMethod.inlineSignature(generator, hashAlgorithm); 345 signingMethods.put(signingKeyIdentifier, signingMethod); 346 } 347 348 /** 349 * Negotiate, which hash algorithm to use. 350 * 351 * This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}. 352 * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. 353 * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is 354 * used as a fallback. 355 * 356 * @param preferences preferences 357 * @param policy policy 358 * @return selected hash algorithm 359 */ 360 private HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> preferences, Policy policy) { 361 if (hashAlgorithmOverride != null) { 362 return hashAlgorithmOverride; 363 } 364 365 return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy) 366 .negotiateHashAlgorithm(preferences); 367 } 368 369 private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey, 370 HashAlgorithm hashAlgorithm, 371 DocumentSignatureType signatureType) 372 throws PGPException { 373 int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm(); 374 PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance() 375 .getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId()); 376 PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder); 377 signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey); 378 379 return signatureGenerator; 380 } 381 382 /** 383 * Return a map of key-ids and signing methods. 384 * For internal use. 385 * 386 * @return signing methods 387 */ 388 public Map<SubkeyIdentifier, SigningMethod> getSigningMethods() { 389 return Collections.unmodifiableMap(signingMethods); 390 } 391 392 /** 393 * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. 394 * If no override has been set, an accetable algorithm will be negotiated instead. 395 * 396 * Note: To override the hash algorithm for signing, call this method *before* calling 397 * {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or 398 * {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}. 399 * 400 * @param hashAlgorithmOverride override hash algorithm 401 * @return this 402 */ 403 public SigningOptions overrideHashAlgorithm(HashAlgorithm hashAlgorithmOverride) { 404 this.hashAlgorithmOverride = hashAlgorithmOverride; 405 return this; 406 } 407 408 /** 409 * Return the hash algorithm override (or null if no override is set). 410 * 411 * @return hash algorithm override 412 */ 413 public HashAlgorithm getHashAlgorithmOverride() { 414 return hashAlgorithmOverride; 415 } 416}