Merge branch 'relaxDecryptionKeyConstraints'

This commit is contained in:
Paul Schaub 2023-04-25 13:28:26 +02:00
commit 6f3f529534
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
3 changed files with 68 additions and 5 deletions

View File

@ -43,15 +43,14 @@ import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.util.io.TeeInputStream;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.OpenPgpPacket;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil;
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
import org.pgpainless.decryption_verification.syntax_check.InputSymbol;
import org.pgpainless.decryption_verification.syntax_check.PDA;
import org.pgpainless.decryption_verification.syntax_check.StackSymbol;
import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil;
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
import org.pgpainless.exception.MalformedOpenPgpMessageException;
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
import org.pgpainless.exception.MissingDecryptionMethodException;
@ -674,7 +673,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
for (PGPPublicKey publicKey : info.getEncryptionSubkeys(EncryptionPurpose.ANY)) {
for (PGPPublicKey publicKey : info.getDecryptionSubkeys()) {
if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) {
PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID());
decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate));
@ -692,7 +691,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
}
KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date());
List<PGPPublicKey> encryptionKeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY);
List<PGPPublicKey> encryptionKeys = info.getDecryptionSubkeys();
for (PGPPublicKey key : encryptionKeys) {
if (key.getKeyID() == keyID) {
return secretKeys;

View File

@ -41,11 +41,15 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.KeyException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.util.KeyIdUtil;
import org.pgpainless.key.util.RevocationAttributes;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.signature.consumer.SignaturePicker;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
import org.pgpainless.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}.
@ -55,6 +59,8 @@ public class KeyRingInfo {
private static final Pattern PATTERN_EMAIL_FROM_USERID = Pattern.compile("<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>");
private static final Pattern PATTERN_EMAIL_EXPLICIT = Pattern.compile("^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$");
private static final Logger LOGGER = LoggerFactory.getLogger(KeyRingInfo.class);
private final PGPKeyRing keys;
private final Signatures signatures;
private final Date referenceDate;
@ -904,6 +910,7 @@ public class KeyRingInfo {
public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(EncryptionPurpose purpose) {
Date primaryExpiration = getPrimaryKeyExpirationDate();
if (primaryExpiration != null && primaryExpiration.before(referenceDate)) {
LOGGER.debug("Certificate is expired: Primary key is expired on " + DateUtil.formatUTCDate(primaryExpiration));
return Collections.emptyList();
}
@ -913,15 +920,18 @@ public class KeyRingInfo {
PGPPublicKey subKey = subkeys.next();
if (!isKeyValidlyBound(subKey.getKeyID())) {
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not validly bound.");
continue;
}
Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey));
if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) {
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is expired on " + DateUtil.formatUTCDate(subkeyExpiration));
continue;
}
if (!subKey.isEncryptionKey()) {
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " algorithm is not capable of encryption.");
continue;
}
@ -947,6 +957,42 @@ public class KeyRingInfo {
return encryptionKeys;
}
/**
* Return a list of all subkeys that could potentially be used to decrypt a message.
* Contrary to {@link #getEncryptionSubkeys(EncryptionPurpose)}, this method also includes revoked, expired keys,
* as well as keys which do not carry any encryption keyflags.
* Merely keys which use algorithms that cannot be used for encryption at all are excluded.
* That way, decryption of messages produced by faulty implementations can still be decrypted.
*
* @return decryption keys
*/
public @Nonnull List<PGPPublicKey> getDecryptionSubkeys() {
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
List<PGPPublicKey> decryptionKeys = new ArrayList<>();
while (subkeys.hasNext()) {
PGPPublicKey subKey = subkeys.next();
// subkeys have been valid at some point
if (subKey.getKeyID() != getKeyId()) {
PGPSignature binding = signatures.subkeyBindings.get(subKey.getKeyID());
if (binding == null) {
LOGGER.debug("Subkey " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " was never validly bound.");
continue;
}
}
// Public-Key algorithm can encrypt
if (!subKey.isEncryptionKey()) {
LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not encryption-capable.");
continue;
}
decryptionKeys.add(subKey);
}
return decryptionKeys;
}
/**
* Return a list of all keys which carry the provided key flag in their signature.
*

View File

@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.exception.MissingDecryptionMethodException;
@ -189,6 +190,23 @@ public class PreventDecryptionUsingNonEncryptionKeyTest {
}
@Test
public void canDecryptMessageDespiteMissingKeyFlag() throws IOException, PGPException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY);
ByteArrayInputStream msgIn = new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8));
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(msgIn)
.withOptions(new ConsumerOptions().addDecryptionKey(secretKeys));
Streams.drain(decryptionStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertEquals(new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()), metadata.getDecryptionKey());
}
@Test
@Disabled
public void nonEncryptionKeyCannotDecrypt() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY);