1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-10 14:21:09 +01:00

Create dedicated KeyException class for key-related exceptions.

This commit is contained in:
Paul Schaub 2022-03-15 14:04:59 +01:00
parent 6b3f37796c
commit a22336a795
13 changed files with 186 additions and 68 deletions

View file

@ -23,6 +23,7 @@ import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.KeyException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
@ -154,7 +155,7 @@ public class EncryptionOptions {
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose));
if (encryptionSubkeys.isEmpty()) {
throw new IllegalArgumentException("Key has no suitable encryption subkeys.");
throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key));
}
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
@ -193,16 +194,16 @@ public class EncryptionOptions {
try {
primaryKeyExpiration = info.getPrimaryKeyExpirationDate();
} catch (NoSuchElementException e) {
throw new IllegalArgumentException("Provided key " + OpenPgpFingerprint.of(key) + " does not have a valid/acceptable signature carrying a primary key expiration date.");
throw new KeyException.UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key));
}
if (primaryKeyExpiration != null && primaryKeyExpiration.before(evaluationDate)) {
throw new IllegalArgumentException("Provided key " + OpenPgpFingerprint.of(key) + " is expired: " + primaryKeyExpiration);
throw new KeyException.ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration);
}
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose));
if (encryptionSubkeys.isEmpty()) {
throw new IllegalArgumentException("Key " + OpenPgpFingerprint.of(key) + " has no suitable encryption subkeys.");
throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key));
}
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {

View file

@ -24,8 +24,7 @@ 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.KeyCannotSignException;
import org.pgpainless.exception.KeyValidationError;
import org.pgpainless.exception.KeyException;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
@ -120,13 +119,13 @@ public final class SigningOptions {
* @param signingKeys collection of signing keys
* @param signatureType type of signature (binary, canonical text)
* @return this
* @throws KeyValidationError if something is wrong with any of the keys
* @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
*/
public SigningOptions addInlineSignatures(SecretKeyRingProtector secrectKeyDecryptor,
Iterable<PGPSecretKeyRing> signingKeys,
DocumentSignatureType signatureType)
throws KeyValidationError, PGPException {
throws KeyException, PGPException {
for (PGPSecretKeyRing signingKey : signingKeys) {
addInlineSignature(secrectKeyDecryptor, signingKey, signatureType);
}
@ -141,14 +140,14 @@ public final class SigningOptions {
* @param secretKeyDecryptor decryptor to unlock the signing secret key
* @param secretKey signing key
* @param signatureType type of signature (binary, canonical text)
* @throws KeyValidationError if something is wrong with the key
* @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
* @return this
*/
public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
PGPSecretKeyRing secretKey,
DocumentSignatureType signatureType)
throws KeyValidationError, PGPException {
throws KeyException, PGPException {
return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
}
@ -164,14 +163,14 @@ public final class SigningOptions {
* @param userId user-id of the signer
* @param signatureType signature type (binary, canonical text)
* @return this
* @throws KeyValidationError if the key is invalid
* @throws KeyException if the key is invalid
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/
public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
PGPSecretKeyRing secretKey,
String userId,
DocumentSignatureType signatureType)
throws KeyValidationError, PGPException {
throws KeyException, PGPException {
return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
}
@ -188,7 +187,8 @@ public final class SigningOptions {
* @param signatureType signature type (binary, canonical text)
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
* @return this
* @throws KeyValidationError if the key is invalid
* @throws KeyException
* if the key is invalid
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/
public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
@ -196,19 +196,27 @@ public final class SigningOptions {
String userId,
DocumentSignatureType signatureType,
@Nullable BaseSignatureSubpackets.Callback subpacketsCallback)
throws KeyValidationError, PGPException {
throws KeyException, PGPException {
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
throw new KeyException.UnboundUserIdException(
OpenPgpFingerprint.of(secretKey),
userId,
keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId)
);
}
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyCannotSignException("Key " + OpenPgpFingerprint.of(secretKey) + " has no valid signing key.");
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());
@ -304,18 +312,23 @@ public final class SigningOptions {
throws PGPException {
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
throw new KeyException.UnboundUserIdException(
OpenPgpFingerprint.of(secretKey),
userId,
keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId)
);
}
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new KeyCannotSignException("Key has no valid signing key.");
throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
}
for (PGPPublicKey signingPubKey : signingPubKeys) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
if (signingSecKey == null) {
throw new PGPException("Missing secret key for signing key " + Long.toHexString(signingPubKey.getKeyID()));
throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
}
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(
secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
@ -340,8 +353,9 @@ public final class SigningOptions {
PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm());
int bitStrength = secretKey.getPublicKey().getBitStrength();
if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
throw new IllegalArgumentException("Public key algorithm policy violation: " +
publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable.");
throw new KeyException.UnacceptableSigningKeyException(
new KeyException.PublicKeyAlgorithmPolicyException(
OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength));
}
PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);

View file

@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.exception;
import org.bouncycastle.openpgp.PGPException;
public class KeyCannotSignException extends PGPException {
public KeyCannotSignException(String message) {
super(message);
}
}

View file

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.exception;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.util.DateUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date;
public abstract class KeyException extends RuntimeException {
private final OpenPgpFingerprint fingerprint;
protected KeyException(@Nonnull String message, @Nonnull OpenPgpFingerprint fingerprint) {
super(message);
this.fingerprint = fingerprint;
}
protected KeyException(@Nonnull String message, @Nonnull OpenPgpFingerprint fingerprint, @Nonnull Throwable underlying) {
super(message, underlying);
this.fingerprint = fingerprint;
}
public OpenPgpFingerprint getFingerprint() {
return fingerprint;
}
public static class ExpiredKeyException extends KeyException {
public ExpiredKeyException(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull Date expirationDate) {
super("Key " + fingerprint + " is expired. Expiration date: " + DateUtil.formatUTCDate(expirationDate), fingerprint);
}
}
public static class UnacceptableEncryptionKeyException extends KeyException {
public UnacceptableEncryptionKeyException(@Nonnull OpenPgpFingerprint fingerprint) {
super("Key " + fingerprint + " has no acceptable encryption key.", fingerprint);
}
public UnacceptableEncryptionKeyException(@Nonnull PublicKeyAlgorithmPolicyException reason) {
super("Key " + reason.getFingerprint() + " has no acceptable encryption key.", reason.getFingerprint(), reason);
}
}
public static class UnacceptableSigningKeyException extends KeyException {
public UnacceptableSigningKeyException(@Nonnull OpenPgpFingerprint fingerprint) {
super("Key " + fingerprint + " has no acceptable signing key.", fingerprint);
}
public UnacceptableSigningKeyException(@Nonnull PublicKeyAlgorithmPolicyException reason) {
super("Key " + reason.getFingerprint() + " has no acceptable signing key.", reason.getFingerprint(), reason);
}
}
public static class UnacceptableSelfSignatureException extends KeyException {
public UnacceptableSelfSignatureException(@Nonnull OpenPgpFingerprint fingerprint) {
super("Key " + fingerprint + " does not have a valid/acceptable signature to derive an expiration date from.", fingerprint);
}
}
public static class MissingSecretKeyException extends KeyException {
private final long missingSecretKeyId;
public MissingSecretKeyException(@Nonnull OpenPgpFingerprint fingerprint, long keyId) {
super("Key " + fingerprint + " does not contain a secret key for public key " + Long.toHexString(keyId), fingerprint);
this.missingSecretKeyId = keyId;
}
public long getMissingSecretKeyId() {
return missingSecretKeyId;
}
}
public static class PublicKeyAlgorithmPolicyException extends KeyException {
private final long violatingSubkeyId;
public PublicKeyAlgorithmPolicyException(@Nonnull OpenPgpFingerprint fingerprint, long keyId, @Nonnull PublicKeyAlgorithm algorithm, int bitSize) {
super("Subkey " + Long.toHexString(keyId) + " of key " + fingerprint + " is violating the Public Key Algorithm Policy:\n" +
algorithm + " of size " + bitSize + " is not acceptable.", fingerprint);
this.violatingSubkeyId = keyId;
}
public long getViolatingSubkeyId() {
return violatingSubkeyId;
}
}
public static class UnboundUserIdException extends KeyException {
public UnboundUserIdException(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull String userId,
@Nullable PGPSignature userIdSignature, @Nullable PGPSignature userIdRevocation) {
super(errorMessage(fingerprint, userId, userIdSignature, userIdRevocation), fingerprint);
}
private static String errorMessage(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull String userId,
@Nullable PGPSignature userIdSignature, @Nullable PGPSignature userIdRevocation) {
String errorMessage = "UserID '" + userId + "' is not valid for key " + fingerprint + ": ";
if (userIdSignature == null) {
return errorMessage + "Missing binding signature.";
}
if (userIdRevocation != null) {
return errorMessage + "UserID is revoked.";
}
return errorMessage + "Unacceptable binding signature.";
}
}
}

View file

@ -4,6 +4,10 @@
package org.pgpainless.exception;
/**
* This exception gets thrown, when the integrity of an OpenPGP key is broken.
* That could happen on accident, or during an active attack, so take this exception seriously.
*/
public class KeyIntegrityException extends AssertionError {
public KeyIntegrityException() {

View file

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.exception;
import org.bouncycastle.openpgp.PGPSignature;
public class KeyValidationError extends AssertionError {
public KeyValidationError(String userId, PGPSignature userIdSig, PGPSignature userIdRevocation) {
super("User-ID '" + userId + "' is not valid: Sig: " + userIdSig + " Rev: " + userIdRevocation);
}
}

View file

@ -36,7 +36,7 @@ import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.KeyValidationError;
import org.pgpainless.exception.KeyException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.util.RevocationAttributes;
@ -949,7 +949,12 @@ public class KeyRingInfo {
*/
public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(String userId, EncryptionPurpose purpose) {
if (userId != null && !isUserIdValid(userId)) {
throw new KeyValidationError(userId, getLatestUserIdCertification(userId), getUserIdRevocation(userId));
throw new KeyException.UnboundUserIdException(
OpenPgpFingerprint.of(keys),
userId,
getLatestUserIdCertification(userId),
getUserIdRevocation(userId)
);
}
return getEncryptionSubkeys(purpose);