mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-19 02:42:05 +01:00
Kotlin conversion: SigningOptions
This commit is contained in:
parent
6e653f3c92
commit
5441993baf
2 changed files with 451 additions and 634 deletions
|
@ -1,634 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
|
||||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
|
||||||
import org.pgpainless.PGPainless;
|
|
||||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
|
||||||
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
|
|
||||||
import org.pgpainless.exception.KeyException;
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
|
||||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
|
||||||
import org.pgpainless.policy.Policy;
|
|
||||||
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets;
|
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpackets;
|
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
|
|
||||||
|
|
||||||
public final class SigningOptions {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A method of signing.
|
|
||||||
*/
|
|
||||||
public static final class SigningMethod {
|
|
||||||
private final PGPSignatureGenerator signatureGenerator;
|
|
||||||
private final boolean detached;
|
|
||||||
private final HashAlgorithm hashAlgorithm;
|
|
||||||
|
|
||||||
private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator,
|
|
||||||
boolean detached,
|
|
||||||
@Nonnull HashAlgorithm hashAlgorithm) {
|
|
||||||
this.signatureGenerator = signatureGenerator;
|
|
||||||
this.detached = detached;
|
|
||||||
this.hashAlgorithm = hashAlgorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inline-signature method.
|
|
||||||
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
|
|
||||||
*
|
|
||||||
* @param signatureGenerator signature generator
|
|
||||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
|
||||||
* @return inline signing method
|
|
||||||
*/
|
|
||||||
public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
|
|
||||||
@Nonnull HashAlgorithm hashAlgorithm) {
|
|
||||||
return new SigningMethod(signatureGenerator, false, hashAlgorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detached signing method.
|
|
||||||
* The resulting signature will not be added to the message, and instead can be distributed separately
|
|
||||||
* to the signed message.
|
|
||||||
*
|
|
||||||
* @param signatureGenerator signature generator
|
|
||||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
|
||||||
* @return detached signing method
|
|
||||||
*/
|
|
||||||
public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
|
|
||||||
@Nonnull HashAlgorithm hashAlgorithm) {
|
|
||||||
return new SigningMethod(signatureGenerator, true, hashAlgorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDetached() {
|
|
||||||
return detached;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignatureGenerator getSignatureGenerator() {
|
|
||||||
return signatureGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashAlgorithm getHashAlgorithm() {
|
|
||||||
return hashAlgorithm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
|
|
||||||
private HashAlgorithm hashAlgorithmOverride;
|
|
||||||
private Date evaluationDate = new Date();
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static SigningOptions get() {
|
|
||||||
return new SigningOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override the evaluation date for signing keys with the given date.
|
|
||||||
*
|
|
||||||
* @param evaluationDate new evaluation date
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public SigningOptions setEvaluationDate(@Nonnull Date evaluationDate) {
|
|
||||||
this.evaluationDate = evaluationDate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign the message using an inline signature made by the provided signing key.
|
|
||||||
*
|
|
||||||
* @param signingKeyProtector protector to unlock the signing key
|
|
||||||
* @param signingKey key ring containing the signing key
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector,
|
|
||||||
@Nonnull PGPSecretKeyRing signingKey)
|
|
||||||
throws PGPException {
|
|
||||||
return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add inline signatures with all secret key rings in the provided secret key ring collection.
|
|
||||||
*
|
|
||||||
* @param secrectKeyDecryptor decryptor to unlock the signing secret keys
|
|
||||||
* @param signingKeys collection of signing keys
|
|
||||||
* @param signatureType type of signature (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with any of the keys
|
|
||||||
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor,
|
|
||||||
@Nonnull Iterable<PGPSecretKeyRing> signingKeys,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws KeyException, PGPException {
|
|
||||||
for (PGPSecretKeyRing signingKey : signingKeys) {
|
|
||||||
addInlineSignature(secrectKeyDecryptor, signingKey, signatureType);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an inline-signature.
|
|
||||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
|
||||||
* of one-pass-signature packets.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the signing secret key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param signatureType type of signature (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws KeyException, PGPException {
|
|
||||||
return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an inline-signature.
|
|
||||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
|
||||||
* of one-pass-signature packets.
|
|
||||||
* <p>
|
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the signing secret key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param userId user-id of the signer
|
|
||||||
* @param signatureType signature type (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nullable CharSequence userId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws KeyException, PGPException {
|
|
||||||
return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an inline-signature.
|
|
||||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
|
||||||
* of one-pass-signature packets.
|
|
||||||
* <p>
|
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the signing secret key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param userId user-id of the signer
|
|
||||||
* @param signatureType signature type (binary, canonical text)
|
|
||||||
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if the key is invalid
|
|
||||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nullable CharSequence userId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType,
|
|
||||||
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback)
|
|
||||||
throws KeyException, PGPException {
|
|
||||||
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
|
|
||||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
|
||||||
throw new KeyException.UnboundUserIdException(
|
|
||||||
OpenPgpFingerprint.of(secretKey),
|
|
||||||
userId.toString(),
|
|
||||||
keyRingInfo.getLatestUserIdCertification(userId),
|
|
||||||
keyRingInfo.getUserIdRevocation(userId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
|
||||||
if (signingPubKeys.isEmpty()) {
|
|
||||||
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
|
||||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
|
||||||
if (signingSecKey == null) {
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
|
|
||||||
}
|
|
||||||
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
|
|
||||||
Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
|
|
||||||
: keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
|
|
||||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
|
||||||
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a binary inline signature using the signing key with the given keyId.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret key
|
|
||||||
* @param secretKey secret key ring
|
|
||||||
* @param keyId keyId of the signing (sub-)key
|
|
||||||
* @return builder
|
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
long keyId) throws PGPException {
|
|
||||||
return addInlineSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an inline signature using the signing key with the given keyId.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret key
|
|
||||||
* @param secretKey secret key ring
|
|
||||||
* @param keyId keyId of the signing (sub-)key
|
|
||||||
* @param signatureType signature type
|
|
||||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
|
||||||
* @return builder
|
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
long keyId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType,
|
|
||||||
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
|
|
||||||
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
|
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
|
||||||
if (signingPubKeys.isEmpty()) {
|
|
||||||
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
|
||||||
if (signingPubKey.getKeyID() == keyId) {
|
|
||||||
|
|
||||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
|
||||||
if (signingSecKey == null) {
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
|
|
||||||
}
|
|
||||||
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
|
|
||||||
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
|
|
||||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
|
||||||
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add detached signatures with all key rings from the provided secret key ring collection.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret signing keys
|
|
||||||
* @param signingKeys collection of signing key rings
|
|
||||||
* @param signatureType type of the signature (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with any of the keys
|
|
||||||
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull Iterable<PGPSecretKeyRing> signingKeys,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws PGPException {
|
|
||||||
for (PGPSecretKeyRing signingKey : signingKeys) {
|
|
||||||
addDetachedSignature(secretKeyDecryptor, signingKey, signatureType);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached signature.
|
|
||||||
* The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
|
||||||
* @param signingKey signing key
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing signingKey)
|
|
||||||
throws PGPException {
|
|
||||||
return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached signature.
|
|
||||||
* Detached signatures are not being added into the PGP message itself.
|
|
||||||
* Instead, they can be distributed separately to the message.
|
|
||||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param signatureType type of data that is signed (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws PGPException {
|
|
||||||
return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached signature.
|
|
||||||
* Detached signatures are not being added into the PGP message itself.
|
|
||||||
* Instead, they can be distributed separately to the message.
|
|
||||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
|
||||||
* <p>
|
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param userId user-id
|
|
||||||
* @param signatureType type of data that is signed (binary, canonical text)
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nullable CharSequence userId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws PGPException {
|
|
||||||
return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached signature.
|
|
||||||
* Detached signatures are not being added into the PGP message itself.
|
|
||||||
* Instead, they can be distributed separately to the message.
|
|
||||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
|
||||||
* <p>
|
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
|
||||||
* @param secretKey signing key
|
|
||||||
* @param userId user-id
|
|
||||||
* @param signatureType type of data that is signed (binary, canonical text)
|
|
||||||
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nullable CharSequence userId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType,
|
|
||||||
@Nullable BaseSignatureSubpackets.Callback subpacketCallback)
|
|
||||||
throws PGPException {
|
|
||||||
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
|
|
||||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
|
||||||
throw new KeyException.UnboundUserIdException(
|
|
||||||
OpenPgpFingerprint.of(secretKey),
|
|
||||||
userId.toString(),
|
|
||||||
keyRingInfo.getLatestUserIdCertification(userId),
|
|
||||||
keyRingInfo.getUserIdRevocation(userId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
|
||||||
if (signingPubKeys.isEmpty()) {
|
|
||||||
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
|
||||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
|
||||||
if (signingSecKey == null) {
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
|
|
||||||
}
|
|
||||||
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
|
|
||||||
Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
|
|
||||||
: keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
|
|
||||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
|
||||||
addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached binary signature using the signing key with the given keyId.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret key
|
|
||||||
* @param secretKey secret key ring
|
|
||||||
* @param keyId keyId of the signing (sub-)key
|
|
||||||
* @return builder
|
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
long keyId) throws PGPException {
|
|
||||||
return addDetachedSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a detached signature using the signing key with the given keyId.
|
|
||||||
*
|
|
||||||
* @param secretKeyDecryptor decryptor to unlock the secret key
|
|
||||||
* @param secretKey secret key ring
|
|
||||||
* @param keyId keyId of the signing (sub-)key
|
|
||||||
* @param signatureType signature type
|
|
||||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
|
||||||
* @return builder
|
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
long keyId,
|
|
||||||
@Nonnull DocumentSignatureType signatureType,
|
|
||||||
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
|
|
||||||
KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
|
|
||||||
|
|
||||||
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
|
|
||||||
if (signingPubKeys.isEmpty()) {
|
|
||||||
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
|
||||||
if (signingPubKey.getKeyID() == keyId) {
|
|
||||||
|
|
||||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
|
||||||
if (signingSecKey == null) {
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
|
|
||||||
}
|
|
||||||
PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
|
|
||||||
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
|
|
||||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
|
||||||
addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, true);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey,
|
|
||||||
@Nonnull PGPPrivateKey signingSubkey,
|
|
||||||
@Nullable BaseSignatureSubpackets.Callback subpacketCallback,
|
|
||||||
@Nonnull HashAlgorithm hashAlgorithm,
|
|
||||||
@Nonnull DocumentSignatureType signatureType,
|
|
||||||
boolean detached)
|
|
||||||
throws PGPException {
|
|
||||||
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID());
|
|
||||||
PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID());
|
|
||||||
PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm());
|
|
||||||
int bitStrength = signingSecretKey.getPublicKey().getBitStrength();
|
|
||||||
if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
|
|
||||||
throw new KeyException.UnacceptableSigningKeyException(
|
|
||||||
new KeyException.PublicKeyAlgorithmPolicyException(
|
|
||||||
OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength));
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);
|
|
||||||
|
|
||||||
// Subpackets
|
|
||||||
SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey());
|
|
||||||
SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets();
|
|
||||||
if (subpacketCallback != null) {
|
|
||||||
subpacketCallback.modifyHashedSubpackets(hashedSubpackets);
|
|
||||||
subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets);
|
|
||||||
}
|
|
||||||
generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets));
|
|
||||||
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets));
|
|
||||||
|
|
||||||
SigningMethod signingMethod = detached ?
|
|
||||||
SigningMethod.detachedSignature(generator, hashAlgorithm) :
|
|
||||||
SigningMethod.inlineSignature(generator, hashAlgorithm);
|
|
||||||
signingMethods.put(signingKeyIdentifier, signingMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Negotiate, which hash algorithm to use.
|
|
||||||
* <p>
|
|
||||||
* This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}.
|
|
||||||
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
|
|
||||||
* Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is
|
|
||||||
* used as a fallback.
|
|
||||||
*
|
|
||||||
* @param preferences preferences
|
|
||||||
* @param policy policy
|
|
||||||
* @return selected hash algorithm
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set<HashAlgorithm> preferences,
|
|
||||||
@Nonnull Policy policy) {
|
|
||||||
if (hashAlgorithmOverride != null) {
|
|
||||||
return hashAlgorithmOverride;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy)
|
|
||||||
.negotiateHashAlgorithm(preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey,
|
|
||||||
@Nonnull HashAlgorithm hashAlgorithm,
|
|
||||||
@Nonnull DocumentSignatureType signatureType)
|
|
||||||
throws PGPException {
|
|
||||||
int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm();
|
|
||||||
PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance()
|
|
||||||
.getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId());
|
|
||||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
|
|
||||||
signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey);
|
|
||||||
|
|
||||||
return signatureGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a map of key-ids and signing methods.
|
|
||||||
* For internal use.
|
|
||||||
*
|
|
||||||
* @return signing methods
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
|
|
||||||
return Collections.unmodifiableMap(signingMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
|
|
||||||
* If no override has been set, an accetable algorithm will be negotiated instead.
|
|
||||||
* <p>
|
|
||||||
* Note: To override the hash algorithm for signing, call this method *before* calling
|
|
||||||
* {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or
|
|
||||||
* {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}.
|
|
||||||
*
|
|
||||||
* @param hashAlgorithmOverride override hash algorithm
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) {
|
|
||||||
this.hashAlgorithmOverride = hashAlgorithmOverride;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the hash algorithm override (or null if no override is set).
|
|
||||||
*
|
|
||||||
* @return hash algorithm override
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public HashAlgorithm getHashAlgorithmOverride() {
|
|
||||||
return hashAlgorithmOverride;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,451 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.*
|
||||||
|
import org.pgpainless.PGPainless.Companion.getPolicy
|
||||||
|
import org.pgpainless.PGPainless.Companion.inspectKeyRing
|
||||||
|
import org.pgpainless.algorithm.DocumentSignatureType
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm
|
||||||
|
import org.pgpainless.algorithm.PublicKeyAlgorithm.Companion.requireFromId
|
||||||
|
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator.Companion.negotiateSignatureHashAlgorithm
|
||||||
|
import org.pgpainless.exception.KeyException
|
||||||
|
import org.pgpainless.exception.KeyException.*
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint.Companion.of
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
|
import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey
|
||||||
|
import org.pgpainless.policy.Policy
|
||||||
|
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SigningOptions {
|
||||||
|
|
||||||
|
val signingMethods: Map<SubkeyIdentifier, SigningMethod> = mutableMapOf()
|
||||||
|
private var _hashAlgorithmOverride: HashAlgorithm? = null
|
||||||
|
private var _evaluationDate: Date = Date()
|
||||||
|
|
||||||
|
val hashAlgorithmOverride: HashAlgorithm?
|
||||||
|
get() = _hashAlgorithmOverride
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
|
||||||
|
* If no override has been set, an acceptable algorithm will be negotiated instead.
|
||||||
|
* Note: To override the hash algorithm for signing, call this method *before* calling
|
||||||
|
* [addInlineSignature] or [addDetachedSignature].
|
||||||
|
*
|
||||||
|
* @param hashAlgorithmOverride override hash algorithm
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
fun overrideHashAlgorithm(hashAlgorithmOverride: HashAlgorithm) = apply {
|
||||||
|
_hashAlgorithmOverride = hashAlgorithmOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
val evaluationDate: Date
|
||||||
|
get() = _evaluationDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the evaluation date for signing keys with the given date.
|
||||||
|
*
|
||||||
|
* @param evaluationDate new evaluation date
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
fun setEvaluationDate(evaluationDate: Date) = apply {
|
||||||
|
_evaluationDate = evaluationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the message using an inline signature made by the provided signing key.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector protector to unlock the signing key
|
||||||
|
* @param signingKey key ring containing the signing key
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with the key
|
||||||
|
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply {
|
||||||
|
addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add inline signatures with all secret key rings in the provided secret key ring collection.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the signing secret keys
|
||||||
|
* @param signingKeys collection of signing keys
|
||||||
|
* @param signatureType type of signature (binary, canonical text)
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with any of the keys
|
||||||
|
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||||
|
signatureType: DocumentSignatureType) = apply {
|
||||||
|
signingKeys.forEach {
|
||||||
|
addInlineSignature(signingKeyProtector, it, null, signatureType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an inline-signature.
|
||||||
|
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||||
|
* of one-pass-signature packets.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the signing secret key
|
||||||
|
* @param signingKey signing key
|
||||||
|
* @param signatureType type of signature (binary, canonical text)
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with the key
|
||||||
|
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
signatureType: DocumentSignatureType) = apply {
|
||||||
|
addInlineSignature(signingKeyProtector, signingKey, null, signatureType)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an inline-signature.
|
||||||
|
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||||
|
* of one-pass-signature packets.
|
||||||
|
* <p>
|
||||||
|
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the signing secret key
|
||||||
|
* @param signingKey signing key
|
||||||
|
* @param userId user-id of the signer
|
||||||
|
* @param signatureType signature type (binary, canonical text)
|
||||||
|
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if the key is invalid
|
||||||
|
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
@JvmOverloads
|
||||||
|
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
userId: CharSequence? = null,
|
||||||
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null) = apply {
|
||||||
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
|
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||||
|
throw UnboundUserIdException(
|
||||||
|
of(signingKey),
|
||||||
|
userId.toString(),
|
||||||
|
keyRingInfo.getLatestUserIdCertification(userId),
|
||||||
|
keyRingInfo.getUserIdRevocation(userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
if (signingPubKeys.isEmpty()) {
|
||||||
|
throw UnacceptableSigningKeyException(of(signingKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (signingPubKey in signingPubKeys) {
|
||||||
|
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
|
val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector)
|
||||||
|
val hashAlgorithms =
|
||||||
|
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||||
|
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
|
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an inline signature using the signing key with the given keyId.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the secret key
|
||||||
|
* @param signingKey secret key ring
|
||||||
|
* @param keyId keyId of the signing (sub-)key
|
||||||
|
* @param signatureType signature type
|
||||||
|
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||||
|
* @return builder
|
||||||
|
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
||||||
|
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
||||||
|
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
@JvmOverloads
|
||||||
|
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
keyId: Long,
|
||||||
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null) = apply {
|
||||||
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
if (signingPubKeys.isEmpty()) {
|
||||||
|
throw UnacceptableSigningKeyException(of(signingKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (signingPubKey in signingPubKeys) {
|
||||||
|
if (signingPubKey.keyID != keyId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
|
val signingSubkey = unlockSecretKey(signingSecKey, signingKeyProtector)
|
||||||
|
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
|
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add detached signatures with all key rings from the provided secret key ring collection.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the secret signing keys
|
||||||
|
* @param signingKeys collection of signing key rings
|
||||||
|
* @param signatureType type of the signature (binary, canonical text)
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with any of the keys
|
||||||
|
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||||
|
signatureType: DocumentSignatureType) = apply {
|
||||||
|
signingKeys.forEach {
|
||||||
|
addDetachedSignature(signingKeyProtector, it, null, signatureType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a detached signature.
|
||||||
|
* Detached signatures are not being added into the PGP message itself.
|
||||||
|
* Instead, they can be distributed separately to the message.
|
||||||
|
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the secret signing key
|
||||||
|
* @param signingKey signing key
|
||||||
|
* @param signatureType type of data that is signed (binary, canonical text)
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with the key
|
||||||
|
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
signatureType: DocumentSignatureType) = apply {
|
||||||
|
addDetachedSignature(signingKeyProtector, signingKey, null, signatureType)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a detached signature.
|
||||||
|
* Detached signatures are not being added into the PGP message itself.
|
||||||
|
* Instead, they can be distributed separately to the message.
|
||||||
|
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||||
|
* <p>
|
||||||
|
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the secret signing key
|
||||||
|
* @param signingKey signing key
|
||||||
|
* @param userId user-id
|
||||||
|
* @param signatureType type of data that is signed (binary, canonical text)
|
||||||
|
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
|
||||||
|
* @return this
|
||||||
|
*
|
||||||
|
* @throws KeyException if something is wrong with the key
|
||||||
|
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
userId: String? = null,
|
||||||
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketCallback: Callback? = null) = apply {
|
||||||
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
|
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||||
|
throw UnboundUserIdException(
|
||||||
|
of(signingKey),
|
||||||
|
userId.toString(),
|
||||||
|
keyRingInfo.getLatestUserIdCertification(userId),
|
||||||
|
keyRingInfo.getUserIdRevocation(userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
if (signingPubKeys.isEmpty()) {
|
||||||
|
throw UnacceptableSigningKeyException(of(signingKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (signingPubKey in signingPubKeys) {
|
||||||
|
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
|
val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector)
|
||||||
|
val hashAlgorithms =
|
||||||
|
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||||
|
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
|
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a detached signature using the signing key with the given keyId.
|
||||||
|
*
|
||||||
|
* @param signingKeyProtector decryptor to unlock the secret key
|
||||||
|
* @param signingKey secret key ring
|
||||||
|
* @param keyId keyId of the signing (sub-)key
|
||||||
|
* @param signatureType signature type
|
||||||
|
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||||
|
* @return builder
|
||||||
|
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
||||||
|
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
||||||
|
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
||||||
|
*/
|
||||||
|
@Throws(KeyException::class, PGPException::class)
|
||||||
|
@JvmOverloads
|
||||||
|
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||||
|
signingKey: PGPSecretKeyRing,
|
||||||
|
keyId: Long,
|
||||||
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null) = apply {
|
||||||
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
|
|
||||||
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
if (signingPubKeys.isEmpty()) {
|
||||||
|
throw UnacceptableSigningKeyException(of(signingKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (signingPubKey in signingPubKeys) {
|
||||||
|
if (signingPubKey.keyID == keyId) {
|
||||||
|
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
|
val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector)
|
||||||
|
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
|
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addSigningMethod(signingKey: PGPSecretKeyRing,
|
||||||
|
signingSubkey: PGPPrivateKey,
|
||||||
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
signatureType: DocumentSignatureType,
|
||||||
|
detached: Boolean,
|
||||||
|
subpacketCallback: Callback? = null) {
|
||||||
|
val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID)
|
||||||
|
val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID)
|
||||||
|
val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm)
|
||||||
|
val bitStrength = signingSecretKey.publicKey.bitStrength
|
||||||
|
if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) {
|
||||||
|
throw UnacceptableSigningKeyException(
|
||||||
|
PublicKeyAlgorithmPolicyException(
|
||||||
|
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
|
||||||
|
}
|
||||||
|
|
||||||
|
val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
|
||||||
|
|
||||||
|
// Subpackets
|
||||||
|
val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
|
||||||
|
val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets()
|
||||||
|
if (subpacketCallback != null) {
|
||||||
|
subpacketCallback.modifyHashedSubpackets(hashedSubpackets)
|
||||||
|
subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets)
|
||||||
|
}
|
||||||
|
generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets))
|
||||||
|
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets))
|
||||||
|
|
||||||
|
val signingMethod =
|
||||||
|
if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm)
|
||||||
|
else SigningMethod.inlineSignature(generator, hashAlgorithm)
|
||||||
|
(signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negotiate, which hash algorithm to use.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm].
|
||||||
|
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
|
||||||
|
* Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is
|
||||||
|
* used as a fallback.
|
||||||
|
*
|
||||||
|
* @param preferences preferences
|
||||||
|
* @param policy policy
|
||||||
|
* @return selected hash algorithm
|
||||||
|
*/
|
||||||
|
private fun negotiateHashAlgorithm(preferences: Set<HashAlgorithm>,
|
||||||
|
policy: Policy): HashAlgorithm {
|
||||||
|
return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PGPException::class)
|
||||||
|
private fun createSignatureGenerator(privateKey: PGPPrivateKey,
|
||||||
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
signatureType: DocumentSignatureType): PGPSignatureGenerator {
|
||||||
|
return ImplementationFactory.getInstance()
|
||||||
|
.getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
|
||||||
|
.let { csb ->
|
||||||
|
PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun get() = SigningOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method of signing.
|
||||||
|
*/
|
||||||
|
class SigningMethod private constructor(
|
||||||
|
val signatureGenerator: PGPSignatureGenerator,
|
||||||
|
val isDetached: Boolean,
|
||||||
|
val hashAlgorithm: HashAlgorithm
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline-signature method.
|
||||||
|
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
|
||||||
|
*
|
||||||
|
* @param signatureGenerator signature generator
|
||||||
|
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||||
|
* @return inline signing method
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
||||||
|
SigningMethod(signatureGenerator, false, hashAlgorithm)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detached signing method.
|
||||||
|
* The resulting signature will not be added to the message, and instead can be distributed separately
|
||||||
|
* to the signed message.
|
||||||
|
*
|
||||||
|
* @param signatureGenerator signature generator
|
||||||
|
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||||
|
* @return detached signing method
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
||||||
|
SigningMethod(signatureGenerator, true, hashAlgorithm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue