001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.signature.builder; 006 007import java.util.Set; 008import javax.annotation.Nonnull; 009 010import org.bouncycastle.openpgp.PGPException; 011import org.bouncycastle.openpgp.PGPPrivateKey; 012import org.bouncycastle.openpgp.PGPPublicKey; 013import org.bouncycastle.openpgp.PGPSecretKey; 014import org.bouncycastle.openpgp.PGPSignature; 015import org.bouncycastle.openpgp.PGPSignatureGenerator; 016import org.pgpainless.PGPainless; 017import org.pgpainless.algorithm.HashAlgorithm; 018import org.pgpainless.algorithm.SignatureType; 019import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; 020import org.pgpainless.implementation.ImplementationFactory; 021import org.pgpainless.key.protection.SecretKeyRingProtector; 022import org.pgpainless.key.protection.UnlockSecretKey; 023import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; 024import org.pgpainless.signature.subpackets.SignatureSubpackets; 025import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; 026 027public abstract class AbstractSignatureBuilder<B extends AbstractSignatureBuilder<B>> { 028 protected final PGPPrivateKey privateSigningKey; 029 protected final PGPPublicKey publicSigningKey; 030 031 protected HashAlgorithm hashAlgorithm; 032 protected SignatureType signatureType; 033 034 protected SignatureSubpackets unhashedSubpackets; 035 protected SignatureSubpackets hashedSubpackets; 036 037 protected AbstractSignatureBuilder(SignatureType signatureType, 038 PGPSecretKey signingKey, 039 SecretKeyRingProtector protector, 040 HashAlgorithm hashAlgorithm, 041 SignatureSubpackets hashedSubpackets, 042 SignatureSubpackets unhashedSubpackets) 043 throws PGPException { 044 if (!isValidSignatureType(signatureType)) { 045 throw new IllegalArgumentException("Invalid signature type."); 046 } 047 this.signatureType = signatureType; 048 this.privateSigningKey = UnlockSecretKey.unlockSecretKey(signingKey, protector); 049 this.publicSigningKey = signingKey.getPublicKey(); 050 this.hashAlgorithm = hashAlgorithm; 051 this.hashedSubpackets = hashedSubpackets; 052 this.unhashedSubpackets = unhashedSubpackets; 053 } 054 055 public AbstractSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) 056 throws PGPException { 057 this( 058 signatureType, 059 signingKey, 060 protector, 061 negotiateHashAlgorithm(signingKey.getPublicKey()), 062 SignatureSubpackets.createHashedSubpackets(signingKey.getPublicKey()), 063 SignatureSubpackets.createEmptySubpackets() 064 ); 065 } 066 067 public AbstractSignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) 068 throws PGPException { 069 this( 070 SignatureType.valueOf(archetypeSignature.getSignatureType()), 071 certificationKey, 072 protector, 073 negotiateHashAlgorithm(certificationKey.getPublicKey()), 074 SignatureSubpackets.refreshHashedSubpackets(certificationKey.getPublicKey(), archetypeSignature), 075 SignatureSubpackets.refreshUnhashedSubpackets(archetypeSignature) 076 ); 077 } 078 079 /** 080 * Negotiate a {@link HashAlgorithm} to be used when creating the signature. 081 * 082 * @param publicKey signing public key 083 * @return hash algorithm 084 */ 085 protected static HashAlgorithm negotiateHashAlgorithm(PGPPublicKey publicKey) { 086 Set<HashAlgorithm> hashAlgorithmPreferences = OpenPgpKeyAttributeUtil.getOrGuessPreferredHashAlgorithms(publicKey); 087 return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) 088 .negotiateHashAlgorithm(hashAlgorithmPreferences); 089 } 090 091 public B overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithm) { 092 this.hashAlgorithm = hashAlgorithm; 093 return (B) this; 094 } 095 096 /** 097 * Set the builders {@link SignatureType}. 098 * Note that only those types who are valid for the concrete subclass of this {@link AbstractSignatureBuilder} 099 * are allowed. Invalid choices result in an {@link IllegalArgumentException} to be thrown. 100 * 101 * @param type signature type 102 * @return builder 103 */ 104 public B setSignatureType(SignatureType type) { 105 if (!isValidSignatureType(type)) { 106 throw new IllegalArgumentException("Invalid signature type: " + type); 107 } 108 this.signatureType = type; 109 return (B) this; 110 } 111 112 /** 113 * Build an instance of {@link PGPSignatureGenerator} initialized with the signing key 114 * and with hashed and unhashed subpackets. 115 * 116 * @return pgp signature generator 117 */ 118 protected PGPSignatureGenerator buildAndInitSignatureGenerator() throws PGPException { 119 PGPSignatureGenerator generator = new PGPSignatureGenerator( 120 ImplementationFactory.getInstance().getPGPContentSignerBuilder( 121 publicSigningKey.getAlgorithm(), hashAlgorithm.getAlgorithmId() 122 ) 123 ); 124 generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)); 125 generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)); 126 generator.init(signatureType.getCode(), privateSigningKey); 127 return generator; 128 } 129 130 /** 131 * Return true if the given {@link SignatureType} is a valid choice for the concrete implementation 132 * of {@link AbstractSignatureBuilder}. 133 * 134 * @param type type 135 * @return return true if valid, false otherwise 136 */ 137 protected abstract boolean isValidSignatureType(SignatureType type); 138}